From 1b087ddc2c31fe1fb18f2182180b815bf477c89c Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 14 Sep 2011 18:56:01 +0200 Subject: [PATCH 01/11] Add a camelize option. Still need to test with the json and yaml formats --- .../default/parts/configuration.php | 6 ++++ .../default/parts/doSave.php | 1 + .../default/parts/getSerializer.php | 4 +-- .../default/skeleton/config/generator.yml | 1 + ...ctrineRestGeneratorConfiguration.class.php | 3 +- lib/serializer/sfResourceSerializer.class.php | 32 ++++++++++++++++--- lib/test/sfTesterJsonResponse.class.php | 2 +- 7 files changed, 40 insertions(+), 9 deletions(-) diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php b/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php index 514ceee..50815f6 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php @@ -91,6 +91,12 @@ public function getSeparator() config['default']['separator']) ?> } + public function getCamelize() + { + return asPhp(isset($this->config['default']['camelize']) ? $this->config['default']['camelize'] : true) ?>; +config['default']['camelize']) ?> + } + diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/doSave.php b/data/generator/sfDoctrineRestGenerator/default/parts/doSave.php index 38a1b5d..14d793a 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/doSave.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/doSave.php @@ -2,6 +2,7 @@ protected function doSave() { $this->object->save(); +configuration->getValue('default.update_key', Doctrine::getTable($this->getModelClass())->getIdentifier()); ?> // Set a Location header with the path to the new / updated object $this->getResponse()->setHttpHeader('Location', $this->getController()->genUrl( array_merge(array( diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/getSerializer.php b/data/generator/sfDoctrineRestGenerator/default/parts/getSerializer.php index b68d95b..ca05915 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/getSerializer.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/getSerializer.php @@ -4,11 +4,11 @@ protected function getSerializer() { try { - $this->serializer = sfResourceSerializer::getInstance($this->getFormat()); + $this->serializer = sfResourceSerializer::getInstance($this->getFormat(), asPhp($this->configuration->getValue('default.camelize')) ?>); } catch (sfException $e) { - $this->serializer = sfResourceSerializer::getInstance('configuration->getValue('get.default_format') ?>'); + $this->serializer = sfResourceSerializer::getInstance('configuration->getValue('get.default_format') ?>', asPhp($this->configuration->getValue('default.camelize')) ?>); throw new sfException($e->getMessage()); } } diff --git a/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml b/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml index 9a437f1..c1fb347 100644 --- a/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml +++ b/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml @@ -10,6 +10,7 @@ generator: # formats_enabled: [ json, xml, yaml ] # enabled formats # formats_strict: true # separator: ',' # separator used for multiple filters +# camelize: true get: # additional_params: [] # list here additionnal params names, which are not object properties # default_format: json # the default format of the response. If not set, will default to json. accepted values are "json", "xml" or "yaml" diff --git a/lib/generator/sfDoctrineRestGeneratorConfiguration.class.php b/lib/generator/sfDoctrineRestGeneratorConfiguration.class.php index 421cc02..bd1f9e3 100644 --- a/lib/generator/sfDoctrineRestGeneratorConfiguration.class.php +++ b/lib/generator/sfDoctrineRestGeneratorConfiguration.class.php @@ -20,7 +20,8 @@ protected function compile() 'fields' => $this->getFieldsDefault(), 'formats_enabled' => $this->getFormatsEnabled(), 'formats_strict' => $this->getFormatsStrict(), - 'separator' => $this->getSeparator() + 'separator' => $this->getSeparator(), + 'camelize' => $this->getCamelize() ), 'get' => array( 'additional_params' => $this->getAdditionalParams(), diff --git a/lib/serializer/sfResourceSerializer.class.php b/lib/serializer/sfResourceSerializer.class.php index 10a3411..5525bdc 100644 --- a/lib/serializer/sfResourceSerializer.class.php +++ b/lib/serializer/sfResourceSerializer.class.php @@ -2,6 +2,8 @@ abstract class sfResourceSerializer { + private $camelize = true; + abstract public function getContentType(); /** @@ -11,21 +13,39 @@ abstract public function getContentType(); * @author CakePHP * @see http://book.cakephp.org/view/572/Class-methods * @param string $string The string to camelize - * @return string with CamelCase + * @return string with CamelCase or underscored depending on configuration */ protected function camelize($string) { - return str_replace(" ", "", ucwords(str_replace("_", " ", $string))); + if ($this->camelize) + { + return str_replace(" ", "", ucwords(str_replace("_", " ", $string))); + } + else + { + return sfInflector::underscore($string); + } + } + + /** + * Tell the serializer to camelize names or to let them flat + * + * @param boolean $camelize + */ + public function setCamelize($camelize) + { + $this->camelize = $camelize; } /** - * Creates an instance of a seriazlizer + * Creates an instance of a serializer * * @param string $format The serializer format (xml, json, etc.) + * @param boolean $camelize Tell the serializer to Camelize nodes * @return object a serializer object * @throws sfException */ - public static function getInstance($format = 'xml') + public static function getInstance($format = 'xml', $camelize = true) { $classname = sprintf('sfResourceSerializer%s', ucfirst($format)); @@ -34,7 +54,9 @@ public static function getInstance($format = 'xml') throw new sfException(sprintf('Could not find seriaizer "%s"', $classname)); } - return new $classname; + $serializer = new $classname; + $serializer->setCamelize($camelize); + return $serializer; } abstract public function serialize($array, $rootNodeName = 'data', $collection = true); diff --git a/lib/test/sfTesterJsonResponse.class.php b/lib/test/sfTesterJsonResponse.class.php index d6e9518..dd751be 100644 --- a/lib/test/sfTesterJsonResponse.class.php +++ b/lib/test/sfTesterJsonResponse.class.php @@ -20,7 +20,7 @@ public function initialize() /** * Try to decode the response body from json format - * + * * @return boolean */ public function isJson() From b1ac7bfd4b918068515103e0f47ac4ea78979277 Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Thu, 15 Sep 2011 10:56:56 +0200 Subject: [PATCH 02/11] Add a root_name option allowing to change the default Xml name. --- .../default/parts/configuration.php | 9 ++++++--- .../default/parts/indexAction.php | 2 +- .../sfDoctrineRestGenerator/default/parts/showAction.php | 2 +- .../default/skeleton/config/generator.yml | 1 + .../sfDoctrineRestGeneratorConfiguration.class.php | 3 ++- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php b/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php index 50815f6..ac91ec4 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php @@ -3,10 +3,7 @@ /** * getModuleName() ?> module configuration. * - * @package ##PROJECT_NAME## * @subpackage getModuleName()."\n" ?> - * @author ##AUTHOR_NAME## - * @version SVN: $Id: configuration.php 24171 2009-11-19 16:37:50Z Kris.Wallsmith $ */ abstract class BasegetModuleName()) ?>GeneratorConfiguration extends sfDoctrineRestGeneratorConfiguration { @@ -97,6 +94,12 @@ public function getCamelize() config['default']['camelize']) ?> } + public function getRootName() + { + return asPhp(isset($this->config['default']['root_name']) ? $this->config['default']['root_name'] : $this->getModelClass()) ?>; +config['default']['root_name']) ?> + } + diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/indexAction.php b/data/generator/sfDoctrineRestGenerator/default/parts/indexAction.php index 8f04b47..74a2436 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/indexAction.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/indexAction.php @@ -82,6 +82,6 @@ public function executeIndex(sfWebRequest $request) $serializer = $this->getSerializer(); $this->getResponse()->setContentType($serializer->getContentType()); - $this->output = $serializer->serialize($this->objects, $this->model); + $this->output = $serializer->serialize($this->objects, asPhp($this->configuration->getValue('default.root_name')); ?>); unset($this->objects); } diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/showAction.php b/data/generator/sfDoctrineRestGenerator/default/parts/showAction.php index 935934f..9e836d0 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/showAction.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/showAction.php @@ -61,6 +61,6 @@ public function executeShow(sfWebRequest $request) $serializer = $this->getSerializer(); $this->getResponse()->setContentType($serializer->getContentType()); - $this->output = $serializer->serialize($this->objects[0], $this->model, false); + $this->output = $serializer->serialize($this->objects[0], asPhp($this->configuration->getValue('default.root_name')); ?>, false); unset($this->objects); } diff --git a/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml b/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml index c1fb347..a170b02 100644 --- a/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml +++ b/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml @@ -11,6 +11,7 @@ generator: # formats_strict: true # separator: ',' # separator used for multiple filters # camelize: true +# root_name: ##MODEL_CLASS## get: # additional_params: [] # list here additionnal params names, which are not object properties # default_format: json # the default format of the response. If not set, will default to json. accepted values are "json", "xml" or "yaml" diff --git a/lib/generator/sfDoctrineRestGeneratorConfiguration.class.php b/lib/generator/sfDoctrineRestGeneratorConfiguration.class.php index bd1f9e3..1da3537 100644 --- a/lib/generator/sfDoctrineRestGeneratorConfiguration.class.php +++ b/lib/generator/sfDoctrineRestGeneratorConfiguration.class.php @@ -21,7 +21,8 @@ protected function compile() 'formats_enabled' => $this->getFormatsEnabled(), 'formats_strict' => $this->getFormatsStrict(), 'separator' => $this->getSeparator(), - 'camelize' => $this->getCamelize() + 'camelize' => $this->getCamelize(), + 'root_name' => $this->getRootName(), ), 'get' => array( 'additional_params' => $this->getAdditionalParams(), From 3e183b26c2fa2c94af219410d88051798492b858 Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 26 Oct 2011 19:26:12 +0200 Subject: [PATCH 03/11] Improve the Json serializer to use the same naming conventions as the XML one, and work on XML collections for the XML serializer --- .../sfResourceSerializerJson.class.php | 89 ++++++++++++++++++- .../sfResourceSerializerXml.class.php | 66 +++++++++----- 2 files changed, 128 insertions(+), 27 deletions(-) diff --git a/lib/serializer/sfResourceSerializerJson.class.php b/lib/serializer/sfResourceSerializerJson.class.php index cfcbf79..b7ccf8c 100644 --- a/lib/serializer/sfResourceSerializerJson.class.php +++ b/lib/serializer/sfResourceSerializerJson.class.php @@ -1,5 +1,9 @@ $result) + { + $array[$key] = array($rootNodeName => $result); + } + + if ($pluralRootNodeName) + { + $array = array($pluralRootNodeName => $this->pluralizeCollections($array)); + } + else + { + $array = array($rootNodeName.'s' => $this->pluralizeCollections($array)); + } + } + else + { + $array = array($rootNodeName => $this->pluralizeCollections($array)); + } + return json_encode($array); } + /** + * Mimic the XML behavior for Json serialization + * + * @param array $array + * @return array + */ + public function pluralizeCollections($array) + { + foreach ($array as $key => $nodes) + { + if (is_array($nodes) && isset($nodes[0])) + { + foreach ($nodes as $noderesult) + { + $array[$key.'s'][] = array($key => $noderesult); + } + unset($array[$key]); + } + elseif (is_array($nodes)) + { + $array[$key] = $this->pluralizeCollections($nodes); + } + } + return $array; + } + public function unserialize($payload) { - return json_decode($payload, true); + $array = json_decode($payload, true); + if ($array) + { + $array = array_shift($array); + } + + $array = $this->unPluralizeCollections($array); + + //var_dump($array['languages']);die(); + + return $array; + } + + public function unPluralizeCollections($array) + { + foreach ($array as $key => $nodes) + { + if (is_array($nodes) && isset($nodes[0])) + { + $group_name = key($nodes[0]); + + foreach ($nodes as $nodekey => $noderesult) + { + unset($array[$key][$nodekey]); + $array[$key][$group_name][] = $noderesult[$group_name]; + } + } + elseif (is_array($nodes)) + { + $array[$key] = $this->unPluralizeCollections($nodes); + } + } + return $array; } } \ No newline at end of file diff --git a/lib/serializer/sfResourceSerializerXml.class.php b/lib/serializer/sfResourceSerializerXml.class.php index 10d62f1..223832b 100644 --- a/lib/serializer/sfResourceSerializerXml.class.php +++ b/lib/serializer/sfResourceSerializerXml.class.php @@ -7,15 +7,21 @@ public function getContentType() return 'application/xml'; } - public function serialize($array, $rootNodeName = 'data', $collection = true) + public function serialize($array, $rootNodeName = 'data', $collection = true, $pluralRootNodeName = false) { $camelizedRootNodeName = $this->camelize($rootNodeName); - return $this->arrayToXml($array, $camelizedRootNodeName, 0, $collection); + + if ($pluralRootNodeName) + { + $pluralRootNodeName = $this->camelize($pluralRootNodeName); + } + + return $this->arrayToXml($array, $camelizedRootNodeName, 0, $collection, $pluralRootNodeName); } /** * Transform the payload into array assuming the payload is XML formatted. - * + * * @param string $payload * @return array * @throw Exception @@ -30,6 +36,11 @@ public function unserialize($payload) throw new sfException("Empty payload, can't unserialize it."); } + // Remove all the XML comments + // Because SimpleXml use them as SimpleXmlElement and there is NO way + // to know if a node is a comment or an Element. + $payload = preg_replace('~~sm', '', $payload); + // Try to parse the XML $xml = @simplexml_load_string( $payload, @@ -42,7 +53,7 @@ public function unserialize($payload) { $errors = libxml_get_errors(); $exception_message = ''; - + foreach ($errors as $error) { $exception_message .= $this->formatXmlError($error); @@ -51,21 +62,8 @@ public function unserialize($payload) libxml_clear_errors(); throw new sfException("XML parsing error(s): \n".$exception_message); } - - $return = $this->unserializeToArray($xml); - // Shift any root node and return only the nested array - if (is_array($return) && count($return) == 1) - { - // Don't want to break up the $return array - $return_shifted = $return; - $collection_return = array_shift($return_shifted); - - if (is_array($collection_return)) - { - $return = $collection_return; - } - } + $return = $this->unserializeToArray($xml); return $return; } @@ -79,7 +77,7 @@ public function unserialize($payload) protected function formatXmlError($error) { $return = "\n\n"; - + switch ($error->level) { case LIBXML_ERR_WARNING: @@ -118,10 +116,12 @@ protected function unserializeToArray($data) if ( (!is_array($item) && (!is_object($item))) || ($item instanceof SimpleXMLElement && - (count((array) $item) < 1 || (trim((string)$item) === '') ) + (count((array) $item) < 1 || (trim((string)$item) === '') ) && + !(($tmp = (array) $item) && count($tmp) > 0 && !isset($tmp[0])) // Deep array with keys? ) ) { + $item = trim((string)$item); unset($data[$name]); @@ -129,6 +129,10 @@ protected function unserializeToArray($data) { $data[sfInflector::underscore($name)] = $this->unserializeToArray($item, true); } + else + { + $data[sfInflector::underscore($name)] = null; + } } else { @@ -140,14 +144,21 @@ protected function unserializeToArray($data) return $data; } - protected function arrayToXml($array, $rootNodeName = 'Data', $level = 0, $collection = true) + protected function arrayToXml($array, $rootNodeName = 'Data', $level = 0, $collection = true, $pluralRootNodeName = false) { $xml = ''; if (0 == $level) { - $plural = (true === $collection) ? 's' : ''; - $xml .= '<'.$rootNodeName.$plural.'>'; + if ($pluralRootNodeName) + { + $xml .= '<'.$pluralRootNodeName.'>'; + } + else + { + $plural = (true === $collection) ? 's' : ''; + $xml .= '<'.$rootNodeName.$plural.'>'; + } } foreach ($array as $key => $value) @@ -194,7 +205,14 @@ protected function arrayToXml($array, $rootNodeName = 'Data', $level = 0, $colle if (0 == $level) { - $xml .= ''; + if ($pluralRootNodeName) + { + $xml .= ''; + } + else + { + $xml .= ''; + } } return $xml; From 87a7f1bd708da1f7766800be4c359b06a7432835 Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 26 Oct 2011 19:28:17 +0200 Subject: [PATCH 04/11] Some doc / cleaning on Json --- lib/serializer/sfResourceSerializerJson.class.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/serializer/sfResourceSerializerJson.class.php b/lib/serializer/sfResourceSerializerJson.class.php index b7ccf8c..37d2de3 100644 --- a/lib/serializer/sfResourceSerializerJson.class.php +++ b/lib/serializer/sfResourceSerializerJson.class.php @@ -73,11 +73,15 @@ public function unserialize($payload) $array = $this->unPluralizeCollections($array); - //var_dump($array['languages']);die(); - return $array; } + /** + * Deal with collections + * + * @param array $array + * @return array + */ public function unPluralizeCollections($array) { foreach ($array as $key => $nodes) From ac0c89b014bea577cda411213098a06e82920037 Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 26 Oct 2011 20:39:13 +0200 Subject: [PATCH 05/11] Add a plural_root_name option to allow custom plural root name on serializer --- .../sfDoctrineRestGenerator/default/parts/configuration.php | 6 ++++++ .../default/skeleton/config/generator.yml | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php b/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php index ac91ec4..d367465 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/configuration.php @@ -100,6 +100,12 @@ public function getRootName() config['default']['root_name']) ?> } + public function getPluralRootName() + { + return asPhp(isset($this->config['default']['plural_root_name']) ? $this->config['default']['plural_root_name'] : false) ?>; +config['default']['plural_root_name']) ?> + } + diff --git a/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml b/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml index a170b02..bb8be73 100644 --- a/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml +++ b/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml @@ -12,6 +12,7 @@ generator: # separator: ',' # separator used for multiple filters # camelize: true # root_name: ##MODEL_CLASS## +# plural_root_name: false # you can overide the plural root node name (if false, we add a "s" to root_name) get: # additional_params: [] # list here additionnal params names, which are not object properties # default_format: json # the default format of the response. If not set, will default to json. accepted values are "json", "xml" or "yaml" @@ -26,7 +27,7 @@ generator: # pagination_enabled: false # set to true to activate the pagination # pagination_custom_page_size: false # set to true to allow the client to pass a page_size parameter # pagination_page_size: 100 # the default number of items in a page -# sort_custom: false # set to true to allow the client to pass a sort_by and a sort_order parameter +# sort_custom: false # set to true to allow the client to pass a orderby and a sortorder parameter # sort_default: [] # set to [column, asc|desc] in order to sort on a column # filters: # list here the filters # created_at: { date_format: 'd-m-Y', multiple: true } # for instance From 5c77580f3c288d3237f3e59d535b8d41f92064d6 Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 26 Oct 2011 20:40:11 +0200 Subject: [PATCH 06/11] Same as previous commit, the option is added on all the serializer --- lib/serializer/sfResourceSerializer.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/serializer/sfResourceSerializer.class.php b/lib/serializer/sfResourceSerializer.class.php index 5525bdc..229d2e2 100644 --- a/lib/serializer/sfResourceSerializer.class.php +++ b/lib/serializer/sfResourceSerializer.class.php @@ -59,6 +59,6 @@ public static function getInstance($format = 'xml', $camelize = true) return $serializer; } - abstract public function serialize($array, $rootNodeName = 'data', $collection = true); + abstract public function serialize($array, $rootNodeName = 'data', $collection = true, $plural_root_name = false); abstract public function unserialize($payload); } \ No newline at end of file From 3561cbe5f2368cac5bbd38459cf934820d2cef28 Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 26 Oct 2011 20:41:08 +0200 Subject: [PATCH 07/11] Update the Yaml serializer to fit the new interface --- lib/serializer/sfResourceSerializerYaml.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/serializer/sfResourceSerializerYaml.class.php b/lib/serializer/sfResourceSerializerYaml.class.php index c85fe0f..ca1fc58 100644 --- a/lib/serializer/sfResourceSerializerYaml.class.php +++ b/lib/serializer/sfResourceSerializerYaml.class.php @@ -7,7 +7,7 @@ public function getContentType() return 'application/yaml'; } - public function serialize($array, $rootNodeName = 'data', $collection = true) + public function serialize($array, $rootNodeName = 'data', $collection = true, $pluralRootNodeName = false) { return sfYaml::dump(array($rootNodeName => $array), 5); } From 0bfaa8ca9fd7d5d0b2e461c48f394c20e641565b Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 26 Oct 2011 20:43:18 +0200 Subject: [PATCH 08/11] Improve the validate* methods. We are now keeping the cleaned params array on the whole process. --- .../default/parts/validate.php | 34 +++++++++++++++++-- .../default/parts/validateCreate.php | 11 +++--- .../default/parts/validateIndex.php | 5 ++- .../default/parts/validateUpdate.php | 11 +++--- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/validate.php b/data/generator/sfDoctrineRestGenerator/default/parts/validate.php index 0c92b20..4e9de32 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/validate.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/validate.php @@ -1,5 +1,6 @@ /** * Applies a set of validators to an array of parameters + * The cleaned value replace * * @param array $params An array of parameters * @param array $validators An array of validators @@ -7,6 +8,12 @@ */ public function validate($params, $validators, $prefix = '') { + if ($params === null && is_array($validators)) + { + // The case of an empty collection + return null; + } + $unused = array_keys($validators); foreach ($params as $name => $value) @@ -19,12 +26,31 @@ public function validate($params, $validators, $prefix = '') { if (is_array($validators[$name])) { - // validator for a related object - $this->validate($value, $validators[$name], $prefix.$name.'.'); + if (is_array($value) && isset($value[0])) + { + // We are on a list of array, not a related object + foreach ($value as $key => $val) + { + $params[$name][$key] = $this->validate($val, $validators[$name], $prefix.$name.'.'); + } + } + else + { + // validator for a related object + $params[$name] = $this->validate($value, $validators[$name], $prefix.$name.'.'); + } + } + elseif (is_array($value) && isset($value[0]) && $validators[$name] instanceof sfValidatorBase) + { + // We are on a list of value + foreach ($value as $key => $val) + { + $params[$name][$key] = $validators[$name]->clean($val); + } } else { - $validators[$name]->clean($value); + $params[$name] = $validators[$name]->clean($value); } unset($unused[array_search($name, $unused, true)]); @@ -46,4 +72,6 @@ public function validate($params, $validators, $prefix = '') throw new sfException(sprintf('Could not validate field "%s": %s', $prefix.$name, $e->getMessage())); } } + + return $params; } \ No newline at end of file diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/validateCreate.php b/data/generator/sfDoctrineRestGenerator/default/parts/validateCreate.php index 14c39db..738fca3 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/validateCreate.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/validateCreate.php @@ -1,15 +1,16 @@ /** * Applies the creation validators to the payload posted to the service * - * @param string $payload A payload string + * @param array $params A parsed payload array + * @return array $params A cleaned params array */ - public function validateCreate($payload) + public function validateCreate($params) { - $params = $this->parsePayload($payload); - $validators = $this->getCreateValidators(); - $this->validate($params, $validators); + $params = $this->validate($params, $validators); $postvalidators = $this->getCreatePostValidators(); $this->postValidate($params, $postvalidators); + + return $params; } diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/validateIndex.php b/data/generator/sfDoctrineRestGenerator/default/parts/validateIndex.php index 2dd85ec..a4d152a 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/validateIndex.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/validateIndex.php @@ -3,12 +3,15 @@ * webservice * * @param array $params An array of criterions used for the selection + * @return array A cleaned array of criterions */ public function validateIndex($params) { $validators = $this->getIndexValidators(); - $this->validate($params, $validators); + $params = $this->validate($params, $validators); $postvalidators = $this->getIndexPostValidators(); $this->postValidate($params, $postvalidators); + + return $params; } diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/validateUpdate.php b/data/generator/sfDoctrineRestGenerator/default/parts/validateUpdate.php index 1f96557..6bac9d1 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/validateUpdate.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/validateUpdate.php @@ -1,15 +1,16 @@ /** * Applies the update validators to the payload posted to the service * - * @param string $payload A payload string + * @param array $params A parsed payload array + * @return array $params A cleaned params array */ - public function validateUpdate($payload) + public function validateUpdate($params) { - $params = $this->parsePayload($payload); - $validators = $this->getUpdateValidators(); - $this->validate($params, $validators); + $params = $this->validate($params, $validators); $postvalidators = $this->getUpdatePostValidators(); $this->postValidate($params, $postvalidators); + + return $params; } From 0517485d60d0b9a5339430ae8b97424e5a0c938d Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 26 Oct 2011 20:46:58 +0200 Subject: [PATCH 09/11] Major improvements accross the code. The params array always keeped, the query method used also in update and delete... --- .../default/parts/createAction.php | 5 +++-- .../default/parts/deleteAction.php | 2 +- .../default/parts/getIndexValidators.php | 6 +++--- .../default/parts/indexAction.php | 4 ++-- .../default/parts/parsePayload.php | 2 +- .../default/parts/showAction.php | 2 +- .../default/parts/updateAction.php | 17 +++++++++-------- .../default/parts/updateObjectFromRequest.php | 4 ++-- 8 files changed, 22 insertions(+), 20 deletions(-) diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/createAction.php b/data/generator/sfDoctrineRestGenerator/default/parts/createAction.php index c6d94f5..c8a9fb9 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/createAction.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/createAction.php @@ -22,7 +22,8 @@ public function executeCreate(sfWebRequest $request) try { - $this->validateCreate($content); + $params = $this->parsePayload($content); + $params = $this->validateCreate($params); } catch (Exception $e) { @@ -52,6 +53,6 @@ public function executeCreate(sfWebRequest $request) } $this->object = $this->createObject(); - $this->updateObjectFromRequest($content); + $this->updateObjectFromRequest($params); return $this->doSave(); } diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/deleteAction.php b/data/generator/sfDoctrineRestGenerator/default/parts/deleteAction.php index d3de9ea..d2b66ec 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/deleteAction.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/deleteAction.php @@ -10,7 +10,7 @@ public function executeDelete(sfWebRequest $request) $this->forward404Unless($request->isMethod(sfRequest::DELETE)); $primaryKey = $request->getParameter(''); $this->forward404Unless($primaryKey); - $this->item = Doctrine::getTable($this->model)->findOneBy($primaryKey); + $this->item = $this->query(array('' => $primaryKey))->fetchOne(); $this->forward404Unless($this->item); $this->item->delete(); return sfView::NONE; diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/getIndexValidators.php b/data/generator/sfDoctrineRestGenerator/default/parts/getIndexValidators.php index 8763637..67101c5 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/getIndexValidators.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/getIndexValidators.php @@ -20,13 +20,13 @@ public function getIndexValidators() 'max' => , 'required' => false ); - $validators['page_size'] = new sfValidatorInteger($params); + $validators['pagesize'] = new sfValidatorInteger($params); configuration->getValue('get.sort_custom'); ?> - $validators['sort_by'] = new sfValidatorChoice(array('choices' => table->getColumnNames()) ?>, 'required' => false)); - $validators['sort_order'] = new sfValidatorChoice(array('choices' => array('asc', 'desc'), 'required' => false)); + $validators['orderby'] = new sfValidatorChoice(array('choices' => table->getColumnNames()) ?>, 'required' => false)); + $validators['sortorder'] = new sfValidatorChoice(array('choices' => array('asc', 'desc'), 'required' => false)); configuration->getValue('get.additional_params'); ?> diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/indexAction.php b/data/generator/sfDoctrineRestGenerator/default/parts/indexAction.php index 74a2436..b36301d 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/indexAction.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/indexAction.php @@ -17,7 +17,7 @@ public function executeIndex(sfWebRequest $request) try { $format = $this->getFormat(); - $this->validateIndex($params); + $params = $this->validateIndex($params); } catch (Exception $e) { @@ -82,6 +82,6 @@ public function executeIndex(sfWebRequest $request) $serializer = $this->getSerializer(); $this->getResponse()->setContentType($serializer->getContentType()); - $this->output = $serializer->serialize($this->objects, asPhp($this->configuration->getValue('default.root_name')); ?>); + $this->output = $serializer->serialize($this->objects, asPhp($this->configuration->getValue('default.root_name')); ?>, true, asPhp($this->configuration->getValue('default.plural_root_name')); ?>); unset($this->objects); } diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/parsePayload.php b/data/generator/sfDoctrineRestGenerator/default/parts/parsePayload.php index dcbb0a9..474399c 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/parsePayload.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/parsePayload.php @@ -10,7 +10,7 @@ protected function parsePayload($payload, $force = false) $payload_array = $serializer->unserialize($payload); } - if (!isset($payload_array) || !$payload_array) + if (!isset($payload_array) || $payload_array === false) { throw new sfException(sprintf('Could not parse payload, obviously not a valid %s data!', $format)); } diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/showAction.php b/data/generator/sfDoctrineRestGenerator/default/parts/showAction.php index 9e836d0..0eaca23 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/showAction.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/showAction.php @@ -47,7 +47,7 @@ public function executeShow(sfWebRequest $request) } $this->queryFetchOne($params); - $this->forward404Unless(is_array($this->objects[0])); + $this->forward404Unless(is_array($this->objects[0]) && !empty($this->objects[0])); configuration->getValue('get.object_additional_fields') as $field): ?> $this->embedAdditional(0, $params); diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/updateAction.php b/data/generator/sfDoctrineRestGenerator/default/parts/updateAction.php index 3438a33..5bcb306 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/updateAction.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/updateAction.php @@ -16,9 +16,16 @@ public function executeUpdate(sfWebRequest $request) $request->setRequestFormat('html'); + // retrieve the object +getModelClass())->getIdentifier() ?> + $primaryKey = $request->getParameter(''); + $this->object = $this->query(array('' => $primaryKey))->fetchOne(); + $this->forward404Unless($this->object); + try { - $this->validateUpdate($content); + $params = $this->parsePayload($content); + $params = $this->validateUpdate($params); } catch (Exception $e) { @@ -47,13 +54,7 @@ public function executeUpdate(sfWebRequest $request) return sfView::SUCCESS; } - // retrieve the object -getModelClass())->getIdentifier() ?> - $primaryKey = $request->getParameter(''); - $this->object = Doctrine_Core::getTable($this->model)->findOneBy($primaryKey); - $this->forward404Unless($this->object); - // update and save it - $this->updateObjectFromRequest($content); + $this->updateObjectFromRequest($params); return $this->doSave(); } diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/updateObjectFromRequest.php b/data/generator/sfDoctrineRestGenerator/default/parts/updateObjectFromRequest.php index e60137b..51a4f9d 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/updateObjectFromRequest.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/updateObjectFromRequest.php @@ -1,4 +1,4 @@ - protected function updateObjectFromRequest($content) + protected function updateObjectFromRequest($params) { - $this->object->importFrom('array', $this->parsePayload($content)); + $this->object->importFrom('array', $params); } From 0770bd58009d5058047e1e52387ba853378f9262 Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 26 Oct 2011 20:52:35 +0200 Subject: [PATCH 10/11] Revert a wrong rename of some internal params --- .../default/parts/getIndexValidators.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/generator/sfDoctrineRestGenerator/default/parts/getIndexValidators.php b/data/generator/sfDoctrineRestGenerator/default/parts/getIndexValidators.php index 67101c5..8763637 100644 --- a/data/generator/sfDoctrineRestGenerator/default/parts/getIndexValidators.php +++ b/data/generator/sfDoctrineRestGenerator/default/parts/getIndexValidators.php @@ -20,13 +20,13 @@ public function getIndexValidators() 'max' => , 'required' => false ); - $validators['pagesize'] = new sfValidatorInteger($params); + $validators['page_size'] = new sfValidatorInteger($params); configuration->getValue('get.sort_custom'); ?> - $validators['orderby'] = new sfValidatorChoice(array('choices' => table->getColumnNames()) ?>, 'required' => false)); - $validators['sortorder'] = new sfValidatorChoice(array('choices' => array('asc', 'desc'), 'required' => false)); + $validators['sort_by'] = new sfValidatorChoice(array('choices' => table->getColumnNames()) ?>, 'required' => false)); + $validators['sort_order'] = new sfValidatorChoice(array('choices' => array('asc', 'desc'), 'required' => false)); configuration->getValue('get.additional_params'); ?> From 43b444d5a81f8ca9625c1948ea6eab7f1ffd7f31 Mon Sep 17 00:00:00 2001 From: Damien ALEXANDRE Date: Wed, 26 Oct 2011 21:37:02 +0200 Subject: [PATCH 11/11] Update the documentation with my new params and settings, and add the undocumented actions_base_class param (usefull to me) --- README.markdown | 63 ++++++++++++++++--- .../default/skeleton/config/generator.yml | 6 +- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/README.markdown b/README.markdown index 5b3964d..b6f8dca 100644 --- a/README.markdown +++ b/README.markdown @@ -22,6 +22,8 @@ for data exchange. Here are some key features : ## How to install +### Symfony automatic install + * go to your project's root * Install the plugin: @@ -33,8 +35,13 @@ for data exchange. Here are some key features : ./symfony cc +### Using subversion (the code might be older!) + + * [http://svn.symfony-project.com/plugins/sfDoctrineRestGeneratorPlugin](http://svn.symfony-project.com/plugins/sfDoctrineRestGeneratorPlugin). + +#### Using git - * alternatively, you might prefer to install this plugin as a Subversion dependancy. In this case, here is the repository: [http://svn.symfony-project.com/plugins/sfDoctrineRestGeneratorPlugin](http://svn.symfony-project.com/plugins/sfDoctrineRestGeneratorPlugin). There is also a Git mirror on GitHub : [https://github.com/xavierlacot/sfDoctrineRestGeneratorPlugin](https://github.com/xavierlacot/sfDoctrineRestGeneratorPlugin) + * [https://github.com/xavierlacot/sfDoctrineRestGeneratorPlugin](https://github.com/xavierlacot/sfDoctrineRestGeneratorPlugin) ## Usage @@ -78,7 +85,7 @@ If we want to expose the model "Post" through a REST API, we will simply type the command: - ./symfony doctrine:generate-rest-module api post Post + ./symfony doctrine:generate-rest-module api post Post This will generate: @@ -100,6 +107,8 @@ This will generate: * lib: contains an empty "postGeneratorConfiguration" class, which extends a on-the-fly generated "BasePostGeneratorConfiguration" class, * templates + * a lime functional test skeleton in /test/functional/api + * after a first request has been made to the REST module, the cache directory will contain the code of the generated module, and particularly the code of the "autopostActions" class, which you should check in order to understand the way the plugin works. @@ -121,7 +130,6 @@ http://api.example.com/myPostAPI.json: format: xml - ## Main configuration Before configuring the content of the response of the webservice, the first @@ -161,7 +169,8 @@ Second, consider a way to make your webservice more secure: * use SSL whenever possible, so that the posted data do not get intercepted and altered by a third party (man in the middle), * use HTTP authentication, * use a stronger / more extensible authentication system (OAuth for example), - * deliver unique API keys to your clients, and check the usage that they do of the API. + * deliver unique API keys to your clients, and check the usage that they do of the API, + * improve the generated validators with your own business rules ## Detailed service configuration @@ -175,7 +184,7 @@ Here is the default content of the `generator.yml` file: class: sfDoctrineRestGenerator param: model_class: Post - + # actions_base_class: sfActions config: default: # fields: # list here the fields. @@ -183,6 +192,9 @@ Here is the default content of the `generator.yml` file: # formats_enabled: [ json, xml, yaml ] # enabled formats # formats_strict: true # separator: ',' # separator used for multiple filters + # camelize: true # tell the serializers to translate fieldnames to camelCase + # root_name: ##MODEL_CLASS## # the root node name used for serilize one ressource + # plural_root_name: false # you can overide the plural root node name (if false, we add a "s" to root_name) get: # additional_params: [] # list here additional params names, which are not object properties # default_format: json # the default format of the response. If not set, will default to json. Accepted values are "json", "xml" or "yaml" @@ -211,6 +223,11 @@ detailed in the following chapters. The `model_class` parameters defines the name of the Doctrine model the REST module is bound to. +### actions_base_class + +All the generated controllers will extend this class. + + ### default The `default` option contains several general configuration directives: @@ -256,12 +273,27 @@ The separator to use in url when passing objects primary keys. The generated module allows to require several resources identified by their ids: http://api.example.com/post/?id=12,17,19 + # camelize: true # tell the serializers to translate fieldnames to camelCase + # root_name: ##MODEL_CLASS## # the root node name used for serilize one ressource + # plural_root_name: false # you can overide the plural root node name (if false, we add a "s" to root_name) + +### camelize + +On serialization, your fieldnames will be camelCased by default (only XML atm). + +### root_name + +You can customize the root_name of the Json / Xml outputs (by default, it's the model name). + +### plural_root_name + +By default, collections of ressources are put in a "s" suffixed model name. But for a Company model, you don't want to have a `Companys` XML root name. Simply write your own here. + ### get The `get` option lists several options specific to the "get" operation: - #### additional_params The `default_format` option allows to define an array of parameter names, @@ -591,11 +623,12 @@ picking one of the following topics: * possibility to disable events notification / filtering (performance) * more serializers ([BSON](http://bsonspec.org/) or RDF for example). Currently, the plugin only allows to serialize the resultsets as a XML, YAML or JSON feeds (see the chapter "Serialization"). Mobile clients, which require the most compact possible streams, would take benefit from a BSON serialization. * possibility to generate client libraries (sfDoctrineRestClientGenerator ?) - * possibility to generate unit tests * possibility to generate API documentation * document authentication solutions * all the possible feedback! - + * recode the post validators logic (to use the full params array on each post validator, same as sfForm) + * camelize on all the serializer? (only XML do that atm) + * don't use the templating system (performance) ## Contribute to the plugin, ask for help @@ -611,10 +644,20 @@ licensed under the MIT license. ## Changelog -### trunk +### master on damienalexandre/sfDoctrineRestGeneratorPlugin - * Enabled the `show` route by default + * Improve the XML and Json serializer (deal with collections, plural naming, Json and XML are now a lot more consistant) + * Add a `camelize` option + * Add a `root_name` option + * Add a `plural_root_name` option + * Use the query() method in update and delete action (you can now have consistent query on all methods) + * The validated payload array is now kept and send to updateObjectFromRequest(). This is allowing a lot of flexibility (like saving sfValidatedFile instance i.e.) + * Add a dynamically generated functional test on each new module + * Add a `location` header on create and update response with the uri to the new ressource +### master + + * Enabled the `show` route by default ### version 0.9.4 - 2010-11-25 diff --git a/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml b/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml index bb8be73..ec96e2f 100644 --- a/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml +++ b/data/generator/sfDoctrineRestGenerator/default/skeleton/config/generator.yml @@ -2,7 +2,7 @@ generator: class: sfDoctrineRestGenerator param: model_class: ##MODEL_CLASS## - +# actions_base_class: sfActions config: default: # fields: # list here the fields. @@ -10,8 +10,8 @@ generator: # formats_enabled: [ json, xml, yaml ] # enabled formats # formats_strict: true # separator: ',' # separator used for multiple filters -# camelize: true -# root_name: ##MODEL_CLASS## +# camelize: true # tell the serializers to translate fieldnames to camelCase +# root_name: ##MODEL_CLASS## # the root node name used for serilize one ressource # plural_root_name: false # you can overide the plural root node name (if false, we add a "s" to root_name) get: # additional_params: [] # list here additionnal params names, which are not object properties