Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 53 additions & 10 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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.


Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -175,14 +184,17 @@ 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.
# created_at: { date_format: 'Y-m-d\TH:i:s', tag_name: 'created' } # for instance
# 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"
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
/**
* <?php echo $this->getModuleName() ?> module configuration.
*
* @package ##PROJECT_NAME##
* @subpackage <?php echo $this->getModuleName()."\n" ?>
* @author ##AUTHOR_NAME##
* @version SVN: $Id: configuration.php 24171 2009-11-19 16:37:50Z Kris.Wallsmith $
*/
abstract class Base<?php echo ucfirst($this->getModuleName()) ?>GeneratorConfiguration extends sfDoctrineRestGeneratorConfiguration
{
Expand Down Expand Up @@ -91,6 +88,24 @@ public function getSeparator()
<?php unset($this->config['default']['separator']) ?>
}

public function getCamelize()
{
return <?php echo $this->asPhp(isset($this->config['default']['camelize']) ? $this->config['default']['camelize'] : true) ?>;
<?php unset($this->config['default']['camelize']) ?>
}

public function getRootName()
{
return <?php echo $this->asPhp(isset($this->config['default']['root_name']) ? $this->config['default']['root_name'] : $this->getModelClass()) ?>;
<?php unset($this->config['default']['root_name']) ?>
}

public function getPluralRootName()
{
return <?php echo $this->asPhp(isset($this->config['default']['plural_root_name']) ? $this->config['default']['plural_root_name'] : false) ?>;
<?php unset($this->config['default']['plural_root_name']) ?>
}

<?php include dirname(__FILE__).'/paginationConfiguration.php' ?>

<?php include dirname(__FILE__).'/sortConfiguration.php' ?>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public function executeCreate(sfWebRequest $request)

try
{
$this->validateCreate($content);
$params = $this->parsePayload($content);
$params = $this->validateCreate($params);
}
catch (Exception $e)
{
Expand Down Expand Up @@ -52,6 +53,6 @@ public function executeCreate(sfWebRequest $request)
}

$this->object = $this->createObject();
$this->updateObjectFromRequest($content);
$this->updateObjectFromRequest($params);
return $this->doSave();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public function executeDelete(sfWebRequest $request)
$this->forward404Unless($request->isMethod(sfRequest::DELETE));
$primaryKey = $request->getParameter('<?php echo $primaryKey ?>');
$this->forward404Unless($primaryKey);
$this->item = Doctrine::getTable($this->model)->findOneBy<?php echo sfInflector::camelize($primaryKey) ?>($primaryKey);
$this->item = $this->query(array('<?php echo $primaryKey ?>' => $primaryKey))->fetchOne();
$this->forward404Unless($this->item);
$this->item->delete();
return sfView::NONE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ protected function doSave()
{
$this->object->save();

<?php $primaryKey = $this->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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ protected function getSerializer()
{
try
{
$this->serializer = sfResourceSerializer::getInstance($this->getFormat());
$this->serializer = sfResourceSerializer::getInstance($this->getFormat(), <?php echo $this->asPhp($this->configuration->getValue('default.camelize')) ?>);
}
catch (sfException $e)
{
$this->serializer = sfResourceSerializer::getInstance('<?php echo $this->configuration->getValue('get.default_format') ?>');
$this->serializer = sfResourceSerializer::getInstance('<?php echo $this->configuration->getValue('get.default_format') ?>', <?php echo $this->asPhp($this->configuration->getValue('default.camelize')) ?>);
throw new sfException($e->getMessage());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function executeIndex(sfWebRequest $request)
try
{
$format = $this->getFormat();
$this->validateIndex($params);
$params = $this->validateIndex($params);
}
catch (Exception $e)
{
Expand Down Expand Up @@ -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, <?php echo $this->asPhp($this->configuration->getValue('default.root_name')); ?>, true, <?php echo $this->asPhp($this->configuration->getValue('default.plural_root_name')); ?>);
unset($this->objects);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]));

<?php foreach ($this->configuration->getValue('get.object_additional_fields') as $field): ?>
$this->embedAdditional<?php echo $field ?>(0, $params);
Expand All @@ -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], <?php echo $this->asPhp($this->configuration->getValue('default.root_name')); ?>, false);
unset($this->objects);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ public function executeUpdate(sfWebRequest $request)

$request->setRequestFormat('html');

// retrieve the object
<?php $primaryKey = Doctrine_Core::getTable($this->getModelClass())->getIdentifier() ?>
$primaryKey = $request->getParameter('<?php echo $primaryKey ?>');
$this->object = $this->query(array('<?php echo $primaryKey ?>' => $primaryKey))->fetchOne();
$this->forward404Unless($this->object);

try
{
$this->validateUpdate($content);
$params = $this->parsePayload($content);
$params = $this->validateUpdate($params);
}
catch (Exception $e)
{
Expand Down Expand Up @@ -47,13 +54,7 @@ public function executeUpdate(sfWebRequest $request)
return sfView::SUCCESS;
}

// retrieve the object
<?php $primaryKey = Doctrine_Core::getTable($this->getModelClass())->getIdentifier() ?>
$primaryKey = $request->getParameter('<?php echo $primaryKey ?>');
$this->object = Doctrine_Core::getTable($this->model)->findOneBy<?php echo sfInflector::camelize($primaryKey) ?>($primaryKey);
$this->forward404Unless($this->object);

// update and save it
$this->updateObjectFromRequest($content);
$this->updateObjectFromRequest($params);
return $this->doSave();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
protected function updateObjectFromRequest($content)
protected function updateObjectFromRequest($params)
{
$this->object->importFrom('array', $this->parsePayload($content));
$this->object->importFrom('array', $params);
}
34 changes: 31 additions & 3 deletions data/generator/sfDoctrineRestGenerator/default/parts/validate.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
/**
* 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
* @throw sfException
*/
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)
Expand All @@ -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)]);
Expand All @@ -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;
}
Loading