Search code examples
phpjsonpropelcamelcasingpropel2

Propel2: lowercase/camelCase keys when converting ObjectCollection toJSON()


How can we convert the keys of a toJson() returned object to lowercase or camelCase? Consider the following example:

Query:

     $foo = FooQuery::create()
        ->filterByBar($bar)
        ->findOne()
        ->toJson();

Result:

{"Id": 1, "Bar":"StackOverflow"}

It seems to be PascalCase by default. How can I get lowercase properties on the json result?

The function I'm referring to can be found here and is applied to an ObjectCollection.

Update: I want to avoid using arrays as: array_change_key_case() does not work for multidimensional arrays when dealing with complex objects.

I know this can be achieved through some modifications but I want to know if there's a better approach, preferably without casting to an array first for performance purposes.


Solution

  • There is a way to configure your generated classes to use camelCase keys. In your propel.json (or .yaml, .php .ini .xml) configuration file add the objectModel as follows:

    "generator": {
      "defaultConnection": "bookstore",
      "connections": [ "bookstore" ],
      "objectModel": {
        "defaultKeyType": "camelName"
      }
    }
    

    This will make all your keys camelCased but it turns out that this only works with the toArray() method. When you call toJSON() you're actually using the exportTo('JSON') method. If you look at the exportTo method you can see that it is calling:

    $this->toArray(TableMap::TYPE_PHPNAME, $includeLazyLoadColumns, array(), true)
    

    This is forcing exportTo('JSON') and toJSON() to use TableMap::TYPE_PHPNAME as the key type. If you look at the toArray method definition it uses your "defaultKeyType" as the default $keyType. If you call toArray() without any parameters and you have "defaultKeyType": "camelName" then it will use TableMap::TYPE_CAMELNAME and therefore return all the keys as camelCase.

    The root of the problem is in Propel's generator classes. The base classes are generated in propel/src/Propel/Generator/Builder/Om/ObjectBuilder.php If we look at how it generates the toArray method we find:

    public function toArray(\$keyType = TableMap::$defaultKeyType, \$includeLazyLoadColumns = true, \$alreadyDumpedObjects = array()" . ($hasFks ? ", \$includeForeignObjects = false" : '') . ")
    

    The important point here is that it is using TableMap::$defaultKeyType. Now if we look at exportTo method generation we have to look in templates/baseObjectMethods.php and the exportTo method definition is as follows:

    public function exportTo($parser, $includeLazyLoadColumns = true)
    {
        if (!$parser instanceof AbstractParser) {
            $parser = AbstractParser::getParser($parser);
        }
    
        return $parser->fromArray($this->toArray(TableMap::TYPE_PHPNAME, $includeLazyLoadColumns, array(), true));
    }
    

    The important point here is that it uses the hardcoded value TableMap::TYPE_PHPNAME. If you change that hardcoded value to TableMap::TYPE_CAMELNAME and regenerate your classes then toJSON() will give all keys as camelCase.

    So unfortunately you cannot make toJSON use camelCase without modifying the source. I would think that the exportTo method should use the defaultKeyType so we can use the configuration to modify this behavior. That being said there may be a perfectly good reason to have the hardcoded value instead of a configurable value.

    Update: It looks like this only works with a single instance of each of the generated model classes. With the ObjectCollection and Collection classes the toArray and exportTo methods use hardcoded values of TableMap::TYPE_PHPNAME

    Propel/Runtime/Collection/Collection.php

    public function exportTo($parser, $usePrefix = true, $includeLazyLoadColumns = true)
    {
        if (!$parser instanceof AbstractParser) {
            $parser = AbstractParser::getParser($parser);
        }
    
        $array = $this->toArray(null, $usePrefix, TableMap::TYPE_PHPNAME, $includeLazyLoadColumns);
    
        return $parser->listFromArray($array, lcfirst($this->getPluralModelName()));
    }
    

    Propel/Runtime/Collection/ObjectCollection.php

    public function toArray($keyColumn = null, $usePrefix = false, $keyType = TableMap::TYPE_CAMELNAME, $includeLazyLoadColumns = true, $alreadyDumpedObjects = [])
    {
        $ret = [];
        $keyGetterMethod = 'get' . $keyColumn;
    
        /** @var $obj ActiveRecordInterface */
        foreach ($this->data as $key => $obj) {
            $key = null === $keyColumn ? $key : $obj->$keyGetterMethod();
            $key = $usePrefix ? ($this->getModel() . '_' . $key) : $key;
            $ret[$key] = $obj->toArray($keyType, $includeLazyLoadColumns, $alreadyDumpedObjects, true);
        }
    
        return $ret;
    }
    

    So once again it would be nice if we could use the configuration file to set these to TableMap::CAMELNAME but unfortunately that won't work.