Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

first commit

  • Loading branch information...
commit c576b5f60042ae86a8737d40859b6e36894316ef 0 parents
@jwage jwage authored
Showing with 2,538 additions and 0 deletions.
  1. +110 −0 README.markdown
  2. +110 −0 lib/Doctrine/REST/Client/Client.php
  3. +133 −0 lib/Doctrine/REST/Client/Entity.php
  4. +202 −0 lib/Doctrine/REST/Client/EntityConfiguration.php
  5. +161 −0 lib/Doctrine/REST/Client/Manager.php
  6. +112 −0 lib/Doctrine/REST/Client/Request.php
  7. +45 −0 lib/Doctrine/REST/Client/ResponseTransformer/AbstractResponseTransformer.php
  8. +82 −0 lib/Doctrine/REST/Client/ResponseTransformer/StandardResponseTransformer.php
  9. +45 −0 lib/Doctrine/REST/Client/URLGenerator/AbstractURLGenerator.php
  10. +68 −0 lib/Doctrine/REST/Client/URLGenerator/StandardURLGenerator.php
  11. +102 −0 lib/Doctrine/REST/Server/Action/AbstractAction.php
  12. +48 −0 lib/Doctrine/REST/Server/Action/AbstractSaveAction.php
  13. +49 −0 lib/Doctrine/REST/Server/Action/DeleteAction.php
  14. +50 −0 lib/Doctrine/REST/Server/Action/GetAction.php
  15. +45 −0 lib/Doctrine/REST/Server/Action/InsertAction.php
  16. +52 −0 lib/Doctrine/REST/Server/Action/ListAction.php
  17. +44 −0 lib/Doctrine/REST/Server/Action/UpdateAction.php
  18. +67 −0 lib/Doctrine/REST/Server/Request.php
  19. +239 −0 lib/Doctrine/REST/Server/RequestHandler.php
  20. +174 −0 lib/Doctrine/REST/Server/Response.php
  21. +88 −0 lib/Doctrine/REST/Server/Server.php
  22. +31 −0 tests/Doctrine/Tests/REST/AllTests.php
  23. +233 −0 tests/Doctrine/Tests/REST/ClientTest.php
  24. +215 −0 tests/Doctrine/Tests/REST/FunctionalTest.php
  25. +33 −0 tests/Doctrine/Tests/REST/TestInit.php
110 README.markdown
@@ -0,0 +1,110 @@
+# Doctrine 2 REST Server and Client
+
+The Doctrine 2 REST server and client component is both an easy way to spin up
+REST services for your Doctrine 2 entities as well as a way to work with REST
+services via an ActiveRecord style implementation similiar to ActiveResource in
+Ruby on Rails!
+
+## Introduction
+
+The basic concept is simple, you have a REST service (http://api.people.com/person)
+and you want to interact with it through a simple ActiveRecord style interface.
+
+First we can retrieve a person:
+
+ $person = Person::find(1); // GET http://api.people.com/person/1.xml
+
+Now we can change some properties of that person:
+
+ $person->setName('Jonathan H. Wage');
+
+Once we're done we can simply save it and the appropriate REST call will be made:
+
+ $person->save(); // POST http://api.people.com/person/1.xml (name=Jonathan H. Wage)
+
+## Client
+
+The REST client is an ActiveRecord style implementation for working with REST
+services. All you need to do is define some PHP classes that are mapped to some
+REST service on the web. Here is an example where we map a Person to
+http://api.people.com/person:
+
+ <?php
+
+ namespace Entities;
+
+ use Doctrine\REST\Client\Entity;
+
+ class Person extends Entity
+ {
+ private $id;
+ private $name;
+
+ public static function configure(EntityConfiguration $entityConfiguration)
+ {
+ $entityConfiguration->setUrl('http://api.people.com');
+ $entityConfiguration->setName('person');
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+ }
+
+Now when we perform some actions it will generate the appropriate REST request,
+execute it, transform the response and hydrate the results to your PHP objects.
+
+ $person = new Person();
+ $person->setName('Jonathan H. Wage');
+ $person->save(); // PUT http://api.people.com/person.xml (name=Jonathan H. Wage)
+
+We can retrieve that person again now:
+
+ $person = Person::find($person->getId()); // GET http://api.people.com/person/1.xml
+
+Or you can retrieve all Person objects:
+
+ $persons = Person::findAll();
+
+## Server
+
+The Doctrine 2 REST server allows you to easily expose your entities through some
+REST services. This is the raw low level server and does not include any routing
+or URL parsing so you would need to implement in some existing framework that
+has routing like Symfony or Zend Framework.
+
+All you need to do is create a new REST server instance and pass it the instance
+of your EntityManager you want to expose the entities for and an array representing
+the server request you want to process:
+
+ $request = array(
+ '_method' => 'get',
+ '_format' => 'xml',
+ '_entity' => 'user',
+ '_action' => 'get',
+ '_id' => 1
+ );
+
+ $server = new \Doctrine\REST\Server\Server($em, $request);
+ $server->addEntityAlias('Entities\User', 'user');
+
+ $xml = $server->execute();
+
+The above would retrieve the User with the id of 1 and return an XML document
+like the following:
+
+ <user>
+ <id>1</id>
+ <username>jwage</username>
+ </user>
110 lib/Doctrine/REST/Client/Client.php
@@ -0,0 +1,110 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Client;
+
+/**
+ * Basic class for issuing HTTP requests via PHP curl.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class Client
+{
+ const POST = 'POST';
+ const GET = 'GET';
+ const PUT = 'PUT';
+ const DELETE = 'DELETE';
+
+ public function post(Request $request)
+ {
+ $request->setMethod(Client::POST);
+ return $this->execute($request);
+ }
+
+ public function get(Request $request)
+ {
+ $request->setMethod(Client::GET);
+ return $this->execute($request);
+ }
+
+ public function put(Request $request)
+ {
+ $request->setMethod(Client::PUT);
+ return $this->execute($request);
+ }
+
+ public function delete(Request $request)
+ {
+ $request->setMethod(Client::DELETE);
+ return $this->execute($request);
+ }
+
+ public function execute(Request $request)
+ {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $request->getUrl());
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
+
+ $username = $request->getUsername();
+ $password = $request->getPassword();
+
+ if ($username && $password) {
+ curl_setopt ($ch, CURLOPT_USERPWD, $username . ':' . $password);
+ }
+
+ switch ($request->getMethod()) {
+ case self::POST:
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($request->getParameters()));
+ break;
+ case self::DELETE:
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
+ break;
+ case self::PUT:
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
+ curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($request->getParameters()));
+ break;
+ case self::GET:
+ default:
+ break;
+ }
+
+ $result = curl_exec($ch);
+
+ if ( ! $result) {
+ $errorNumber = curl_errno($ch);
+ $error = curl_error($ch);
+ curl_close($ch);
+
+ throw new \Exception($errorNumer . ': ' . $error);
+ }
+
+ curl_close($ch);
+
+ return $request->getResponseTransformerImpl()->transform($result);
+ }
+}
133 lib/Doctrine/REST/Client/Entity.php
@@ -0,0 +1,133 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Client;
+
+/**
+ * Abstract entity class for REST entities to extend from to give ActiveRecord
+ * style interface for working with REST services.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+abstract class Entity
+{
+ protected static $_manager;
+
+ public static function setManager(Manager $manager)
+ {
+ self::$_manager = $manager;
+ }
+
+ public function toArray()
+ {
+ return get_object_vars($this);
+ }
+
+ public function exists()
+ {
+ return self::$_manager->entityExists($this);
+ }
+
+ public function getIdentifier()
+ {
+ return self::$_manager->getEntityIdentifier($this);
+ }
+
+ public static function generateUrl(array $options = array())
+ {
+ $configuration = self::$_manager->getEntityConfiguration(get_called_class());
+ return $configuration->generateUrl($options);
+ }
+
+ public static function find($id, $action = null)
+ {
+ return self::$_manager->execute(
+ get_called_class(),
+ self::generateUrl(get_defined_vars()),
+ Client::GET
+ );
+ }
+
+ public static function findAll($action = null, $parameters = null)
+ {
+ return self::$_manager->execute(
+ get_called_class(),
+ self::generateUrl(get_defined_vars()),
+ Client::GET, $parameters
+ );
+ }
+
+ public function save($action = null)
+ {
+ $parameters = $this->toArray();
+ $exists = $this->exists();
+ $method = $exists ? Client::POST : Client::PUT;
+ $id = $exists ? $this->getIdentifier() : null;
+ $path = $this->generateUrl(get_defined_vars());
+ return self::$_manager->execute($this, $path, $method, $parameters, $action);
+ }
+
+ public function delete($action = null)
+ {
+ $id = $this->getIdentifier();
+ return self::$_manager->execute(
+ $this, $this->generateUrl(get_defined_vars()), Client::DELETE
+ );
+ }
+
+ public function post($action = null)
+ {
+ $id = $this->getIdentifier();
+ return self::$_manager->execute(
+ $this, $this->generateUrl(get_defined_vars()),
+ Client::POST, $this->toArray()
+ );
+ }
+
+ public function get($action = null)
+ {
+ return self::$_manager->execute(
+ $this, $this->generateUrl(get_defined_vars()),
+ Client::GET, $this->toArray()
+ );
+ }
+
+ public function put($action = null)
+ {
+ return self::$_manager->execute(
+ $this, $this->generateUrl(get_defined_vars()),
+ Client::PUT, $this->toArray()
+ );
+ }
+
+ public static function execute($method, $action, $parameters = null)
+ {
+ return self::$_manager->execute(
+ get_called_class(),
+ self::generateUrl(get_defined_vars()),
+ $method, $parameters
+ );
+ }
+}
202 lib/Doctrine/REST/Client/EntityConfiguration.php
@@ -0,0 +1,202 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Client;
+
+use Doctrine\REST\Client\URLGenerator\StandardURLGenerator,
+ Doctrine\REST\Client\ResponseTransformer\StandardResponseTransformer;
+
+/**
+ * Entity configuration class holds all the configuration information for a PHP5
+ * object entity that maps to a REST service.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class EntityConfiguration
+{
+ private $_prototype;
+ private $_reflection;
+ private $_reflectionProperties = array();
+
+ private $_attributes = array(
+ 'class' => null,
+ 'url' => null,
+ 'name' => null,
+ 'username' => null,
+ 'password' => null,
+ 'identifierKey' => 'id',
+ 'responseType' => 'xml',
+ 'urlGeneratorImpl' => null,
+ 'responseTransformerImpl' => null,
+ );
+
+ public function __construct($class)
+ {
+ $this->_attributes['class'] = $class;
+ $this->_attributes['urlGeneratorImpl'] = new StandardURLGenerator($this);
+ $this->_attributes['responseTransformerImpl'] = new StandardResponseTransformer($this);
+
+ $this->_reflection = new \ReflectionClass($class);
+ foreach ($this->_reflection->getProperties() as $property) {
+ if ($property->getDeclaringClass()->getName() == $class) {
+ $property->setAccessible(true);
+ $this->_reflectionProperties[$property->getName()] = $property;
+ $this->_properties[] = $property->getName();
+ }
+ }
+ }
+
+ public function getReflection()
+ {
+ return $this->_reflection;
+ }
+
+ public function getReflectionProperties()
+ {
+ return $this->_reflectionProperties;
+ }
+
+ public function getProperties()
+ {
+ return $this->_properties;
+ }
+
+ public function setValue($entity, $field, $value)
+ {
+ $this->_reflectionProperties[$field]->setValue($entity, $value);
+ }
+
+ public function getValue($entity, $field)
+ {
+ return $this->_reflectionProperties[$field]->getValue($entity);
+ }
+
+ public function generateUrl(array $options)
+ {
+ return $this->_attributes['urlGeneratorImpl']->generate($options);
+ }
+
+ public function setUrl($url)
+ {
+ $this->_attributes['url'] = rtrim($url, '/');
+ }
+
+ public function getUrl()
+ {
+ return $this->_attributes['url'];
+ }
+
+ public function setClass($class)
+ {
+ $this->_attributes['class'] = $class;
+ }
+
+ public function getClass()
+ {
+ return $this->_attributes['class'];
+ }
+
+ public function setName($name)
+ {
+ $this->_attributes['name'] = $name;
+ }
+
+ public function getName()
+ {
+ return $this->_attributes['name'];
+ }
+
+ public function setUsername($username)
+ {
+ $this->_attributes['username'] = $username;
+ }
+
+ public function getUsername()
+ {
+ return $this->_attributes['username'];
+ }
+
+ public function setPassword($password)
+ {
+ $this->_attributes['password'] = $password;
+ }
+
+ public function getPassword()
+ {
+ return $this->_attributes['password'];
+ }
+
+ public function setIdentifierKey($identifierKey)
+ {
+ $this->_attributes['identifierKey'] = $identifierKey;
+ }
+
+ public function getIdentifierKey()
+ {
+ return $this->_attributes['identifierKey'];
+ }
+
+ public function setResponseType($responseType)
+ {
+ $this->_attributes['responseType'] = $responseType;
+ }
+
+ public function getResponseType()
+ {
+ return $this->_attributes['responseType'];
+ }
+
+ public function setURLGeneratorImpl($urlGeneratorImpl)
+ {
+ $this->_attributes['urlGeneratorImpl'] = $urlGeneratorImpl;
+ }
+
+ public function getURLGeneratorImpl()
+ {
+ return $this->_attributes['urlGeneratorImpl'];
+ }
+
+ public function setResponseTransformerImpl($responseHandlerImpl)
+ {
+ $this->_attributes['responseTransformerImpl'] = $responseHandlerImpl;
+ }
+
+ public function getResponseTransformerImpl()
+ {
+ return $this->_attributes['responseTransformerImpl'];
+ }
+
+ public function newInstance()
+ {
+ if ($this->_prototype === null) {
+ $this->_prototype = unserialize(sprintf(
+ 'O:%d:"%s":0:{}',
+ strlen($this->_attributes['class']),
+ $this->_attributes['class']
+ ));
+ }
+ return clone $this->_prototype;
+ }
+}
161 lib/Doctrine/REST/Client/Manager.php
@@ -0,0 +1,161 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Client;
+
+/**
+ * Class responsible for managing the entities registered for REST services.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class Manager
+{
+ private $_client;
+ private $_entityConfigurations = array();
+ private $_identityMap = array();
+
+ public function __construct(Client $client)
+ {
+ $this->_client = $client;
+ }
+
+ public function registerEntity($entity)
+ {
+ $this->_entityConfigurations[$entity] = $entity;
+ }
+
+ public function getEntityConfiguration($entity)
+ {
+ if ( ! isset($this->_entityConfigurations[$entity])) {
+ throw new \InvalidArgumentException(
+ sprintf('Could not find entity configuration for "%s"', $entity)
+ );
+ }
+ if (is_string($this->_entityConfigurations[$entity])) {
+ $entityConfiguration = new EntityConfiguration($entity);
+ call_user_func_array(
+ array($entity, 'configure'),
+ array($entityConfiguration)
+ );
+ $this->_entityConfigurations[$entity] = $entityConfiguration;
+ }
+ return $this->_entityConfigurations[$entity];
+ }
+
+ public function entityExists($entity)
+ {
+ return $this->getEntityIdentifier($entity) ? true : false;
+ }
+
+ public function getEntityIdentifier($entity)
+ {
+ $configuration = $this->getEntityConfiguration(get_class($entity));
+ $identifierKey = $configuration->getIdentifierKey();
+ return $configuration->getValue($entity, $identifierKey);
+ }
+
+ public function execute($entity, $url = null, $method = Client::GET, $parameters = null)
+ {
+ if (is_object($entity)) {
+ $className = get_class($entity);
+ } else {
+ $className = $entity;
+ }
+ $configuration = $this->getEntityConfiguration($className);
+
+ $request = new Request();
+ $request->setUrl($url);
+ $request->setMethod($method);
+ $request->setParameters($parameters);
+ $request->setUsername($configuration->getUsername());
+ $request->setPassword($configuration->getPassword());
+ $request->setResponseType($configuration->getResponseType());
+ $request->setResponseTransformerImpl($configuration->getResponseTransformerImpl());
+
+ $result = $this->_client->execute($request);
+
+ if (is_array($result))
+ {
+ $name = $configuration->getName();
+
+ $identifierKey = $configuration->getIdentifierKey();
+ $className = $configuration->getClass();
+ if (isset($result[$name]) && is_array($result[$name]))
+ {
+ $collection = array();
+ foreach ($result[$name] as $data) {
+ $identifier = $data[$identifierKey];
+ if (isset($this->_identityMap[$className][$identifier]))
+ {
+ $instance = $this->_identityMap[$className][$identifier];
+ } else {
+ $instance = $configuration->newInstance();
+ $this->_identityMap[$className][$identifier] = $instance;
+ }
+ $collection[] = $this->_hydrate(
+ $configuration, $instance, $data
+ );
+ }
+ return $collection;
+ } else if ($result) {
+
+ if (is_object($entity))
+ {
+ $instance = $entity;
+ $this->_hydrate($configuration, $instance, $result);
+ $identifier = $this->getEntityIdentifier($instance);
+ $this->_identityMap[$className][$identifier] = $instance;
+ } else {
+ $identifier = $result[$identifierKey];
+ if (isset($this->_identityMap[$className][$identifier]))
+ {
+ $instance = $this->_identityMap[$className][$identifier];
+ } else {
+ $instance = $configuration->newInstance();
+ $this->_identityMap[$className][$identifier] = $instance;
+ }
+ $this->_hydrate($configuration, $instance, $result);
+ }
+ return $instance;
+ }
+ } else {
+ return array();
+ }
+ }
+
+ private function _hydrate($configuration, $instance, $data)
+ {
+ foreach ($data as $key => $value) {
+ if (is_array($value))
+ {
+ $configuration->setValue($instance, $key, (string) $value);
+ } else {
+ $configuration->setValue($instance, $key, $value);
+ }
+ }
+
+ return $instance;
+ }
+}
112 lib/Doctrine/REST/Client/Request.php
@@ -0,0 +1,112 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Client;
+
+/**
+ * Class that represents a request to a REST service through the raw HTTP client.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class Request
+{
+ private $_url;
+ private $_method = Client::GET;
+ private $_parameters = array();
+ private $_username;
+ private $_password;
+ private $_responseType = 'xml';
+ private $_responseTransformerImpl;
+
+ public function setUrl($url)
+ {
+ $this->_url = $url;
+ }
+
+ public function getUrl()
+ {
+ return $this->_url;
+ }
+
+ public function setMethod($method)
+ {
+ $this->_method = $method;
+ }
+
+ public function getMethod()
+ {
+ return $this->_method;
+ }
+
+ public function setParameters($parameters)
+ {
+ $this->_parameters = $parameters;
+ }
+
+ public function getParameters()
+ {
+ return $this->_parameters;
+ }
+
+ public function setResponseType($responseType)
+ {
+ $this->_responseType = $responseType;
+ }
+
+ public function getResponseType()
+ {
+ return $this->_responseType;
+ }
+
+ public function setUsername($username)
+ {
+ $this->_username = $username;
+ }
+
+ public function getUsername()
+ {
+ return $this->_username;
+ }
+
+ public function setPassword($password)
+ {
+ $this->_password = $password;
+ }
+
+ public function getPassword()
+ {
+ return $this->_password;
+ }
+
+ public function setResponseTransformerImpl($responseTransformerImpl)
+ {
+ $this->_responseTransformerImpl = $responseTransformerImpl;
+ }
+
+ public function getResponseTransformerImpl()
+ {
+ return $this->_responseTransformerImpl;
+ }
+}
45 lib/Doctrine/REST/Client/ResponseTransformer/AbstractResponseTransformer.php
@@ -0,0 +1,45 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Client\ResponseTransformer;
+
+use Doctrine\REST\Client\EntityConfiguration;
+
+/**
+ * Abstract REST request response transformer
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+abstract class AbstractResponseTransformer
+{
+ protected $_entityConfiguration;
+
+ public function __construct(EntityConfiguration $entityConfiguration)
+ {
+ $this->_entityConfiguration = $entityConfiguration;
+ }
+
+ abstract public function transform($data);
+}
82 lib/Doctrine/REST/Client/ResponseTransformer/StandardResponseTransformer.php
@@ -0,0 +1,82 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Client\ResponseTransformer;
+
+/**
+ * Standard REST request response handler. Converts a standard REST service response
+ * to an array for easy manipulation. Works for both xml and json response types.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class StandardResponseTransformer extends AbstractResponseTransformer
+{
+ public function transform($data)
+ {
+ switch ($this->_entityConfiguration->getResponseType()) {
+ case 'xml':
+ return $this->xmlToArray($data);
+ case 'json':
+ return $this->jsonToArray($data);
+ break;
+ }
+ }
+
+ public function xmlToArray($object, &$array = array())
+ {
+ if (is_string($object)) {
+ $object = new \SimpleXMLElement($object);
+ }
+ $children = $object->children();
+ $executed = false;
+ foreach ($children as $elementName => $node) {
+ if (isset($array[$elementName]) && $array[$elementName] !== null) {
+ if (isset($array[$elementName][0]) && $array[$elementName][0] !== null) {
+ $i = count($array[$elementName]);
+ $this->xmlToArray($node, $array[$elementName][$i]);
+ } else {
+ $tmp = $array[$elementName];
+ $array[$elementName] = array();
+ $array[$elementName][0] = $tmp;
+ $i = count($array[$elementName]);
+ $this->xmlToArray($node, $array[$elementName][$i]);
+ }
+ } else {
+ $array[$elementName] = array();
+ $this->xmlToArray($node, $array[$elementName]);
+ }
+ $executed = true;
+ }
+ if ( ! $executed && ! $children->getName()) {
+ $array = (string) $object;
+ }
+ return $array;
+ }
+
+ public function jsonToArray($json)
+ {
+ return (array) json_decode($data);
+ }
+}
45 lib/Doctrine/REST/Client/URLGenerator/AbstractURLGenerator.php
@@ -0,0 +1,45 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Client\URLGenerator;
+
+use Doctrine\REST\Client\EntityConfiguration;
+
+/**
+ * Abstract URL generator for REST services
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+abstract class AbstractURLGenerator
+{
+ protected $_entityConfiguration;
+
+ public function __construct(EntityConfiguration $entityConfiguration)
+ {
+ $this->_entityConfiguration = $entityConfiguration;
+ }
+
+ abstract public function generate(array $options);
+}
68 lib/Doctrine/REST/Client/URLGenerator/StandardURLGenerator.php
@@ -0,0 +1,68 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Client\URLGenerator;
+
+/**
+ * Standard REST request URL generator
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class StandardURLGenerator extends AbstractURLGenerator
+{
+ public function generate(array $options)
+ {
+ $id = isset($options['id']) ? $options['id'] : null;
+ $action = isset($options['action']) ? $options['action'] : null;
+ $parameters = isset($options['parameters']) ? $options['parameters'] : array();
+
+ if ($id)
+ {
+ if ($action !== null)
+ {
+ $path = sprintf('/%s/%s.' . $this->_entityConfiguration->getResponseType(), $id, $action);
+ } else {
+ $path = sprintf('/%s.' . $this->_entityConfiguration->getResponseType(), $id);
+ }
+ } else {
+ if ($action !== null)
+ {
+ $path = sprintf('/%s.' . $this->_entityConfiguration->getResponseType(), $action);
+ } else {
+ $path = '.' . $this->_entityConfiguration->getResponseType();
+ }
+ }
+ $url = $this->_entityConfiguration->getUrl() . '/' . $this->_entityConfiguration->getName() . $path;
+ if (is_array($parameters) && $parameters) {
+ foreach ($this->_entityConfiguration->getProperties() as $field) {
+ unset($parameters[$field]);
+ }
+ if ($parameters) {
+ $url .= '?' . http_build_query($parameters);
+ }
+ }
+ return $url;
+ }
+}
102 lib/Doctrine/REST/Server/Action/AbstractAction.php
@@ -0,0 +1,102 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server\Action;
+
+use \Doctrine\REST\Server\RequestHandler;
+
+/**
+ * Abstract server action class for REST server actions to extend from.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+abstract class AbstractAction
+{
+ protected $_requestHandler;
+ protected $_em;
+ protected $_request;
+
+ public function __construct(RequestHandler $requestHandler)
+ {
+ $this->_requestHandler = $requestHandler;
+ $this->_em = $requestHandler->getEntityManager();
+ $this->_request = $requestHandler->getRequest();
+ }
+
+ abstract public function execute();
+
+ protected function _resolveEntityAlias($alias)
+ {
+ return $this->_requestHandler->resolveEntityAlias($alias);
+ }
+
+ protected function _setQueryFirstAndMax($q)
+ {
+ if ( ! isset($this->_request['_page']) && ! isset($this->_request['_first']) && ! isset($this->_request['_max'])) {
+ $this->_request['_page'] = '1';
+ }
+ $maxPerPage = isset($this->_request['_max_per_page']) ? $this->_request['_max_per_page'] : 20;
+ if (isset($this->_request['_page'])) {
+ $page = $this->_request['_page'];
+ $first = ($page - 1) * $maxPerPage;
+ $q->setFirstResult($first);
+ $q->setMaxResults($maxPerPage);
+ } else {
+ if (isset($this->_request['_first'])) {
+ $q->setFirstResult($this->_request['_first']);
+ } else {
+ $q->setFirstResult(0);
+ }
+ if (isset($this->_request['_max'])) {
+ $q->setMaxResults($this->_request['_max']);
+ } else {
+ $q->setMaxResults($maxPerPage);
+ }
+ }
+ }
+
+ protected function _getFindByIdQuery($entity, $id)
+ {
+ $qb = $this->_em->createQueryBuilder()
+ ->select('a')
+ ->from($entity, 'a')
+ ->where('a.id = ?1')
+ ->setParameter('1', $id);
+ $q = $qb->getQuery();
+ return $q;
+ }
+
+ protected function _gatherData()
+ {
+ $data = array();
+ foreach ($this->_request->getData() as $key => $value) {
+ if ($key[0] == '_') {
+ continue;
+ }
+ $data[$key] = $value;
+ }
+ return $data;
+ }
+}
48 lib/Doctrine/REST/Server/Action/AbstractSaveAction.php
@@ -0,0 +1,48 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server\Action;
+
+use \Doctrine\REST\Server\RequestHandler;
+
+/**
+ * Abstract class for insert and update server actions to extend from.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+abstract class AbstractSaveAction extends AbstractAction
+{
+ protected function _updateEntityInstance($entity)
+ {
+ $data = $this->_gatherData($this->_request->getData());
+ foreach ($data as $key => $value) {
+ $setter = 'set' . ucfirst($key);
+ if (is_callable(array($entity, $setter))) {
+ $entity->$setter($value);
+ }
+ }
+ return $entity;
+ }
+}
49 lib/Doctrine/REST/Server/Action/DeleteAction.php
@@ -0,0 +1,49 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server\Action;
+
+/**
+ * REST server delete action.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class DeleteAction extends AbstractAction
+{
+ public function execute()
+ {
+ $query = $this->_getFindByIdQuery(
+ $this->_resolveEntityAlias($this->_request['_entity']),
+ $this->_request['_id']
+ );
+ $entity = $query->getSingleResult();
+
+ if ($entity) {
+ $this->_em->remove($entity);
+ $this->_em->flush();
+ }
+ return $entity;
+ }
+}
50 lib/Doctrine/REST/Server/Action/GetAction.php
@@ -0,0 +1,50 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server\Action;
+
+/**
+ * REST server get action.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class GetAction extends AbstractAction
+{
+ public function execute()
+ {
+ $query = $this->_getFindByIdQuery(
+ $this->_resolveEntityAlias($this->_request['_entity']),
+ $this->_request['_id']
+ );
+ $this->_setQueryFirstAndMax($query);
+
+ $result = $query->getSingleResult();
+
+ if ( ! $result) {
+ throw new \InvalidArgumentException(sprintf('Could not find the "%s" with an ids of "%s"', $this->_request['_entity'], implode(', ', (array) $this->_request['_id'])));
+ }
+ return $result;
+ }
+}
45 lib/Doctrine/REST/Server/Action/InsertAction.php
@@ -0,0 +1,45 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server\Action;
+
+/**
+ * REST server insert action.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class InsertAction extends AbstractSaveAction
+{
+ public function execute()
+ {
+ $entityName = $this->_resolveEntityAlias($this->_request['_entity']);
+ $entity = new $entityName();
+ $this->_updateEntityInstance($entity);
+ $this->_em->persist($entity);
+ $this->_em->flush();
+
+ return $entity;
+ }
+}
52 lib/Doctrine/REST/Server/Action/ListAction.php
@@ -0,0 +1,52 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server\Action;
+
+/**
+ * REST server list action.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class ListAction extends AbstractAction
+{
+ public function execute()
+ {
+ $qb = $this->_em->createQueryBuilder()
+ ->select('a')
+ ->from($this->_resolveEntityAlias($this->_request['_entity']), 'a');
+
+ $data = $this->_gatherData();
+ foreach ($data as $key => $value) {
+ $qb->andWhere("a.$key = :$key");
+ $qb->setParameter($key, $value);
+ }
+
+ $query = $qb->getQuery();
+ $this->_setQueryFirstAndMax($query);
+ $collection = $query->execute();
+ return $collection;
+ }
+}
44 lib/Doctrine/REST/Server/Action/UpdateAction.php
@@ -0,0 +1,44 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server\Action;
+
+/**
+ * REST server update action.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class UpdateAction extends AbstractSaveAction
+{
+ public function execute()
+ {
+ if ($entity = $this->_em->find($this->_resolveEntityAlias($this->_request['_entity']), $this->_request['_id'])) {
+ $this->_updateEntityInstance($entity);
+ $this->_em->flush();
+ }
+
+ return $entity;
+ }
+}
67 lib/Doctrine/REST/Server/Request.php
@@ -0,0 +1,67 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server;
+
+/**
+ * Class that represents a REST server request.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class Request implements \ArrayAccess
+{
+ private $_data;
+
+ public function __construct(array $request)
+ {
+ $this->_data = $request;
+ $this->_data['_format'] = isset($this->_data['_format']) ? $this->_data['_format'] : 'json';
+ }
+
+ public function getData()
+ {
+ return $this->_data;
+ }
+
+ public function offsetSet($key, $value)
+ {
+ $this->_data[$key] = $value;
+ }
+
+ public function offsetGet($key)
+ {
+ return isset($this->_data[$key]) ? $this->_data[$key] : null;
+ }
+
+ public function offsetUnset($key)
+ {
+ unset($this->_data[$key]);
+ }
+
+ public function offsetExists($key)
+ {
+ return isset($this->_data[$key]);
+ }
+}
239 lib/Doctrine/REST/Server/RequestHandler.php
@@ -0,0 +1,239 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server;
+
+use Doctrine\ORM\EntityManager;
+
+/**
+ * Class responsible for transforming a REST server request to a response.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class RequestHandler
+{
+ private $_em;
+ private $_request;
+ private $_response;
+ private $_username;
+ private $_password;
+ private $_credentialsCallback;
+ private $_entityAliases = array();
+
+ private $_actions = array(
+ 'delete' => 'Doctrine\\REST\\Server\\Action\\DeleteAction',
+ 'get' => 'Doctrine\\REST\\Server\\Action\\GetAction',
+ 'insert' => 'Doctrine\\REST\\Server\\Action\\InsertAction',
+ 'update' => 'Doctrine\\REST\\Server\\Action\\UpdateAction',
+ 'list' => 'Doctrine\\REST\\Server\\Action\\ListAction'
+ );
+
+ public function __construct(EntityManager $em, Request $request, Response $response)
+ {
+ $this->_em = $em;
+ $this->_request = $request;
+ $this->_response = $response;
+ $this->_response->setRequestHandler($this);
+ $this->_credentialsCallback = array($this, 'checkCredentials');
+ }
+
+ public function addEntityAlias($entity, $alias)
+ {
+ $this->_entityAliases[$alias] = $entity;
+ }
+
+ public function resolveEntityAlias($alias)
+ {
+ if (isset($this->_entityAliases[$alias]))
+ {
+ return $this->_entityAliases[$alias];
+ }
+ return $alias;
+ }
+
+ public function setCredentialsCallback($callback)
+ {
+ $this->_credentialsCallback = $callback;
+ }
+
+ public function registerAction($action, $className)
+ {
+ $this->_actions[$action] = $className;
+ }
+
+ public function isSecure()
+ {
+ return ($this->_username && $this->_password) ? true : false;
+ }
+
+ public function checkCredentials($username, $password)
+ {
+ if ( ! $this->isSecure()) {
+ return true;
+ }
+
+ if ($this->_username == $username && $this->_password == $password) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public function hasValidCredentials()
+ {
+ $args = array($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
+ return call_user_func_array($this->_credentialsCallback, $args);
+ }
+
+ public function getUsername()
+ {
+ return $this->_username;
+ }
+
+ public function setUsername($username)
+ {
+ $this->_username = $username;
+ }
+
+ public function getPassword()
+ {
+ return $this->_password;
+ }
+
+ public function setPassword($password)
+ {
+ $this->_password = $password;
+ }
+
+ public function getActions()
+ {
+ return $this->_actions;
+ }
+
+ public function getEntityManager()
+ {
+ return $this->_em;
+ }
+
+ public function getRequest()
+ {
+ return $this->_request;
+ }
+
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ public function execute()
+ {
+ try {
+ $this->_executeAction();
+ } catch (\Exception $e) {
+ $this->_response->setError($this->_getExceptionErrorMessage($e));
+ }
+ return $this->_response;
+ }
+
+ public function getAction($actionName)
+ {
+ if ( ! is_object($this->_actions[$actionName])) {
+ $actionClassName = $this->_actions[$actionName];
+ if (class_exists($actionClassName)) {
+ $this->_actions[$actionName] = new $actionClassName($this);
+ } else {
+ throw new \Exception(sprintf('Invalid action specified %s', $actionName));
+ }
+ }
+ return $this->_actions[$actionName];
+ }
+
+ private function _executeAction()
+ {
+ $actionInstance = $this->getAction($this->_request['_action']);
+
+ $result = $actionInstance->execute();
+
+ if ($result !== false) {
+ $this->_response->setResponseData(
+ $this->_transformResultForResponse($result)
+ );
+ } else {
+ $this->_response->setError(
+ sprintf(
+ 'An error occurred executing the action named "%s" with a request method of "%s."',
+ $this->_request['_action'],
+ $this->_request['_method']
+ )
+ );
+ }
+ }
+
+ private function _getExceptionErrorMessage(\Exception $e)
+ {
+ $message = $e->getMessage();
+
+ if ($e instanceof \PDOException) {
+ $message = preg_replace("/SQLSTATE\[.*\]: (.*)/", "$1", $message);
+ }
+
+ return $message;
+ }
+
+ private function _transformResultForResponse($result, $array = null)
+ {
+ if ( ! $array) {
+ $array = array();
+ }
+ if (is_object($result)) {
+ $entityName = get_class($result);
+ try {
+ $class = $this->_em->getMetadataFactory()->getMetadataFor($entityName);
+ foreach ($class->fieldMappings as $fieldMapping) {
+ $array[$fieldMapping['fieldName']] = $class->getReflectionProperty($fieldMapping['fieldName'])->getValue($result);
+ }
+ } catch (\Exception $e) {
+ $vars = get_object_vars($result);
+ foreach ($vars as $key => $value) {
+ $array[$key] = $value;
+ }
+ }
+ } else if (is_array($result)) {
+ foreach ($result as $key => $value) {
+ if (is_object($value) || is_array($value)) {
+ if (is_object($value)) {
+ $key = $this->_request['_entity'] . $key;
+ }
+ $array[$key] = $this->_transformResultForResponse($value, isset($array[$key]) ? $array[$key] : array());
+ } else {
+ $array[$key] = $value;
+ }
+ }
+ } else if (is_string($result)) {
+ $array = $result;
+ }
+ return $array;
+ }
+}
174 lib/Doctrine/REST/Server/Response.php
@@ -0,0 +1,174 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server;
+
+/**
+ * Class that represents a REST server response.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class Response
+{
+ private $_requestHandler;
+ private $_request;
+ private $_responseData;
+
+ public function __construct(Request $request)
+ {
+ $this->_request = $request;
+ }
+
+ public function setRequestHandler(RequestHandler $requestHandler)
+ {
+ $this->_requestHandler = $requestHandler;
+ }
+
+ public function setError($error)
+ {
+ $this->_responseData = array();
+ $this->_responseData['error'] = $error;
+ }
+
+ public function setResponseData($responseData)
+ {
+ $this->_responseData = $responseData;
+ }
+
+ public function send()
+ {
+ $this->_sendHeaders();
+ echo $this->getContent();
+ }
+
+ public function getContent()
+ {
+ $data = $this->_responseData;
+
+ switch ($this->_request['_format']) {
+ case 'php':
+ return serialize($data);
+ break;
+
+ case 'json':
+ return json_encode($data);
+ break;
+
+ case 'xml':
+ default:
+ return $this->_arrayToXml($data, $this->_request['_entity']);
+ }
+ }
+
+ private function _sendHeaders()
+ {
+ if ( ! isset($_SERVER['PHP_AUTH_USER'])) {
+ header('WWW-Authenticate: Basic realm="Doctrine REST API"');
+ header('HTTP/1.0 401 Unauthorized');
+ } else {
+ if ( ! $this->_requestHandler->hasValidCredentials()) {
+ $this->setError('Invalid credentials specified.');
+ }
+ }
+
+ switch ($this->_request['_format']) {
+ case 'php':
+ header('Content-type: text/html;');
+ break;
+
+ case 'json':
+ header('Content-type: text/json;');
+ header('Content-Disposition: attachment; filename="' . $this->_request['_action'] . '.json"');
+ break;
+
+ case 'xml':
+ default:
+ header('Content-type: application/xml;');
+ }
+ }
+
+ private function _arrayToXml($array, $rootNodeName = 'doctrine', $xml = null, $charset = null)
+ {
+ if ($xml === null) {
+ $xml = new \SimpleXmlElement("<?xml version=\"1.0\" encoding=\"utf-8\"?><$rootNodeName/>");
+ }
+
+ foreach($array as $key => $value) {
+ $key = preg_replace('/[^A-Za-z_]/i', '', $key);
+
+ if (is_array($value) && ! empty($value)) {
+ $node = $xml->addChild($key);
+ $this->_arrayToXml($value, $rootNodeName, $node, $charset);
+ } else if ($value) {
+ $charset = $charset ? $charset : 'utf-8';
+ if (strcasecmp($charset, 'utf-8') !== 0 && strcasecmp($charset, 'utf8') !== 0) {
+ $value = iconv($charset, 'UTF-8', $value);
+ }
+ $value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
+ $xml->addChild($key, $value);
+ }
+ }
+
+ return $this->_formatXml($xml);
+ }
+
+ private function _formatXml($simpleXml)
+ {
+ $xml = $simpleXml->asXml();
+
+ // add marker linefeeds to aid the pretty-tokeniser (adds a linefeed between all tag-end boundaries)
+ $xml = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", $xml);
+
+ // now indent the tags
+ $token = strtok($xml, "\n");
+ $result = ''; // holds formatted version as it is built
+ $pad = 0; // initial indent
+ $matches = array(); // returns from preg_matches()
+
+ // test for the various tag states
+ while ($token !== false) {
+ // 1. open and closing tags on same line - no change
+ if (preg_match('/.+<\/\w[^>]*>$/', $token, $matches)) {
+ $indent = 0;
+ // 2. closing tag - outdent now
+ } else if (preg_match('/^<\/\w/', $token, $matches)) {
+ $pad = $pad - 4;
+ // 3. opening tag - don't pad this one, only subsequent tags
+ } elseif (preg_match('/^<\w[^>]*[^\/]>.*$/', $token, $matches)) {
+ $indent = 4;
+ // 4. no indentation needed
+ } else {
+ $indent = 0;
+ }
+
+ // pad the line with the required number of leading spaces
+ $line = str_pad($token, strlen($token)+$pad, ' ', STR_PAD_LEFT);
+ $result .= $line . "\n"; // add to the cumulative result, with linefeed
+ $token = strtok("\n"); // get the next token
+ $pad += $indent; // update the pad size for subsequent lines
+ }
+ return $result;
+ }
+}
88 lib/Doctrine/REST/Server/Server.php
@@ -0,0 +1,88 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\REST\Server;
+
+use Doctrine\ORM\EntityManager;
+
+/**
+ * Simple REST server facade.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.org
+ * @since 2.0
+ * @version $Revision$
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class Server
+{
+ private $_requestHandler;
+ private $_request;
+ private $_response;
+
+ public function __construct(EntityManager $em, array $requestData = array())
+ {
+ $this->_request = new Request($requestData);
+ $this->_response = new Response($this->_request);
+ $this->_requestHandler = new RequestHandler($em, $this->_request, $this->_response);
+ }
+
+ public function execute()
+ {
+ $this->_requestHandler->execute();
+ return $this->_requestHandler->getResponse()->getContent();
+ }
+
+ public function addEntityAlias($entity, $alias)
+ {
+ $this->_requestHandler->addEntityAlias($entity, $alias);
+ }
+
+ public function registerAction($action, $className)
+ {
+ $this->_requestHandler->registerAction($action, $className);
+ }
+
+ public function setUsername($username)
+ {
+ $this->_requestHandler->setUsername($username);
+ }
+
+ public function setPassword($password)
+ {
+ $this->_requestHandler->setPassword($password);
+ }
+
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ public function getRequest()
+ {
+ return $this->_request;
+ }
+
+ public function getRequestHandler()
+ {
+ return $this->_requestHandler;
+ }
+}
31 tests/Doctrine/Tests/REST/AllTests.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Doctrine\Tests\REST;
+
+if ( ! defined('PHPUnit_MAIN_METHOD')) {
+ define('PHPUnit_MAIN_METHOD', 'AllTests::main');
+}
+
+require_once __DIR__ . '/TestInit.php';
+
+class AllTests
+{
+ public static function main()
+ {
+ \PHPUnit_TextUI_TestRunner::run(self::suite());
+ }
+
+ public static function suite()
+ {
+ $suite = new \PHPUnit_Framework_TestSuite('Doctrine2REST Tests');
+
+ $suite->addTestSuite('Doctrine\Tests\REST\ClientTest');
+ $suite->addTestSuite('Doctrine\Tests\REST\FunctionalTest');
+
+ return $suite;
+ }
+}
+
+if (PHPUnit_MAIN_METHOD == 'AllTests::main') {
+ AllTests::main();
+}
233 tests/Doctrine/Tests/REST/ClientTest.php
@@ -0,0 +1,233 @@
+<?php
+
+namespace Doctrine\Tests\REST;
+
+use Doctrine\REST\Client\Manager,
+ Doctrine\REST\Client\Request,
+ Doctrine\REST\Client\Entity,
+ Doctrine\REST\Client\EntityConfiguration,
+ Doctrine\REST\Client\Client,
+ Entities\ServerTest\User;
+
+require_once __DIR__ . '/TestInit.php';
+
+class ClientTest extends \PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ $this->client = new TestClient();
+
+ $manager = new Manager($this->client);
+ $manager->registerEntity('Doctrine\Tests\REST\ClientArticleTest');
+ $manager->registerEntity('Doctrine\Tests\REST\Status');
+
+ Entity::setManager($manager);
+ }
+
+ public function testGetPath()
+ {
+ $this->assertEquals('http://api.people.com/article/1.xml', ClientArticleTest::generateUrl(array('id' => 1)));
+ $this->assertEquals('http://api.people.com/article/1/test.xml', ClientArticleTest::generateUrl(array('id' => 1, 'action' => 'test')));
+
+ $this->assertEquals('http://api.people.com/article.xml', ClientArticleTest::generateUrl());
+ $this->assertEquals('http://api.people.com/article/test.xml', ClientArticleTest::generateUrl(array('action' => 'test')));
+
+ $this->assertEquals('http://api.people.com/article.xml?test=test', ClientArticleTest::generateUrl(array('parameters' => array('test' => 'test'))));
+ }
+
+ public function testInsert()
+ {
+ $test = new ClientArticleTest();
+ $test->setTitle('testing');
+ $test->save();
+
+ $this->assertEquals(1, $test->getId());
+ $this->assertEquals('http://api.people.com/article.xml', $this->client->last['url']);
+ $this->assertEquals('PUT', $this->client->last['method']);
+ }
+
+ public function testUpdate()
+ {
+ $test = new ClientArticleTest();
+ $test->setId(1);
+ $test->setTitle('test');
+ $test->save();
+
+ $this->assertEquals('test', $test->getTitle());
+ $this->assertEquals('http://api.people.com/article/1.xml', $this->client->last['url']);
+ $this->assertEquals('POST', $this->client->last['method']);
+ }
+
+ public function testDelete()
+ {
+ $test = new ClientArticleTest();
+ $test->setId(1);
+ $test->delete();
+
+ $this->assertEquals('http://api.people.com/article/1.xml', $this->client->last['url']);
+ $this->assertEquals('DELETE', $this->client->last['method']);
+ }
+
+ public function testFind()
+ {
+ $test = ClientArticleTest::find(1);
+
+ $this->assertEquals('test', $test->getTitle());
+ $this->assertEquals('http://api.people.com/article/1.xml', $this->client->last['url']);
+ $this->assertEquals('GET', $this->client->last['method']);
+ }
+
+ public function testFindWithAction()
+ {
+ $test = ClientArticleTest::find(1, 'test');
+
+ $this->assertEquals('http://api.people.com/article/1/test.xml', $this->client->last['url']);
+ $this->assertEquals('GET', $this->client->last['method']);
+ }
+
+ public function testFindAll()
+ {
+ $test = ClientArticleTest::findAll();
+ $this->assertEquals(2, count($test));
+
+ $one = $test[0];
+ $two = $test[1];
+
+ $this->assertEquals(1, $one->getId());
+ $this->assertEquals('test1', $one->getTitle());
+
+ $this->assertEquals(2, $two->getId());
+ $this->assertEquals('test2', $two->getTitle());
+
+ $this->assertEquals('http://api.people.com/article.xml', $this->client->last['url']);
+ $this->assertEquals('GET', $this->client->last['method']);
+ }
+
+ public function testFindAllWithAction()
+ {
+ $test = ClientArticleTest::findAll('test');
+
+ $this->assertEquals('http://api.people.com/article/test.xml', $this->client->last['url']);
+ $this->assertEquals('GET', $this->client->last['method']);
+ }
+
+ public function testFindAllWithParameters()
+ {
+ $test = ClientArticleTest::findAll(null, array('test' => 'test'));
+
+ $this->assertEquals('http://api.people.com/article.xml?test=test', $this->client->last['url']);
+ $this->assertEquals('GET', $this->client->last['method']);
+ }
+
+ public function testExecute()
+ {
+ $test = ClientArticleTest::execute(Client::GET, 'test', array('test' => 'test'));
+
+ $this->assertEquals('http://api.people.com/article/test.xml?test=test', $this->client->last['url']);
+ $this->assertEquals('GET', $this->client->last['method']);
+ }
+
+ public function testTwitterStatus()
+ {
+ $test = Status::execute(Client::POST, 'update', array('status' => 'updating my status'));
+
+ $this->assertEquals('http://twitter.com/statuses/update.xml', $this->client->last['url']);
+ $this->assertEquals('POST', $this->client->last['method']);
+ $this->assertEquals(array('status' => 'updating my status'), $this->client->last['parameters']);
+ $this->assertEquals('username', $this->client->last['username']);
+ $this->assertEquals('password', $this->client->last['password']);
+ }
+}
+
+class Status extends Entity
+{
+ private $id;
+ private $status;
+ private $text;
+
+ public static function configure(EntityConfiguration $entityConfiguration)
+ {
+ $entityConfiguration->setUrl('http://twitter.com');
+ $entityConfiguration->setName('statuses');
+ $entityConfiguration->setUsername('username');
+ $entityConfiguration->setPassword('password');
+ }
+}
+
+class ClientArticleTest extends Entity
+{
+ private $id;
+ private $title;
+
+ public static function configure(EntityConfiguration $entityConfiguration)
+ {
+ $entityConfiguration->setUrl('http://api.people.com');
+ $entityConfiguration->setName('article');
+ }
+
+ public function setId($id)
+ {
+ $this->id = $id;
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ }
+
+ public function getTitle()
+ {
+ return $this->title;
+ }
+}
+
+class TestClient extends Client
+{
+ public $last;
+
+ public function execute(Request $request)
+ {
+ $url = $request->getUrl();
+ $method = $request->getMethod();
+ $parameters = $request->getParameters();
+ $username = $request->getUsername();
+ $password = $request->getPassword();
+
+ $this->last = get_defined_vars();
+
+ if ($url === 'http://api.people.com/article.xml') {
+ if ($method === Client::PUT)
+ {
+ return array('id' => 1, 'title' => 'test');
+ } else if ($method === Client::POST) {
+ return $parameters;
+ } else if ($method === Client::GET) {
+ return array(
+ 'article' => array(
+ array(
+ 'id' => 1,
+ 'title' => 'test1'
+ ),
+ array(
+ 'id' => 2,
+ 'title' => 'test2'
+ )
+ )
+ );
+ }
+ return array();
+ } else if ($url === 'http://api.people.com/article/1.xml') {
+ if ($method === Client::DELETE) {
+ return array('id' => 1, 'title' => 'test');
+ } else if ($method === Client::GET) {
+ return array('id' => 1, 'title' => 'test');
+ }
+ }
+ return array();
+ }
+}
215 tests/Doctrine/Tests/REST/FunctionalTest.php
@@ -0,0 +1,215 @@
+<?php
+
+namespace Doctrine\Tests\REST;
+
+use Doctrine\ORM\EntityManager,
+ Doctrine\REST\Client\Manager,
+ Doctrine\REST\Client\Request,
+ Doctrine\REST\Client\Entity,
+ Doctrine\REST\Client\EntityConfiguration,
+ Doctrine\REST\Client\Client,
+ Doctrine\REST\Server\Server;
+
+require_once __DIR__ . '/TestInit.php';
+
+class FunctionalTest extends \PHPUnit_Framework_TestCase
+{
+ private $_manager;
+ private $_client;