├── .gitignore ├── tests └── Doctrine │ └── Tests │ ├── DoctrineTestCase.php │ ├── TestInit.php │ └── REST │ ├── AllTests.php │ ├── FunctionalTest.php │ └── ClientTest.php ├── .travis.yml ├── composer.json ├── phpunit.xml.dist ├── client.php ├── LICENSE ├── server.php ├── js ├── README.markdown ├── jActiveResource.js └── example.html ├── lib └── Doctrine │ └── REST │ ├── Client │ ├── URLGenerator │ │ ├── ApontadorURLGenerator.php │ │ ├── AbstractURLGenerator.php │ │ └── StandardURLGenerator.php │ ├── ResponseTransformer │ │ ├── AbstractResponseTransformer.php │ │ └── StandardResponseTransformer.php │ ├── Request.php │ ├── Client.php │ ├── Entity.php │ ├── Manager.php │ └── EntityConfiguration.php │ └── Server │ ├── PHPRequestParser.php │ ├── Action │ ├── GetAction.php │ ├── DeleteAction.php │ ├── UpdateAction.php │ ├── InsertAction.php │ ├── ListAction.php │ └── AbstractAction.php │ ├── Request.php │ ├── Server.php │ ├── Response.php │ └── RequestHandler.php ├── README.markdown └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | phpunit.xml 3 | -------------------------------------------------------------------------------- /tests/Doctrine/Tests/DoctrineTestCase.php: -------------------------------------------------------------------------------- 1 | =5.3.2", 13 | "doctrine/common": "*" 14 | }, 15 | "require-dev": { 16 | "doctrine/orm": "~2.4.2", 17 | "doctrine/dbal": "~2.4.2", 18 | "phpunit/phpunit": "~3.7" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Doctrine\\REST\\": "lib/Doctrine/REST" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Doctrine/Tests/REST/AllTests.php: -------------------------------------------------------------------------------- 1 | addTestSuite('Doctrine\Tests\REST\ClientTest'); 23 | $suite->addTestSuite('Doctrine\Tests\REST\FunctionalTest'); 24 | 25 | return $suite; 26 | } 27 | } 28 | 29 | if (PHPUnit_MAIN_METHOD == 'AllTests::main') { 30 | AllTests::main(); 31 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 22 | ./tests/Doctrine/ 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /client.php: -------------------------------------------------------------------------------- 1 | register(); 13 | 14 | $client = new Client(); 15 | 16 | $manager = new Manager($client); 17 | $manager->registerEntity('User'); 18 | 19 | Entity::setManager($manager); 20 | 21 | class User extends Entity 22 | { 23 | public $id; 24 | public $username; 25 | public $password; 26 | 27 | public static function configure(EntityConfiguration $entityConfiguration) 28 | { 29 | $entityConfiguration->setUrl('http://localhost/rest/server.php'); 30 | $entityConfiguration->setName('user'); 31 | } 32 | } 33 | 34 | $user = User::find(9); 35 | $user->username = 'teetertertsting'; 36 | $user->password = 'w00t'; 37 | $user->save(); 38 | print_r($user); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2014 Doctrine Project 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | register(); 10 | 11 | $classLoader = new ClassLoader('Doctrine', '/Users/jwage/Sites/doctrine2git/lib'); 12 | $classLoader->register(); 13 | 14 | $config = new \Doctrine\ORM\Configuration(); 15 | $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); 16 | $config->setProxyDir('/tmp'); 17 | $config->setProxyNamespace('Proxies'); 18 | $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver()); 19 | 20 | $connectionOptions = array( 21 | 'driver' => 'pdo_mysql', 22 | 'dbname' => 'rest_test', 23 | 'user' => 'root' 24 | ); 25 | 26 | $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); 27 | 28 | $parser = new \Doctrine\REST\Server\PHPRequestParser(); 29 | $requestData = $parser->getRequestArray(); 30 | 31 | class TestAction 32 | { 33 | public function executeDBAL() 34 | { 35 | return array('test' => 'test'); 36 | } 37 | } 38 | 39 | $server = new \Doctrine\REST\Server\Server($em->getConnection(), $requestData); 40 | $server->addEntityAction('user', 'test', 'TestAction'); 41 | $server->execute(); 42 | $server->getResponse()->send(); -------------------------------------------------------------------------------- /js/README.markdown: -------------------------------------------------------------------------------- 1 | # Javascript REST Client 2 | 3 | The Javascript REST client is an ActiveRecord style API for working with REST 4 | services. It is built on top of jQuery and is easy to use. 5 | 6 | All you need to do is make sure you require jQuery and jActiveResource: 7 | 8 | 9 | 10 | 11 | Now you can get started by defining new entity: 12 | 13 | jActiveResource.define('User', { 14 | url: 'http://localhost/rest/server.php/user', 15 | 16 | username: null, 17 | password: null, 18 | 19 | toString: function () { 20 | return 'username=' + this.username + '&password=' + this.password; 21 | } 22 | }); 23 | 24 | 25 | You can start creating instances and saving them: 26 | 27 | var user = User.create(); 28 | user.username = 'jwage'; 29 | user.password = 'password'; 30 | 31 | user.save(function (user) { 32 | alert(user.username + ' saved!'); 33 | }); 34 | 35 | You can easily retrieve all the users with findAll(): 36 | 37 | var users = User.findAll(null, function(users) { 38 | alert(users.length + ' users returned'); 39 | }); 40 | 41 | If you want to retrieve a single User you can use the find() method: 42 | 43 | var user = User.find(1); 44 | user.username = 'jon'; 45 | user.save(function (user) { 46 | alert(user.id + ' updated'); 47 | }); -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/URLGenerator/ApontadorURLGenerator.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ApontadorURLGenerator extends AbstractURLGenerator 11 | { 12 | public function generate(array $options) 13 | { 14 | $id = isset($options['id']) ? $options['id'] : null; 15 | $action = isset($options['action']) ? $options['action'] : null; 16 | $parameters = isset($options['parameters']) ? $options['parameters'] : array(); 17 | 18 | $parameters['type'] = $this->_entityConfiguration->getResponseType(); 19 | if ($id) 20 | { 21 | if ($action !== null) 22 | { 23 | $path = sprintf('/%s/%s', $id, $action); 24 | } else { 25 | $path = sprintf('/%s', $id); 26 | } 27 | } else { 28 | if ($action !== null) 29 | { 30 | $path = sprintf('/%s', $action); 31 | } else { 32 | $path = ''; 33 | } 34 | } 35 | $url = $this->_entityConfiguration->getUrl() . '/' . $this->_entityConfiguration->getName() . $path; 36 | if (is_array($parameters) && $parameters) { 37 | foreach ($this->_entityConfiguration->getProperties() as $field) { 38 | unset($parameters[$field]); 39 | } 40 | if ($parameters) { 41 | $url .= '?' . http_build_query($parameters); 42 | } 43 | } 44 | return $url; 45 | } 46 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/PHPRequestParser.php: -------------------------------------------------------------------------------- 1 | $entity, 43 | '_id' => $id, 44 | '_action' => $action, 45 | '_format' => $format 46 | ), $_REQUEST); 47 | 48 | return $data; 49 | } 50 | } -------------------------------------------------------------------------------- /js/jActiveResource.js: -------------------------------------------------------------------------------- 1 | var jActiveResource = jQuery.extend({ 2 | create: function() { 3 | return jQuery.extend(this); 4 | }, 5 | 6 | define: function (name, definition) { 7 | var instance = jQuery.extend(this, definition); 8 | eval(name + ' = instance;'); 9 | eval(name + 'Factory = jQuery.extend(jActiveResourceFactory);') 10 | }, 11 | 12 | find: function(id, callback) { 13 | this.execute('get', this.getUrl(id), null, callback); 14 | }, 15 | 16 | findAll: function(data, callback) { 17 | this.execute('get', this.getUrl(), data, callback); 18 | }, 19 | 20 | destroy: function(id, callback) { 21 | this.execute('delete', this.getUrl(this.id), null, callback); 22 | }, 23 | 24 | save: function(callback) { 25 | this.execute('post', this.getUrl(this.id), this.toString(), callback); 26 | }, 27 | 28 | getUrl: function(id, action, parameters) { 29 | if (id) { 30 | return this.url + '/' + id + '.json'; 31 | } else { 32 | return this.url + '.json'; 33 | } 34 | }, 35 | 36 | execute: function(method, url, data, callback) { 37 | $.ajax({ 38 | type: method, 39 | dataType: 'json', 40 | url: url, 41 | data: data, 42 | success: function(data) { 43 | if (data.length > 0) { 44 | var results = new Array(); 45 | for (i = 0; i < data.length; i++) { 46 | results[i] = jQuery.extend(this.prototype, data[i]); 47 | } 48 | callback(results) 49 | } else { 50 | callback(jQuery.extend(this.prototype, data)); 51 | } 52 | } 53 | }); 54 | } 55 | }); 56 | 57 | var jActiveResourceFactory = jActiveResource.extend({ 58 | destroy: function(id, callback) { 59 | this.execute('delete', this.getUrl(id), null, callback); 60 | } 61 | }); -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/URLGenerator/AbstractURLGenerator.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Client\URLGenerator; 23 | 24 | use Doctrine\REST\Client\EntityConfiguration; 25 | 26 | /** 27 | * Abstract URL generator for REST services 28 | * 29 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 30 | * @link www.doctrine-project.org 31 | * @since 2.0 32 | * @version $Revision$ 33 | * @author Jonathan H. Wage 34 | */ 35 | abstract class AbstractURLGenerator 36 | { 37 | protected $_entityConfiguration; 38 | 39 | public function __construct(EntityConfiguration $entityConfiguration) 40 | { 41 | $this->_entityConfiguration = $entityConfiguration; 42 | } 43 | 44 | abstract public function generate(array $options); 45 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/Action/GetAction.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server\Action; 23 | 24 | /** 25 | * REST server get action. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class GetAction extends AbstractAction 34 | { 35 | public function execute() 36 | { 37 | $entity = $this->_findEntityById(); 38 | 39 | if ( ! $entity) { 40 | throw new \InvalidArgumentException(sprintf('Could not find the "%s" with an id of "%s"', $this->_request['_entity'], implode(', ', (array) $this->_request['_id']))); 41 | } 42 | 43 | return $entity; 44 | } 45 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/ResponseTransformer/AbstractResponseTransformer.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Client\ResponseTransformer; 23 | 24 | use Doctrine\REST\Client\EntityConfiguration; 25 | 26 | /** 27 | * Abstract REST request response transformer 28 | * 29 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 30 | * @link www.doctrine-project.org 31 | * @since 2.0 32 | * @version $Revision$ 33 | * @author Jonathan H. Wage 34 | */ 35 | abstract class AbstractResponseTransformer 36 | { 37 | protected $_entityConfiguration; 38 | 39 | public function __construct(EntityConfiguration $entityConfiguration) 40 | { 41 | $this->_entityConfiguration = $entityConfiguration; 42 | } 43 | 44 | abstract public function transform($data); 45 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/Action/DeleteAction.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server\Action; 23 | 24 | /** 25 | * REST server delete action. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class DeleteAction extends AbstractAction 34 | { 35 | public function executeORM() 36 | { 37 | if ($entity = $this->_findEntityById()) { 38 | $this->_source->remove($entity); 39 | $this->_source->flush(); 40 | return $entity; 41 | } 42 | } 43 | 44 | public function executeDBAL() 45 | { 46 | if ($entity = $this->_findEntityById()) { 47 | $this->_source->delete($this->_getEntity(), array( 48 | $this->_getEntityIdentifierKey() => $this->_request['_id'] 49 | )); 50 | return $entity; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/Action/UpdateAction.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server\Action; 23 | 24 | /** 25 | * REST server update action. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class UpdateAction extends AbstractAction 34 | { 35 | public function executeORM() 36 | { 37 | if ($entity = $this->_findEntityById()) { 38 | $this->_updateEntityInstance($entity); 39 | $this->_source->flush(); 40 | } 41 | 42 | return $entity; 43 | } 44 | 45 | public function executeDBAL() 46 | { 47 | $entity = $this->_getEntity(); 48 | $identifierKey = $this->_getEntityIdentifierKey($entity); 49 | 50 | $data = $this->_gatherData(); 51 | $this->_source->update($entity, $data, array( 52 | $identifierKey => $this->_request['_id'] 53 | )); 54 | 55 | return $this->_findEntityById(); 56 | } 57 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/Action/InsertAction.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server\Action; 23 | 24 | /** 25 | * REST server insert action. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class InsertAction extends AbstractAction 34 | { 35 | public function executeORM() 36 | { 37 | $entity = $this->_getEntity(); 38 | 39 | $instance = new $entity(); 40 | $this->_updateEntityInstance($instance); 41 | $this->_source->persist($instance); 42 | $this->_source->flush(); 43 | 44 | return $instance; 45 | } 46 | 47 | public function executeDBAL() 48 | { 49 | $entity = $this->_getEntity(); 50 | $identifierKey = $this->_getEntityIdentifierKey(); 51 | 52 | $data = $this->_gatherData(); 53 | 54 | unset($data['id']); 55 | $this->_source->insert($entity, $data); 56 | $data = array_merge( 57 | array($identifierKey => $this->_source->lastInsertId()), 58 | $data 59 | ); 60 | 61 | return $data; 62 | } 63 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/Request.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server; 23 | 24 | /** 25 | * Class that represents a REST server request. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class Request implements \ArrayAccess 34 | { 35 | private $_data; 36 | 37 | public function __construct(array $request) 38 | { 39 | $this->_data = $request; 40 | $this->_data['_format'] = isset($this->_data['_format']) ? $this->_data['_format'] : 'json'; 41 | } 42 | 43 | public function getData() 44 | { 45 | return $this->_data; 46 | } 47 | 48 | public function offsetSet($key, $value) 49 | { 50 | $this->_data[$key] = $value; 51 | } 52 | 53 | public function offsetGet($key) 54 | { 55 | return isset($this->_data[$key]) ? $this->_data[$key] : null; 56 | } 57 | 58 | public function offsetUnset($key) 59 | { 60 | unset($this->_data[$key]); 61 | } 62 | 63 | public function offsetExists($key) 64 | { 65 | return isset($this->_data[$key]); 66 | } 67 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/Action/ListAction.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server\Action; 23 | 24 | /** 25 | * REST server list action. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class ListAction extends AbstractAction 34 | { 35 | public function executeORM() 36 | { 37 | $entity = $this->_getEntity(); 38 | $qb = $this->_source->createQueryBuilder() 39 | ->select('a') 40 | ->from($entity, 'a'); 41 | 42 | $data = $this->_gatherData(); 43 | foreach ($data as $key => $value) { 44 | $qb->andWhere("a.$key = :$key"); 45 | $qb->setParameter($key, $value); 46 | } 47 | 48 | $query = $qb->getQuery(); 49 | $this->_setQueryFirstAndMax($query); 50 | $results = $query->execute(); 51 | 52 | return $results; 53 | } 54 | 55 | public function executeDBAL() 56 | { 57 | $entity = $this->_getEntity(); 58 | 59 | $params = array(); 60 | $query = sprintf('SELECT * FROM %s', $entity); 61 | if ($data = $this->_gatherData()) { 62 | $query .= ' WHERE '; 63 | foreach ($data as $key => $value) { 64 | $query .= $key . ' = ? AND '; 65 | $params[] = $value; 66 | } 67 | $query = substr($query, 0, strlen($query) - 5); 68 | } 69 | $query = $this->_setQueryFirstAndMax($query); 70 | $results = $this->_source->fetchAll($query, $params); 71 | 72 | return $results; 73 | } 74 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/URLGenerator/StandardURLGenerator.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Client\URLGenerator; 23 | 24 | /** 25 | * Standard REST request URL generator 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class StandardURLGenerator extends AbstractURLGenerator 34 | { 35 | public function generate(array $options) 36 | { 37 | $id = isset($options['id']) ? $options['id'] : null; 38 | $action = isset($options['action']) ? $options['action'] : null; 39 | $parameters = isset($options['parameters']) ? $options['parameters'] : array(); 40 | 41 | if ($id) 42 | { 43 | if ($action !== null) 44 | { 45 | $path = sprintf('/%s/%s.' . $this->_entityConfiguration->getResponseType(), $id, $action); 46 | } else { 47 | $path = sprintf('/%s.' . $this->_entityConfiguration->getResponseType(), $id); 48 | } 49 | } else { 50 | if ($action !== null) 51 | { 52 | $path = sprintf('/%s.' . $this->_entityConfiguration->getResponseType(), $action); 53 | } else { 54 | $path = '.' . $this->_entityConfiguration->getResponseType(); 55 | } 56 | } 57 | $url = $this->_entityConfiguration->getUrl() . '/' . $this->_entityConfiguration->getName() . $path; 58 | if (is_array($parameters) && $parameters) { 59 | foreach ($this->_entityConfiguration->getProperties() as $field) { 60 | unset($parameters[$field]); 61 | } 62 | if ($parameters) { 63 | $url .= '?' . http_build_query($parameters); 64 | } 65 | } 66 | return $url; 67 | } 68 | } -------------------------------------------------------------------------------- /js/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jAct demo 5 | 6 | 7 | 8 | 9 | 56 | 57 | 58 | 59 | 60 |

Create User

61 |
62 | 63 | Username:
64 | Password:
65 | 66 |
67 | 68 |

Users

69 |
    70 | 71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/Server.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server; 23 | 24 | use Doctrine\ORM\EntityManager, 25 | Doctrine\ORM\Connection; 26 | 27 | /** 28 | * Simple REST server facade. 29 | * 30 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 31 | * @link www.doctrine-project.org 32 | * @since 2.0 33 | * @version $Revision$ 34 | * @author Jonathan H. Wage 35 | */ 36 | class Server 37 | { 38 | private $_requestHandler; 39 | private $_request; 40 | private $_response; 41 | 42 | public function __construct($source, array $requestData = array()) 43 | { 44 | $this->_request = new Request($requestData); 45 | $this->_response = new Response($this->_request); 46 | $this->_requestHandler = new RequestHandler($source, $this->_request, $this->_response); 47 | } 48 | 49 | public function execute() 50 | { 51 | $this->_requestHandler->execute(); 52 | return $this->_requestHandler->getResponse()->getContent(); 53 | } 54 | 55 | public function setEntityIdentifierKey($entity, $identifierKey) 56 | { 57 | $this->_requestHandler->setEntityIdentifierKey($entity, $identifierKey); 58 | } 59 | 60 | public function setEntityAlias($entity, $alias) 61 | { 62 | $this->_requestHandler->setEntityAlias($entity, $alias); 63 | } 64 | 65 | public function registerAction($action, $className) 66 | { 67 | $this->_requestHandler->registerAction($action, $className); 68 | } 69 | 70 | public function addEntityAction($entity, $action, $className) 71 | { 72 | $this->_requestHandler->addEntityAction($entity, $action, $className); 73 | } 74 | 75 | public function setUsername($username) 76 | { 77 | $this->_requestHandler->setUsername($username); 78 | } 79 | 80 | public function setPassword($password) 81 | { 82 | $this->_requestHandler->setPassword($password); 83 | } 84 | 85 | public function getResponse() 86 | { 87 | return $this->_response; 88 | } 89 | 90 | public function getRequest() 91 | { 92 | return $this->_request; 93 | } 94 | 95 | public function getRequestHandler() 96 | { 97 | return $this->_requestHandler; 98 | } 99 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/Request.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Client; 23 | 24 | /** 25 | * Class that represents a request to a REST service through the raw HTTP client. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class Request 34 | { 35 | private $_url; 36 | private $_method = Client::GET; 37 | private $_parameters = array(); 38 | private $_username; 39 | private $_password; 40 | private $_responseType = 'xml'; 41 | private $_responseTransformerImpl; 42 | 43 | public function setUrl($url) 44 | { 45 | $this->_url = $url; 46 | } 47 | 48 | public function getUrl() 49 | { 50 | return $this->_url; 51 | } 52 | 53 | public function setMethod($method) 54 | { 55 | $this->_method = $method; 56 | } 57 | 58 | public function getMethod() 59 | { 60 | return $this->_method; 61 | } 62 | 63 | public function setParameters($parameters) 64 | { 65 | $this->_parameters = $parameters; 66 | } 67 | 68 | public function getParameters() 69 | { 70 | return $this->_parameters; 71 | } 72 | 73 | public function setResponseType($responseType) 74 | { 75 | $this->_responseType = $responseType; 76 | } 77 | 78 | public function getResponseType() 79 | { 80 | return $this->_responseType; 81 | } 82 | 83 | public function setUsername($username) 84 | { 85 | $this->_username = $username; 86 | } 87 | 88 | public function getUsername() 89 | { 90 | return $this->_username; 91 | } 92 | 93 | public function setPassword($password) 94 | { 95 | $this->_password = $password; 96 | } 97 | 98 | public function getPassword() 99 | { 100 | return $this->_password; 101 | } 102 | 103 | public function setResponseTransformerImpl($responseTransformerImpl) 104 | { 105 | $this->_responseTransformerImpl = $responseTransformerImpl; 106 | } 107 | 108 | public function getResponseTransformerImpl() 109 | { 110 | return $this->_responseTransformerImpl; 111 | } 112 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/ResponseTransformer/StandardResponseTransformer.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Client\ResponseTransformer; 23 | 24 | /** 25 | * Standard REST request response handler. Converts a standard REST service response 26 | * to an array for easy manipulation. Works for both xml and json response types. 27 | * 28 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 29 | * @link www.doctrine-project.org 30 | * @since 2.0 31 | * @version $Revision$ 32 | * @author Jonathan H. Wage 33 | */ 34 | class StandardResponseTransformer extends AbstractResponseTransformer 35 | { 36 | public function transform($data) 37 | { 38 | switch ($this->_entityConfiguration->getResponseType()) { 39 | case 'xml': 40 | return $this->xmlToArray($data); 41 | case 'json': 42 | return $this->jsonToArray($data); 43 | break; 44 | } 45 | } 46 | 47 | public function xmlToArray($object, &$array = array()) 48 | { 49 | if (is_string($object)) { 50 | $object = new \SimpleXMLElement($object); 51 | } 52 | $children = $object->children(); 53 | $executed = false; 54 | foreach ($children as $elementName => $node) { 55 | if (isset($array[$elementName]) && $array[$elementName] !== null) { 56 | if (isset($array[$elementName][0]) && $array[$elementName][0] !== null) { 57 | $i = count($array[$elementName]); 58 | $this->xmlToArray($node, $array[$elementName][$i]); 59 | } else { 60 | $tmp = $array[$elementName]; 61 | $array[$elementName] = array(); 62 | $array[$elementName][0] = $tmp; 63 | $i = count($array[$elementName]); 64 | $this->xmlToArray($node, $array[$elementName][$i]); 65 | } 66 | } else { 67 | $array[$elementName] = array(); 68 | $this->xmlToArray($node, $array[$elementName]); 69 | } 70 | $executed = true; 71 | } 72 | if ( ! $executed && ! $children->getName()) { 73 | $array = (string) $object; 74 | } 75 | return $array; 76 | } 77 | 78 | public function jsonToArray($json) 79 | { 80 | return (array) json_decode($json); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # THIS PROJECT IS NOT MAINTAINED 2 | 3 | # Doctrine 2 REST Server and Client 4 | 5 | The Doctrine 2 REST server and client component is both an easy way to spin up 6 | REST services for your Doctrine 2 entities as well as a way to work with REST 7 | services via an ActiveRecord style implementation similiar to ActiveResource in 8 | Ruby on Rails! 9 | 10 | ## Introduction 11 | 12 | The basic concept is simple, you have a REST service (http://api.people.com/person) 13 | and you want to interact with it through a simple ActiveRecord style interface. 14 | 15 | First we can retrieve a person: 16 | 17 | $person = Person::find(1); // GET http://api.people.com/person/1.xml 18 | 19 | Now we can change some properties of that person: 20 | 21 | $person->setName('Jonathan H. Wage'); 22 | 23 | Once we're done we can simply save it and the appropriate REST call will be made: 24 | 25 | $person->save(); // POST http://api.people.com/person/1.xml (name=Jonathan H. Wage) 26 | 27 | ## Client 28 | 29 | The REST client is an ActiveRecord style implementation for working with REST 30 | services. All you need to do is define some PHP classes that are mapped to some 31 | REST service on the web. Here is an example where we map a Person to 32 | http://api.people.com/person: 33 | 34 | setUrl('http://api.people.com'); 48 | $entityConfiguration->setName('person'); 49 | } 50 | 51 | public function getId() 52 | { 53 | return $this->id; 54 | } 55 | 56 | public function setName($name) 57 | { 58 | $this->name = $name; 59 | } 60 | 61 | public function getName() 62 | { 63 | return $this->name; 64 | } 65 | } 66 | 67 | Now when we perform some actions it will generate the appropriate REST request, 68 | execute it, transform the response and hydrate the results to your PHP objects. 69 | 70 | $person = new Person(); 71 | $person->setName('Jonathan H. Wage'); 72 | $person->save(); // PUT http://api.people.com/person.xml (name=Jonathan H. Wage) 73 | 74 | We can retrieve that person again now: 75 | 76 | $person = Person::find($person->getId()); // GET http://api.people.com/person/1.xml 77 | 78 | Or you can retrieve all Person objects: 79 | 80 | $persons = Person::findAll(); 81 | 82 | ## Server 83 | 84 | The Doctrine 2 REST server allows you to easily expose your entities through some 85 | REST services. This is the raw low level server and does not include any routing 86 | or URL parsing so you would need to implement in some existing framework that 87 | has routing like Symfony or Zend Framework. 88 | 89 | All you need to do is create a new REST server instance and pass it the instance 90 | of your EntityManager you want to expose the entities for and an array representing 91 | the server request you want to process: 92 | 93 | $request = array( 94 | '_method' => 'get', 95 | '_format' => 'xml', 96 | '_entity' => 'user', 97 | '_action' => 'get', 98 | '_id' => 1 99 | ); 100 | 101 | $server = new \Doctrine\REST\Server\Server($em, $request); 102 | $server->setEntityAlias('Entities\User', 'user'); 103 | 104 | $xml = $server->execute(); 105 | 106 | The above would retrieve the User with the id of 1 and return an XML document 107 | like the following: 108 | 109 | 110 | 1 111 | jwage 112 | 113 | -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/Client.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Client; 23 | 24 | /** 25 | * Basic class for issuing HTTP requests via PHP curl. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class Client 34 | { 35 | const POST = 'POST'; 36 | const GET = 'GET'; 37 | const PUT = 'PUT'; 38 | const DELETE = 'DELETE'; 39 | 40 | public function post(Request $request) 41 | { 42 | $request->setMethod(Client::POST); 43 | return $this->execute($request); 44 | } 45 | 46 | public function get(Request $request) 47 | { 48 | $request->setMethod(Client::GET); 49 | return $this->execute($request); 50 | } 51 | 52 | public function put(Request $request) 53 | { 54 | $request->setMethod(Client::PUT); 55 | return $this->execute($request); 56 | } 57 | 58 | public function delete(Request $request) 59 | { 60 | $request->setMethod(Client::DELETE); 61 | return $this->execute($request); 62 | } 63 | 64 | public function execute(Request $request) 65 | { 66 | $ch = curl_init(); 67 | curl_setopt($ch, CURLOPT_URL, $request->getUrl()); 68 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 69 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 70 | curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); 71 | 72 | $username = $request->getUsername(); 73 | $password = $request->getPassword(); 74 | 75 | if ($username && $password) { 76 | curl_setopt ($ch, CURLOPT_USERPWD, $username . ':' . $password); 77 | } 78 | 79 | switch ($request->getMethod()) { 80 | case self::POST: 81 | case self::PUT: 82 | curl_setopt($ch, CURLOPT_POST, 1); 83 | curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($request->getParameters())); 84 | break; 85 | case self::DELETE: 86 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); 87 | break; 88 | case self::GET: 89 | default: 90 | break; 91 | } 92 | 93 | $result = curl_exec($ch); 94 | 95 | if ( ! $result) { 96 | $errorNumber = curl_errno($ch); 97 | $error = curl_error($ch); 98 | curl_close($ch); 99 | 100 | throw new \Exception($errorNumber . ': ' . $error); 101 | } 102 | 103 | curl_close($ch); 104 | 105 | return $request->getResponseTransformerImpl()->transform($result); 106 | } 107 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/Entity.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Client; 23 | 24 | /** 25 | * Abstract entity class for REST entities to extend from to give ActiveRecord 26 | * style interface for working with REST services. 27 | * 28 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 29 | * @link www.doctrine-project.org 30 | * @since 2.0 31 | * @version $Revision$ 32 | * @author Jonathan H. Wage 33 | */ 34 | abstract class Entity 35 | { 36 | protected static $_manager; 37 | 38 | public static function setManager(Manager $manager) 39 | { 40 | self::$_manager = $manager; 41 | } 42 | 43 | public function toArray() 44 | { 45 | return get_object_vars($this); 46 | } 47 | 48 | public function exists() 49 | { 50 | return self::$_manager->entityExists($this); 51 | } 52 | 53 | public function getIdentifier() 54 | { 55 | return self::$_manager->getEntityIdentifier($this); 56 | } 57 | 58 | public static function generateUrl(array $options = array()) 59 | { 60 | $configuration = self::$_manager->getEntityConfiguration(get_called_class()); 61 | return $configuration->generateUrl($options); 62 | } 63 | 64 | public static function find($id, $action = null) 65 | { 66 | return self::$_manager->execute( 67 | get_called_class(), 68 | self::generateUrl(get_defined_vars()), 69 | Client::GET 70 | ); 71 | } 72 | 73 | public static function findAll($action = null, $parameters = null) 74 | { 75 | return self::$_manager->execute( 76 | get_called_class(), 77 | self::generateUrl(get_defined_vars()), 78 | Client::GET, $parameters 79 | ); 80 | } 81 | 82 | public function save($action = null) 83 | { 84 | $parameters = $this->toArray(); 85 | $exists = $this->exists(); 86 | $method = $exists ? Client::POST : Client::PUT; 87 | $id = $exists ? $this->getIdentifier() : null; 88 | $path = $this->generateUrl(get_defined_vars()); 89 | return self::$_manager->execute($this, $path, $method, $parameters, $action); 90 | } 91 | 92 | public function delete($action = null) 93 | { 94 | $id = $this->getIdentifier(); 95 | return self::$_manager->execute( 96 | $this, $this->generateUrl(get_defined_vars()), Client::DELETE 97 | ); 98 | } 99 | 100 | public function post($action = null) 101 | { 102 | $id = $this->getIdentifier(); 103 | return self::$_manager->execute( 104 | $this, $this->generateUrl(get_defined_vars()), 105 | Client::POST, $this->toArray() 106 | ); 107 | } 108 | 109 | public function get($action = null) 110 | { 111 | return self::$_manager->execute( 112 | $this, $this->generateUrl(get_defined_vars()), 113 | Client::GET, $this->toArray() 114 | ); 115 | } 116 | 117 | public function put($action = null) 118 | { 119 | return self::$_manager->execute( 120 | $this, $this->generateUrl(get_defined_vars()), 121 | Client::PUT, $this->toArray() 122 | ); 123 | } 124 | 125 | public static function execute($method, $action, $parameters = null) 126 | { 127 | return self::$_manager->execute( 128 | get_called_class(), 129 | self::generateUrl(get_defined_vars()), 130 | $method, $parameters 131 | ); 132 | } 133 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/Action/AbstractAction.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server\Action; 23 | 24 | use Doctrine\REST\Server\RequestHandler, 25 | Doctrine\ORM\EntityManager; 26 | 27 | /** 28 | * Abstract server action class for REST server actions to extend from. 29 | * 30 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 31 | * @link www.doctrine-project.org 32 | * @since 2.0 33 | * @version $Revision$ 34 | * @author Jonathan H. Wage 35 | */ 36 | abstract class AbstractAction 37 | { 38 | protected $_requestHandler; 39 | protected $_source; 40 | protected $_request; 41 | 42 | public function __construct(RequestHandler $requestHandler) 43 | { 44 | $this->_requestHandler = $requestHandler; 45 | $this->_source = $requestHandler->getSource(); 46 | $this->_request = $requestHandler->getRequest(); 47 | } 48 | 49 | public function executeORM() 50 | { 51 | } 52 | 53 | public function executeDBAL() 54 | { 55 | } 56 | 57 | protected function _getEntity() 58 | { 59 | return $this->_requestHandler->getEntity(); 60 | } 61 | 62 | protected function _getEntityIdentifierKey() 63 | { 64 | return $this->_requestHandler->getEntityIdentifierKey($this->_getEntity()); 65 | } 66 | 67 | protected function _setQueryFirstAndMax($q) 68 | { 69 | if ( ! isset($this->_request['_page']) && ! isset($this->_request['_first']) && ! isset($this->_request['_max'])) { 70 | $this->_request['_page'] = '1'; 71 | } 72 | $maxPerPage = isset($this->_request['_max_per_page']) ? $this->_request['_max_per_page'] : 20; 73 | if (isset($this->_request['_page'])) { 74 | $page = $this->_request['_page']; 75 | $first = ($page - 1) * $maxPerPage; 76 | } else { 77 | if (isset($this->_request['_first'])) { 78 | $first = $this->_request['_first']; 79 | } else { 80 | $first = 0; 81 | } 82 | if (isset($this->_request['_max'])) { 83 | $maxPerPage = $this->_request['_max']; 84 | } 85 | } 86 | 87 | if ($this->_source instanceof EntityManager) { 88 | $q->setFirstResult($first); 89 | $q->setMaxResults($maxPerPage); 90 | } else { 91 | $platform = $this->_source->getDatabasePlatform(); 92 | return $platform->modifyLimitQuery($q, $maxPerPage, $first); 93 | } 94 | } 95 | 96 | protected function _findEntityById() 97 | { 98 | if ($this->_source instanceof EntityManager) { 99 | $entity = $this->_getEntity(); 100 | $id = $this->_request['_id']; 101 | 102 | $qb = $this->_source->createQueryBuilder() 103 | ->select('a') 104 | ->from($entity, 'a') 105 | ->where('a.id = ?1') 106 | ->setParameter('1', $id); 107 | $query = $qb->getQuery(); 108 | 109 | return $query->getSingleResult(); 110 | } else { 111 | $entity = $this->_getEntity(); 112 | $identifierKey = $this->_getEntityIdentifierKey($entity); 113 | 114 | $query = sprintf('SELECT * FROM %s WHERE %s = ?', $entity, $identifierKey); 115 | 116 | return $this->_source->fetchAssoc($query, array($this->_request['_id'])); 117 | } 118 | } 119 | 120 | protected function _updateEntityInstance($entity) 121 | { 122 | $data = $this->_gatherData($this->_request->getData()); 123 | foreach ($data as $key => $value) { 124 | $setter = 'set' . ucfirst($key); 125 | if (is_callable(array($entity, $setter))) { 126 | $entity->$setter($value); 127 | } 128 | } 129 | return $entity; 130 | } 131 | 132 | protected function _gatherData() 133 | { 134 | $data = array(); 135 | foreach ($this->_request->getData() as $key => $value) { 136 | if ($key[0] == '_') { 137 | continue; 138 | } 139 | $data[$key] = $value; 140 | } 141 | return $data; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/Manager.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Client; 23 | 24 | /** 25 | * Class responsible for managing the entities registered for REST services. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class Manager 34 | { 35 | private $_client; 36 | private $_entityConfigurations = array(); 37 | private $_identityMap = array(); 38 | 39 | public function __construct(Client $client) 40 | { 41 | $this->_client = $client; 42 | } 43 | 44 | public function registerEntity($entity) 45 | { 46 | $this->_entityConfigurations[$entity] = $entity; 47 | } 48 | 49 | public function getEntityConfiguration($entity) 50 | { 51 | if ( ! isset($this->_entityConfigurations[$entity])) { 52 | throw new \InvalidArgumentException( 53 | sprintf('Could not find entity configuration for "%s"', $entity) 54 | ); 55 | } 56 | if (is_string($this->_entityConfigurations[$entity])) { 57 | $entityConfiguration = new EntityConfiguration($entity); 58 | call_user_func_array( 59 | array($entity, 'configure'), 60 | array($entityConfiguration) 61 | ); 62 | $this->_entityConfigurations[$entity] = $entityConfiguration; 63 | } 64 | return $this->_entityConfigurations[$entity]; 65 | } 66 | 67 | public function entityExists($entity) 68 | { 69 | return $this->getEntityIdentifier($entity) ? true : false; 70 | } 71 | 72 | public function getEntityIdentifier($entity) 73 | { 74 | $configuration = $this->getEntityConfiguration(get_class($entity)); 75 | $identifierKey = $configuration->getIdentifierKey(); 76 | return $configuration->getValue($entity, $identifierKey); 77 | } 78 | 79 | public function execute($entity, $url = null, $method = Client::GET, $parameters = null) 80 | { 81 | if (is_object($entity)) { 82 | $className = get_class($entity); 83 | } else { 84 | $className = $entity; 85 | } 86 | $configuration = $this->getEntityConfiguration($className); 87 | 88 | $request = new Request(); 89 | $request->setUrl($url); 90 | $request->setMethod($method); 91 | $request->setParameters($parameters); 92 | $request->setUsername($configuration->getUsername()); 93 | $request->setPassword($configuration->getPassword()); 94 | $request->setResponseType($configuration->getResponseType()); 95 | $request->setResponseTransformerImpl($configuration->getResponseTransformerImpl()); 96 | 97 | $result = $this->_client->execute($request); 98 | 99 | if (is_array($result)) 100 | { 101 | $name = $configuration->getName(); 102 | 103 | $identifierKey = $configuration->getIdentifierKey(); 104 | $className = $configuration->getClass(); 105 | if (isset($result[$name]) && is_array($result[$name])) 106 | { 107 | $collection = array(); 108 | foreach ($result[$name] as $data) { 109 | $identifier = $data[$identifierKey]; 110 | if (isset($this->_identityMap[$className][$identifier])) 111 | { 112 | $instance = $this->_identityMap[$className][$identifier]; 113 | } else { 114 | $instance = $configuration->newInstance(); 115 | $this->_identityMap[$className][$identifier] = $instance; 116 | } 117 | $collection[] = $this->_hydrate( 118 | $configuration, $instance, $data 119 | ); 120 | } 121 | return $collection; 122 | } else if ($result) { 123 | 124 | if (is_object($entity)) 125 | { 126 | $instance = $entity; 127 | $this->_hydrate($configuration, $instance, $result); 128 | $identifier = $this->getEntityIdentifier($instance); 129 | $this->_identityMap[$className][$identifier] = $instance; 130 | } else { 131 | $identifier = $result[$identifierKey]; 132 | if (isset($this->_identityMap[$className][$identifier])) 133 | { 134 | $instance = $this->_identityMap[$className][$identifier]; 135 | } else { 136 | $instance = $configuration->newInstance(); 137 | $this->_identityMap[$className][$identifier] = $instance; 138 | } 139 | $this->_hydrate($configuration, $instance, $result); 140 | } 141 | return $instance; 142 | } 143 | } else { 144 | return array(); 145 | } 146 | } 147 | 148 | private function _hydrate($configuration, $instance, $data) 149 | { 150 | foreach ($data as $key => $value) { 151 | if (is_array($value)) 152 | { 153 | $configuration->setValue($instance, $key, (string) $value); 154 | } else { 155 | $configuration->setValue($instance, $key, $value); 156 | } 157 | } 158 | 159 | return $instance; 160 | } 161 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Client/EntityConfiguration.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Client; 23 | 24 | use Doctrine\REST\Client\URLGenerator\StandardURLGenerator, 25 | Doctrine\REST\Client\ResponseTransformer\StandardResponseTransformer; 26 | 27 | /** 28 | * Entity configuration class holds all the configuration information for a PHP5 29 | * object entity that maps to a REST service. 30 | * 31 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 32 | * @link www.doctrine-project.org 33 | * @since 2.0 34 | * @version $Revision$ 35 | * @author Jonathan H. Wage 36 | */ 37 | class EntityConfiguration 38 | { 39 | private $_prototype; 40 | private $_reflection; 41 | private $_reflectionProperties = array(); 42 | 43 | private $_attributes = array( 44 | 'class' => null, 45 | 'url' => null, 46 | 'name' => null, 47 | 'username' => null, 48 | 'password' => null, 49 | 'identifierKey' => 'id', 50 | 'responseType' => 'xml', 51 | 'urlGeneratorImpl' => null, 52 | 'responseTransformerImpl' => null, 53 | ); 54 | 55 | public function __construct($class) 56 | { 57 | $this->_attributes['class'] = $class; 58 | $this->_attributes['urlGeneratorImpl'] = new StandardURLGenerator($this); 59 | $this->_attributes['responseTransformerImpl'] = new StandardResponseTransformer($this); 60 | 61 | $this->_reflection = new \ReflectionClass($class); 62 | foreach ($this->_reflection->getProperties() as $property) { 63 | if ($property->getDeclaringClass()->getName() == $class) { 64 | $property->setAccessible(true); 65 | $this->_reflectionProperties[$property->getName()] = $property; 66 | $this->_properties[] = $property->getName(); 67 | } 68 | } 69 | } 70 | 71 | public function getReflection() 72 | { 73 | return $this->_reflection; 74 | } 75 | 76 | public function getReflectionProperties() 77 | { 78 | return $this->_reflectionProperties; 79 | } 80 | 81 | public function getProperties() 82 | { 83 | return $this->_properties; 84 | } 85 | 86 | public function setValue($entity, $field, $value) 87 | { 88 | if (isset($this->_reflectionProperties[$field])) { 89 | $this->_reflectionProperties[$field]->setValue($entity, $value); 90 | } else { 91 | $entity->$field = $value; 92 | } 93 | } 94 | 95 | public function getValue($entity, $field) 96 | { 97 | return $this->_reflectionProperties[$field]->getValue($entity); 98 | } 99 | 100 | public function generateUrl(array $options) 101 | { 102 | return $this->_attributes['urlGeneratorImpl']->generate($options); 103 | } 104 | 105 | public function setUrl($url) 106 | { 107 | $this->_attributes['url'] = rtrim($url, '/'); 108 | } 109 | 110 | public function getUrl() 111 | { 112 | return $this->_attributes['url']; 113 | } 114 | 115 | public function setClass($class) 116 | { 117 | $this->_attributes['class'] = $class; 118 | } 119 | 120 | public function getClass() 121 | { 122 | return $this->_attributes['class']; 123 | } 124 | 125 | public function setName($name) 126 | { 127 | $this->_attributes['name'] = $name; 128 | } 129 | 130 | public function getName() 131 | { 132 | return $this->_attributes['name']; 133 | } 134 | 135 | public function setUsername($username) 136 | { 137 | $this->_attributes['username'] = $username; 138 | } 139 | 140 | public function getUsername() 141 | { 142 | return $this->_attributes['username']; 143 | } 144 | 145 | public function setPassword($password) 146 | { 147 | $this->_attributes['password'] = $password; 148 | } 149 | 150 | public function getPassword() 151 | { 152 | return $this->_attributes['password']; 153 | } 154 | 155 | public function setIdentifierKey($identifierKey) 156 | { 157 | $this->_attributes['identifierKey'] = $identifierKey; 158 | } 159 | 160 | public function getIdentifierKey() 161 | { 162 | return $this->_attributes['identifierKey']; 163 | } 164 | 165 | public function setResponseType($responseType) 166 | { 167 | $this->_attributes['responseType'] = $responseType; 168 | } 169 | 170 | public function getResponseType() 171 | { 172 | return $this->_attributes['responseType']; 173 | } 174 | 175 | public function setURLGeneratorImpl($urlGeneratorImpl) 176 | { 177 | $this->_attributes['urlGeneratorImpl'] = $urlGeneratorImpl; 178 | } 179 | 180 | public function getURLGeneratorImpl() 181 | { 182 | return $this->_attributes['urlGeneratorImpl']; 183 | } 184 | 185 | public function setResponseTransformerImpl($responseHandlerImpl) 186 | { 187 | $this->_attributes['responseTransformerImpl'] = $responseHandlerImpl; 188 | } 189 | 190 | public function getResponseTransformerImpl() 191 | { 192 | return $this->_attributes['responseTransformerImpl']; 193 | } 194 | 195 | public function newInstance() 196 | { 197 | if ($this->_prototype === null) { 198 | $this->_prototype = unserialize(sprintf( 199 | 'O:%d:"%s":0:{}', 200 | strlen($this->_attributes['class']), 201 | $this->_attributes['class'] 202 | )); 203 | } 204 | return clone $this->_prototype; 205 | } 206 | } -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/Response.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server; 23 | 24 | /** 25 | * Class that represents a REST server response. 26 | * 27 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 28 | * @link www.doctrine-project.org 29 | * @since 2.0 30 | * @version $Revision$ 31 | * @author Jonathan H. Wage 32 | */ 33 | class Response 34 | { 35 | private $_requestHandler; 36 | private $_request; 37 | private $_responseData; 38 | 39 | public function __construct(Request $request) 40 | { 41 | $this->_request = $request; 42 | } 43 | 44 | public function setRequestHandler(RequestHandler $requestHandler) 45 | { 46 | $this->_requestHandler = $requestHandler; 47 | } 48 | 49 | public function setError($error) 50 | { 51 | $this->_responseData = array(); 52 | $this->_responseData['error'] = $error; 53 | } 54 | 55 | public function setResponseData($responseData) 56 | { 57 | $this->_responseData = $responseData; 58 | } 59 | 60 | public function send() 61 | { 62 | $this->_sendHeaders(); 63 | echo $this->getContent(); 64 | } 65 | 66 | public function getContent() 67 | { 68 | $data = $this->_responseData; 69 | 70 | switch ($this->_request['_format']) { 71 | case 'php': 72 | return serialize($data); 73 | break; 74 | 75 | case 'json': 76 | return json_encode($data); 77 | break; 78 | 79 | case 'xml': 80 | default: 81 | return $this->_arrayToXml($data, $this->_request['_entity']); 82 | } 83 | } 84 | 85 | private function _sendHeaders() 86 | { 87 | if ($this->_requestHandler->getUsername()) { 88 | if ( ! isset($_SERVER['PHP_AUTH_USER'])) { 89 | header('WWW-Authenticate: Basic realm="Doctrine REST API"'); 90 | header('HTTP/1.0 401 Unauthorized'); 91 | } else { 92 | if ( ! $this->_requestHandler->hasValidCredentials()) { 93 | $this->setError('Invalid credentials specified.'); 94 | } 95 | } 96 | } 97 | 98 | switch ($this->_request['_format']) { 99 | case 'php': 100 | header('Content-type: text/html;'); 101 | break; 102 | 103 | case 'json': 104 | header('Content-type: text/json;'); 105 | header('Content-Disposition: attachment; filename="' . $this->_request['_action'] . '.json"'); 106 | break; 107 | 108 | case 'xml': 109 | default: 110 | header('Content-type: application/xml;'); 111 | } 112 | } 113 | 114 | private function _arrayToXml($array, $rootNodeName = 'doctrine', $xml = null, $charset = null) 115 | { 116 | if ($xml === null) { 117 | $xml = new \SimpleXmlElement("<$rootNodeName/>"); 118 | } 119 | 120 | foreach($array as $key => $value) { 121 | if (is_numeric($key)) { 122 | $key = $rootNodeName . $key; 123 | } 124 | $key = preg_replace('/[^A-Za-z_]/i', '', $key); 125 | 126 | if (is_array($value) && ! empty($value)) { 127 | $node = $xml->addChild($key); 128 | $this->_arrayToXml($value, $rootNodeName, $node, $charset); 129 | } else if ($value) { 130 | $charset = $charset ? $charset : 'utf-8'; 131 | if (strcasecmp($charset, 'utf-8') !== 0 && strcasecmp($charset, 'utf8') !== 0) { 132 | $value = iconv($charset, 'UTF-8', $value); 133 | } 134 | $value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8'); 135 | $xml->addChild($key, $value); 136 | } 137 | } 138 | 139 | return $this->_formatXml($xml); 140 | } 141 | 142 | private function _formatXml($simpleXml) 143 | { 144 | $xml = $simpleXml->asXml(); 145 | 146 | // add marker linefeeds to aid the pretty-tokeniser (adds a linefeed between all tag-end boundaries) 147 | $xml = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", $xml); 148 | 149 | // now indent the tags 150 | $token = strtok($xml, "\n"); 151 | $result = ''; // holds formatted version as it is built 152 | $pad = 0; // initial indent 153 | $matches = array(); // returns from preg_matches() 154 | 155 | // test for the various tag states 156 | while ($token !== false) { 157 | // 1. open and closing tags on same line - no change 158 | if (preg_match('/.+<\/\w[^>]*>$/', $token, $matches)) { 159 | $indent = 0; 160 | // 2. closing tag - outdent now 161 | } else if (preg_match('/^<\/\w/', $token, $matches)) { 162 | $pad = $pad - 4; 163 | // 3. opening tag - don't pad this one, only subsequent tags 164 | } elseif (preg_match('/^<\w[^>]*[^\/]>.*$/', $token, $matches)) { 165 | $indent = 4; 166 | // 4. no indentation needed 167 | } else { 168 | $indent = 0; 169 | } 170 | 171 | // pad the line with the required number of leading spaces 172 | $line = str_pad($token, strlen($token)+$pad, ' ', STR_PAD_LEFT); 173 | $result .= $line . "\n"; // add to the cumulative result, with linefeed 174 | $token = strtok("\n"); // get the next token 175 | $pad += $indent; // update the pad size for subsequent lines 176 | } 177 | return $result; 178 | } 179 | } -------------------------------------------------------------------------------- /tests/Doctrine/Tests/REST/FunctionalTest.php: -------------------------------------------------------------------------------- 1 | setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); 23 | $config->setProxyDir('/tmp'); 24 | $config->setProxyNamespace('Proxies'); 25 | $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver()); 26 | 27 | $connectionOptions = array( 28 | 'driver' => 'pdo_sqlite', 29 | 'memory' => true 30 | ); 31 | 32 | $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); 33 | $classes = array($em->getMetadataFactory()->getMetadataFor('Doctrine\Tests\REST\DoctrineUser')); 34 | 35 | $schemaTool = new \Doctrine\ORM\Tools\SchemaTool($em); 36 | $schemaTool->dropSchema($classes); 37 | $schemaTool->createSchema($classes); 38 | 39 | if ($type === 'orm') { 40 | $this->_client = new TestFunctionalClient('user', $em); 41 | } else { 42 | $this->_client = new TestFunctionalClient('user', $em->getConnection()); 43 | } 44 | 45 | $this->_manager = new Manager($this->_client); 46 | $this->_manager->registerEntity('Doctrine\Tests\REST\User'); 47 | 48 | Entity::setManager($this->_manager); 49 | } 50 | 51 | public function testOrm() 52 | { 53 | $this->setUpRest('orm'); 54 | $this->_testActiveRecordApi(); 55 | } 56 | 57 | public function testDbal() 58 | { 59 | $this->setUpRest('dbal'); 60 | $this->_testActiveRecordApi(); 61 | } 62 | 63 | private function _testActiveRecordApi() 64 | { 65 | $user1 = new User(); 66 | $user1->setUsername('jwage'); 67 | $user1->save(); 68 | 69 | $this->assertEquals(1, $user1->getId()); 70 | 71 | $user2 = new User(); 72 | $user2->setUsername('fabpot'); 73 | $user2->save(); 74 | 75 | $this->assertEquals(2, $user2->getId()); 76 | 77 | $user3 = new User(); 78 | $user3->setUsername('romanb'); 79 | $user3->save(); 80 | 81 | $this->assertEquals(3, $user3->getId()); 82 | 83 | $user3->setUsername('romanb_new'); 84 | $user3->save(); 85 | 86 | $user3test = User::find($user3->getId()); 87 | $this->assertEquals('romanb_new', $user3test->getUsername()); 88 | 89 | $test = User::findAll(); 90 | $this->assertEquals(3, count($test)); 91 | $this->assertTrue($user1 === $test[0]); 92 | $this->assertTrue($user2 === $test[1]); 93 | $this->assertTrue($user3 === $test[2]); 94 | 95 | $user3->delete(); 96 | 97 | $test = User::findAll(); 98 | 99 | $this->assertEquals(2, count($test)); 100 | } 101 | } 102 | 103 | class TestFunctionalClient extends Client 104 | { 105 | public $name; 106 | public $source; 107 | public $data = array(); 108 | public $count = 0; 109 | 110 | public function __construct($name, $source) 111 | { 112 | $this->name = $name; 113 | $this->source = $source; 114 | } 115 | 116 | public function execServer($request, $requestArray, $parameters = array(), $responseType = 'xml') 117 | { 118 | $requestArray = array_merge($requestArray, (array) $parameters); 119 | $server = new Server($this->source, $requestArray); 120 | if ($this->source instanceof EntityManager) { 121 | $server->setEntityAlias('Doctrine\Tests\REST\DoctrineUser', 'user'); 122 | } 123 | $response = $server->getRequestHandler()->execute(); 124 | $data = $request->getResponseTransformerImpl()->transform($response->getContent()); 125 | return $data; 126 | } 127 | 128 | public function execute(Request $request) 129 | { 130 | $url = $request->getUrl(); 131 | $method = $request->getMethod(); 132 | $parameters = $request->getParameters(); 133 | $responseType = $request->getResponseType(); 134 | 135 | // GET api/user/1.xml (get) 136 | if ($method === 'GET' && preg_match_all('/api\/' . $this->name . '\/([0-9]).xml/', $url, $matches)) { 137 | $id = $matches[1][0]; 138 | return $this->execServer($request, array( 139 | '_method' => $method, 140 | '_format' => $responseType, 141 | '_entity' => $this->name, 142 | '_action' => 'get', 143 | '_id' => $id 144 | ), $parameters, $responseType); 145 | } 146 | 147 | // GET api/user.xml (list) 148 | if ($method === 'GET' && preg_match_all('/api\/' . $this->name . '.xml/', $url, $matches)) { 149 | return $this->execServer($request, array( 150 | '_method' => $method, 151 | '_format' => $responseType, 152 | '_entity' => $this->name, 153 | '_action' => 'list' 154 | ), $parameters, $responseType); 155 | } 156 | 157 | // PUT api/user.xml (insert) 158 | if ($method === 'PUT' && preg_match_all('/api\/' . $this->name . '.xml/', $url, $matches)) { 159 | return $this->execServer($request, array( 160 | '_method' => $method, 161 | '_format' => $responseType, 162 | '_entity' => $this->name, 163 | '_action' => 'insert' 164 | ), $parameters, $responseType); 165 | } 166 | 167 | 168 | // POST api/user/1.xml (update) 169 | if ($method === 'POST' && preg_match_all('/api\/' . $this->name . '\/([0-9]).xml/', $url, $matches)) { 170 | return $this->execServer($request, array( 171 | '_method' => $method, 172 | '_format' => $responseType, 173 | '_entity' => $this->name, 174 | '_action' => 'update', 175 | '_id' => $parameters['id'] 176 | ), $parameters, $responseType); 177 | } 178 | 179 | // DELETE api/user/1.xml (delete) 180 | if ($method === 'DELETE' && preg_match_all('/api\/' . $this->name . '\/([0-9]).xml/', $url, $matches)) { 181 | return $this->execServer($request, array( 182 | '_method' => $method, 183 | '_format' => $responseType, 184 | '_entity' => $this->name, 185 | '_action' => 'delete', 186 | '_id' => $matches[1][0] 187 | ), $parameters, $responseType); 188 | } 189 | } 190 | } 191 | 192 | class User extends Entity 193 | { 194 | protected $id; 195 | protected $username; 196 | 197 | public static function configure(EntityConfiguration $entityConfiguration) 198 | { 199 | $entityConfiguration->setUrl('api'); 200 | $entityConfiguration->setName('user'); 201 | } 202 | 203 | public function getId() 204 | { 205 | return $this->id; 206 | } 207 | 208 | public function getUsername() 209 | { 210 | return $this->username; 211 | } 212 | 213 | public function setUsername($username) 214 | { 215 | $this->username = $username; 216 | } 217 | } 218 | 219 | /** 220 | * @Entity 221 | * @Table(name="user") 222 | */ 223 | class DoctrineUser 224 | { 225 | /** 226 | * @Id @Column(type="integer") 227 | * @GeneratedValue(strategy="AUTO") 228 | */ 229 | private $id; 230 | 231 | /** 232 | * @Column(type="string", length=255, unique=true) 233 | */ 234 | private $username; 235 | 236 | public function setUsername($username) 237 | { 238 | $this->username = $username; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /tests/Doctrine/Tests/REST/ClientTest.php: -------------------------------------------------------------------------------- 1 | client = new TestClient(); 17 | 18 | $manager = new Manager($this->client); 19 | $manager->registerEntity('Doctrine\Tests\REST\ClientArticleTest'); 20 | $manager->registerEntity('Doctrine\Tests\REST\Status'); 21 | 22 | Entity::setManager($manager); 23 | } 24 | 25 | public function testGetPath() 26 | { 27 | $this->assertEquals('http://api.people.com/article/1.xml', ClientArticleTest::generateUrl(array('id' => 1))); 28 | $this->assertEquals('http://api.people.com/article/1/test.xml', ClientArticleTest::generateUrl(array('id' => 1, 'action' => 'test'))); 29 | 30 | $this->assertEquals('http://api.people.com/article.xml', ClientArticleTest::generateUrl()); 31 | $this->assertEquals('http://api.people.com/article/test.xml', ClientArticleTest::generateUrl(array('action' => 'test'))); 32 | 33 | $this->assertEquals('http://api.people.com/article.xml?test=test', ClientArticleTest::generateUrl(array('parameters' => array('test' => 'test')))); 34 | } 35 | 36 | public function testInsert() 37 | { 38 | $test = new ClientArticleTest(); 39 | $test->setTitle('testing'); 40 | $test->save(); 41 | 42 | $this->assertEquals(1, $test->getId()); 43 | $this->assertEquals('http://api.people.com/article.xml', $this->client->last['url']); 44 | $this->assertEquals('PUT', $this->client->last['method']); 45 | } 46 | 47 | public function testUpdate() 48 | { 49 | $test = new ClientArticleTest(); 50 | $test->setId(1); 51 | $test->setTitle('test'); 52 | $test->save(); 53 | 54 | $this->assertEquals('test', $test->getTitle()); 55 | $this->assertEquals('http://api.people.com/article/1.xml', $this->client->last['url']); 56 | $this->assertEquals('POST', $this->client->last['method']); 57 | } 58 | 59 | public function testDelete() 60 | { 61 | $test = new ClientArticleTest(); 62 | $test->setId(1); 63 | $test->delete(); 64 | 65 | $this->assertEquals('http://api.people.com/article/1.xml', $this->client->last['url']); 66 | $this->assertEquals('DELETE', $this->client->last['method']); 67 | } 68 | 69 | public function testFind() 70 | { 71 | $test = ClientArticleTest::find(1); 72 | 73 | $this->assertEquals('test', $test->getTitle()); 74 | $this->assertEquals('http://api.people.com/article/1.xml', $this->client->last['url']); 75 | $this->assertEquals('GET', $this->client->last['method']); 76 | } 77 | 78 | public function testFindWithAction() 79 | { 80 | $test = ClientArticleTest::find(1, 'test'); 81 | 82 | $this->assertEquals('http://api.people.com/article/1/test.xml', $this->client->last['url']); 83 | $this->assertEquals('GET', $this->client->last['method']); 84 | } 85 | 86 | public function testFindAll() 87 | { 88 | $test = ClientArticleTest::findAll(); 89 | $this->assertEquals(2, count($test)); 90 | 91 | $one = $test[0]; 92 | $two = $test[1]; 93 | 94 | $this->assertEquals(1, $one->getId()); 95 | $this->assertEquals('test1', $one->getTitle()); 96 | 97 | $this->assertEquals(2, $two->getId()); 98 | $this->assertEquals('test2', $two->getTitle()); 99 | 100 | $this->assertEquals('http://api.people.com/article.xml', $this->client->last['url']); 101 | $this->assertEquals('GET', $this->client->last['method']); 102 | } 103 | 104 | public function testFindAllWithAction() 105 | { 106 | $test = ClientArticleTest::findAll('test'); 107 | 108 | $this->assertEquals('http://api.people.com/article/test.xml', $this->client->last['url']); 109 | $this->assertEquals('GET', $this->client->last['method']); 110 | } 111 | 112 | public function testFindAllWithParameters() 113 | { 114 | $test = ClientArticleTest::findAll(null, array('test' => 'test')); 115 | 116 | $this->assertEquals('http://api.people.com/article.xml?test=test', $this->client->last['url']); 117 | $this->assertEquals('GET', $this->client->last['method']); 118 | } 119 | 120 | public function testExecute() 121 | { 122 | $test = ClientArticleTest::execute(Client::GET, 'test', array('test' => 'test')); 123 | 124 | $this->assertEquals('http://api.people.com/article/test.xml?test=test', $this->client->last['url']); 125 | $this->assertEquals('GET', $this->client->last['method']); 126 | } 127 | 128 | public function testTwitterStatus() 129 | { 130 | $test = Status::execute(Client::POST, 'update', array('status' => 'updating my status')); 131 | 132 | $this->assertEquals('http://twitter.com/statuses/update.xml', $this->client->last['url']); 133 | $this->assertEquals('POST', $this->client->last['method']); 134 | $this->assertEquals(array('status' => 'updating my status'), $this->client->last['parameters']); 135 | $this->assertEquals('username', $this->client->last['username']); 136 | $this->assertEquals('password', $this->client->last['password']); 137 | } 138 | } 139 | 140 | class Status extends Entity 141 | { 142 | private $id; 143 | private $status; 144 | private $text; 145 | 146 | public static function configure(EntityConfiguration $entityConfiguration) 147 | { 148 | $entityConfiguration->setUrl('http://twitter.com'); 149 | $entityConfiguration->setName('statuses'); 150 | $entityConfiguration->setUsername('username'); 151 | $entityConfiguration->setPassword('password'); 152 | } 153 | } 154 | 155 | class ClientArticleTest extends Entity 156 | { 157 | private $id; 158 | private $title; 159 | 160 | public static function configure(EntityConfiguration $entityConfiguration) 161 | { 162 | $entityConfiguration->setUrl('http://api.people.com'); 163 | $entityConfiguration->setName('article'); 164 | } 165 | 166 | public function setId($id) 167 | { 168 | $this->id = $id; 169 | } 170 | 171 | public function getId() 172 | { 173 | return $this->id; 174 | } 175 | 176 | public function setTitle($title) 177 | { 178 | $this->title = $title; 179 | } 180 | 181 | public function getTitle() 182 | { 183 | return $this->title; 184 | } 185 | } 186 | 187 | class TestClient extends Client 188 | { 189 | public $last; 190 | 191 | public function execute(Request $request) 192 | { 193 | $url = $request->getUrl(); 194 | $method = $request->getMethod(); 195 | $parameters = $request->getParameters(); 196 | $username = $request->getUsername(); 197 | $password = $request->getPassword(); 198 | 199 | $this->last = get_defined_vars(); 200 | 201 | if ($url === 'http://api.people.com/article.xml') { 202 | if ($method === Client::PUT) 203 | { 204 | return array('id' => 1, 'title' => 'test'); 205 | } else if ($method === Client::POST) { 206 | return $parameters; 207 | } else if ($method === Client::GET) { 208 | return array( 209 | 'article' => array( 210 | array( 211 | 'id' => 1, 212 | 'title' => 'test1' 213 | ), 214 | array( 215 | 'id' => 2, 216 | 'title' => 'test2' 217 | ) 218 | ) 219 | ); 220 | } 221 | return array(); 222 | } else if ($url === 'http://api.people.com/article/1.xml') { 223 | if ($method === Client::DELETE) { 224 | return array('id' => 1, 'title' => 'test'); 225 | } else if ($method === Client::GET) { 226 | return array('id' => 1, 'title' => 'test'); 227 | } 228 | } 229 | return array(); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /lib/Doctrine/REST/Server/RequestHandler.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Doctrine\REST\Server; 23 | 24 | use Doctrine\ORM\EntityManager, 25 | Doctrine\DBAL\Connection; 26 | 27 | /** 28 | * Class responsible for transforming a REST server request to a response. 29 | * 30 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 31 | * @link www.doctrine-project.org 32 | * @since 2.0 33 | * @version $Revision$ 34 | * @author Jonathan H. Wage 35 | */ 36 | class RequestHandler 37 | { 38 | private $_source; 39 | private $_request; 40 | private $_response; 41 | private $_username; 42 | private $_password; 43 | private $_credentialsCallback; 44 | private $_entities = array(); 45 | 46 | private $_actions = array( 47 | 'delete' => 'Doctrine\\REST\\Server\\Action\\DeleteAction', 48 | 'get' => 'Doctrine\\REST\\Server\\Action\\GetAction', 49 | 'insert' => 'Doctrine\\REST\\Server\\Action\\InsertAction', 50 | 'update' => 'Doctrine\\REST\\Server\\Action\\UpdateAction', 51 | 'list' => 'Doctrine\\REST\\Server\\Action\\ListAction' 52 | ); 53 | 54 | public function __construct($source, Request $request, Response $response) 55 | { 56 | $this->_source = $source; 57 | $this->_request = $request; 58 | $this->_response = $response; 59 | $this->_response->setRequestHandler($this); 60 | $this->_credentialsCallback = array($this, 'checkCredentials'); 61 | } 62 | 63 | public function configureEntity($entity, $configuration) 64 | { 65 | $this->_entities[$entity] = $configuration; 66 | } 67 | 68 | public function setEntityAlias($entity, $alias) 69 | { 70 | $this->_entities[$entity]['alias'] = $alias; 71 | } 72 | 73 | public function addEntityAction($entity, $action, $className) 74 | { 75 | $this->_entities[$entity]['actions'][$action] = $className; 76 | } 77 | 78 | public function setEntityIdentifierKey($entity, $identifierKey) 79 | { 80 | $this->_entities[$entity]['identifierKey'] = $identifierKey; 81 | } 82 | 83 | public function getEntityIdentifierKey($entity) 84 | { 85 | return isset($this->_entities[$entity]['identifierKey']) ? $this->_entities[$entity]['identifierKey'] : 'id'; 86 | } 87 | 88 | public function resolveEntityAlias($alias) 89 | { 90 | foreach ($this->_entities as $entity => $configuration) { 91 | if (isset($configuration['alias']) && $configuration['alias'] === $alias) { 92 | return $entity; 93 | } 94 | } 95 | return $alias; 96 | } 97 | 98 | public function setCredentialsCallback($callback) 99 | { 100 | $this->_credentialsCallback = $callback; 101 | } 102 | 103 | public function registerAction($action, $className) 104 | { 105 | $this->_actions[$action] = $className; 106 | } 107 | 108 | public function isSecure() 109 | { 110 | return ($this->_username && $this->_password) ? true : false; 111 | } 112 | 113 | public function checkCredentials($username, $password) 114 | { 115 | if ( ! $this->isSecure()) { 116 | return true; 117 | } 118 | 119 | if ($this->_username == $username && $this->_password == $password) { 120 | return true; 121 | } else { 122 | return false; 123 | } 124 | } 125 | 126 | public function hasValidCredentials() 127 | { 128 | $args = array($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); 129 | return call_user_func_array($this->_credentialsCallback, $args); 130 | } 131 | 132 | public function getUsername() 133 | { 134 | return $this->_username; 135 | } 136 | 137 | public function setUsername($username) 138 | { 139 | $this->_username = $username; 140 | } 141 | 142 | public function getPassword() 143 | { 144 | return $this->_password; 145 | } 146 | 147 | public function setPassword($password) 148 | { 149 | $this->_password = $password; 150 | } 151 | 152 | public function getActions() 153 | { 154 | return $this->_actions; 155 | } 156 | 157 | public function getSource() 158 | { 159 | return $this->_source; 160 | } 161 | 162 | public function getRequest() 163 | { 164 | return $this->_request; 165 | } 166 | 167 | public function getResponse() 168 | { 169 | return $this->_response; 170 | } 171 | 172 | public function getEntity() 173 | { 174 | return $this->resolveEntityAlias($this->_request['_entity']); 175 | } 176 | 177 | public function execute() 178 | { 179 | try { 180 | $entity = $this->getEntity(); 181 | $actionInstance = $this->getAction($entity, $this->_request['_action']); 182 | 183 | if (method_exists($actionInstance, 'execute')) { 184 | $result = $actionInstance->execute(); 185 | } else { 186 | if ($this->_source instanceof EntityManager) { 187 | $result = $actionInstance->executeORM(); 188 | } else { 189 | $result = $actionInstance->executeDBAL(); 190 | } 191 | } 192 | 193 | $this->_response->setResponseData( 194 | $this->_transformResultForResponse($result) 195 | ); 196 | } catch (\Exception $e) { 197 | $this->_response->setError($this->_getExceptionErrorMessage($e)); 198 | } 199 | return $this->_response; 200 | } 201 | 202 | public function getAction($entity, $actionName) 203 | { 204 | if (isset($this->_actions[$actionName])) { 205 | if ( ! is_object($this->_actions[$actionName])) { 206 | $actionClassName = $this->_actions[$actionName]; 207 | $this->_actions[$actionName] = new $actionClassName($this); 208 | } 209 | return $this->_actions[$actionName]; 210 | } 211 | if (isset($this->_entities[$entity]['actions'][$actionName])) { 212 | if ( ! is_object($this->_entities[$entity]['actions'][$actionName])) { 213 | $actionClassName = $this->_entities[$entity]['actions'][$actionName]; 214 | $this->_entities[$entity]['actions'][$actionName] = new $actionClassName($this); 215 | } 216 | return $this->_entities[$entity]['actions'][$actionName]; 217 | } 218 | } 219 | 220 | private function _getExceptionErrorMessage(\Exception $e) 221 | { 222 | $message = $e->getMessage(); 223 | 224 | if ($e instanceof \PDOException) { 225 | $message = preg_replace("/SQLSTATE\[.*\]: (.*)/", "$1", $message); 226 | } 227 | 228 | return $message; 229 | } 230 | 231 | private function _transformResultForResponse($result, $array = null) 232 | { 233 | if ( ! $array) { 234 | $array = array(); 235 | } 236 | if (is_object($result)) { 237 | $entityName = get_class($result); 238 | if ($this->_source instanceof EntityManager) { 239 | $class = $this->_source->getMetadataFactory()->getMetadataFor($entityName); 240 | foreach ($class->fieldMappings as $fieldMapping) { 241 | $array[$fieldMapping['fieldName']] = $class->getReflectionProperty($fieldMapping['fieldName'])->getValue($result); 242 | } 243 | } else { 244 | $vars = get_object_vars($result); 245 | foreach ($vars as $key => $value) { 246 | $array[$key] = $value; 247 | } 248 | } 249 | } else if (is_array($result)) { 250 | foreach ($result as $key => $value) { 251 | if (is_object($value) || is_array($value)) { 252 | if (is_object($value)) { 253 | $key = $this->_request['_entity'] . $key; 254 | } 255 | $array[$key] = $this->_transformResultForResponse($value, isset($array[$key]) ? $array[$key] : array()); 256 | } else { 257 | $array[$key] = $value; 258 | } 259 | } 260 | } else if (is_string($result)) { 261 | $array = $result; 262 | } 263 | return $array; 264 | } 265 | } -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "0f0ebd9b8bc386447ed1b2b077b59f1d", 8 | "packages": [ 9 | { 10 | "name": "doctrine/annotations", 11 | "version": "v1.2.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/doctrine/annotations.git", 15 | "reference": "6a6bec0670bb6e71a263b08bc1b98ea242928633" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/doctrine/annotations/zipball/6a6bec0670bb6e71a263b08bc1b98ea242928633", 20 | "reference": "6a6bec0670bb6e71a263b08bc1b98ea242928633", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "doctrine/lexer": "1.*", 25 | "php": ">=5.3.2" 26 | }, 27 | "require-dev": { 28 | "doctrine/cache": "1.*", 29 | "phpunit/phpunit": "4.*" 30 | }, 31 | "type": "library", 32 | "extra": { 33 | "branch-alias": { 34 | "dev-master": "1.3.x-dev" 35 | } 36 | }, 37 | "autoload": { 38 | "psr-0": { 39 | "Doctrine\\Common\\Annotations\\": "lib/" 40 | } 41 | }, 42 | "notification-url": "https://packagist.org/downloads/", 43 | "license": [ 44 | "MIT" 45 | ], 46 | "authors": [ 47 | { 48 | "name": "Roman Borschel", 49 | "email": "roman@code-factory.org" 50 | }, 51 | { 52 | "name": "Benjamin Eberlei", 53 | "email": "kontakt@beberlei.de" 54 | }, 55 | { 56 | "name": "Guilherme Blanco", 57 | "email": "guilhermeblanco@gmail.com" 58 | }, 59 | { 60 | "name": "Jonathan Wage", 61 | "email": "jonwage@gmail.com" 62 | }, 63 | { 64 | "name": "Johannes Schmitt", 65 | "email": "schmittjoh@gmail.com" 66 | } 67 | ], 68 | "description": "Docblock Annotations Parser", 69 | "homepage": "http://www.doctrine-project.org", 70 | "keywords": [ 71 | "annotations", 72 | "docblock", 73 | "parser" 74 | ], 75 | "time": "2014-09-25 16:45:30" 76 | }, 77 | { 78 | "name": "doctrine/cache", 79 | "version": "v1.3.1", 80 | "source": { 81 | "type": "git", 82 | "url": "https://github.com/doctrine/cache.git", 83 | "reference": "cf483685798a72c93bf4206e3dd6358ea07d64e7" 84 | }, 85 | "dist": { 86 | "type": "zip", 87 | "url": "https://api.github.com/repos/doctrine/cache/zipball/cf483685798a72c93bf4206e3dd6358ea07d64e7", 88 | "reference": "cf483685798a72c93bf4206e3dd6358ea07d64e7", 89 | "shasum": "" 90 | }, 91 | "require": { 92 | "php": ">=5.3.2" 93 | }, 94 | "conflict": { 95 | "doctrine/common": ">2.2,<2.4" 96 | }, 97 | "require-dev": { 98 | "phpunit/phpunit": ">=3.7", 99 | "satooshi/php-coveralls": "~0.6" 100 | }, 101 | "type": "library", 102 | "extra": { 103 | "branch-alias": { 104 | "dev-master": "1.4.x-dev" 105 | } 106 | }, 107 | "autoload": { 108 | "psr-0": { 109 | "Doctrine\\Common\\Cache\\": "lib/" 110 | } 111 | }, 112 | "notification-url": "https://packagist.org/downloads/", 113 | "license": [ 114 | "MIT" 115 | ], 116 | "authors": [ 117 | { 118 | "name": "Roman Borschel", 119 | "email": "roman@code-factory.org" 120 | }, 121 | { 122 | "name": "Benjamin Eberlei", 123 | "email": "kontakt@beberlei.de" 124 | }, 125 | { 126 | "name": "Guilherme Blanco", 127 | "email": "guilhermeblanco@gmail.com" 128 | }, 129 | { 130 | "name": "Jonathan Wage", 131 | "email": "jonwage@gmail.com" 132 | }, 133 | { 134 | "name": "Johannes Schmitt", 135 | "email": "schmittjoh@gmail.com" 136 | } 137 | ], 138 | "description": "Caching library offering an object-oriented API for many cache backends", 139 | "homepage": "http://www.doctrine-project.org", 140 | "keywords": [ 141 | "cache", 142 | "caching" 143 | ], 144 | "time": "2014-09-17 14:24:04" 145 | }, 146 | { 147 | "name": "doctrine/collections", 148 | "version": "v1.2", 149 | "source": { 150 | "type": "git", 151 | "url": "https://github.com/doctrine/collections.git", 152 | "reference": "b99c5c46c87126201899afe88ec490a25eedd6a2" 153 | }, 154 | "dist": { 155 | "type": "zip", 156 | "url": "https://api.github.com/repos/doctrine/collections/zipball/b99c5c46c87126201899afe88ec490a25eedd6a2", 157 | "reference": "b99c5c46c87126201899afe88ec490a25eedd6a2", 158 | "shasum": "" 159 | }, 160 | "require": { 161 | "php": ">=5.3.2" 162 | }, 163 | "type": "library", 164 | "extra": { 165 | "branch-alias": { 166 | "dev-master": "1.2.x-dev" 167 | } 168 | }, 169 | "autoload": { 170 | "psr-0": { 171 | "Doctrine\\Common\\Collections\\": "lib/" 172 | } 173 | }, 174 | "notification-url": "https://packagist.org/downloads/", 175 | "license": [ 176 | "MIT" 177 | ], 178 | "authors": [ 179 | { 180 | "name": "Jonathan Wage", 181 | "email": "jonwage@gmail.com", 182 | "homepage": "http://www.jwage.com/", 183 | "role": "Creator" 184 | }, 185 | { 186 | "name": "Guilherme Blanco", 187 | "email": "guilhermeblanco@gmail.com", 188 | "homepage": "http://www.instaclick.com" 189 | }, 190 | { 191 | "name": "Roman Borschel", 192 | "email": "roman@code-factory.org" 193 | }, 194 | { 195 | "name": "Benjamin Eberlei", 196 | "email": "kontakt@beberlei.de" 197 | }, 198 | { 199 | "name": "Johannes Schmitt", 200 | "email": "schmittjoh@gmail.com", 201 | "homepage": "https://github.com/schmittjoh", 202 | "role": "Developer of wrapped JMSSerializerBundle" 203 | } 204 | ], 205 | "description": "Collections Abstraction library", 206 | "homepage": "http://www.doctrine-project.org", 207 | "keywords": [ 208 | "array", 209 | "collections", 210 | "iterator" 211 | ], 212 | "time": "2014-02-03 23:07:43" 213 | }, 214 | { 215 | "name": "doctrine/common", 216 | "version": "v2.4.2", 217 | "source": { 218 | "type": "git", 219 | "url": "https://github.com/doctrine/common.git", 220 | "reference": "5db6ab40e4c531f14dad4ca96a394dfce5d4255b" 221 | }, 222 | "dist": { 223 | "type": "zip", 224 | "url": "https://api.github.com/repos/doctrine/common/zipball/5db6ab40e4c531f14dad4ca96a394dfce5d4255b", 225 | "reference": "5db6ab40e4c531f14dad4ca96a394dfce5d4255b", 226 | "shasum": "" 227 | }, 228 | "require": { 229 | "doctrine/annotations": "1.*", 230 | "doctrine/cache": "1.*", 231 | "doctrine/collections": "1.*", 232 | "doctrine/inflector": "1.*", 233 | "doctrine/lexer": "1.*", 234 | "php": ">=5.3.2" 235 | }, 236 | "require-dev": { 237 | "phpunit/phpunit": "~3.7" 238 | }, 239 | "type": "library", 240 | "extra": { 241 | "branch-alias": { 242 | "dev-master": "2.4.x-dev" 243 | } 244 | }, 245 | "autoload": { 246 | "psr-0": { 247 | "Doctrine\\Common\\": "lib/" 248 | } 249 | }, 250 | "notification-url": "https://packagist.org/downloads/", 251 | "license": [ 252 | "MIT" 253 | ], 254 | "authors": [ 255 | { 256 | "name": "Jonathan Wage", 257 | "email": "jonwage@gmail.com", 258 | "homepage": "http://www.jwage.com/", 259 | "role": "Creator" 260 | }, 261 | { 262 | "name": "Guilherme Blanco", 263 | "email": "guilhermeblanco@gmail.com", 264 | "homepage": "http://www.instaclick.com" 265 | }, 266 | { 267 | "name": "Roman Borschel", 268 | "email": "roman@code-factory.org" 269 | }, 270 | { 271 | "name": "Benjamin Eberlei", 272 | "email": "kontakt@beberlei.de" 273 | }, 274 | { 275 | "name": "Johannes Schmitt", 276 | "email": "schmittjoh@gmail.com", 277 | "homepage": "https://github.com/schmittjoh", 278 | "role": "Developer of wrapped JMSSerializerBundle" 279 | } 280 | ], 281 | "description": "Common Library for Doctrine projects", 282 | "homepage": "http://www.doctrine-project.org", 283 | "keywords": [ 284 | "annotations", 285 | "collections", 286 | "eventmanager", 287 | "persistence", 288 | "spl" 289 | ], 290 | "time": "2014-05-21 19:28:51" 291 | }, 292 | { 293 | "name": "doctrine/inflector", 294 | "version": "v1.0", 295 | "source": { 296 | "type": "git", 297 | "url": "https://github.com/doctrine/inflector.git", 298 | "reference": "54b8333d2a5682afdc690060c1cf384ba9f47f08" 299 | }, 300 | "dist": { 301 | "type": "zip", 302 | "url": "https://api.github.com/repos/doctrine/inflector/zipball/54b8333d2a5682afdc690060c1cf384ba9f47f08", 303 | "reference": "54b8333d2a5682afdc690060c1cf384ba9f47f08", 304 | "shasum": "" 305 | }, 306 | "require": { 307 | "php": ">=5.3.2" 308 | }, 309 | "type": "library", 310 | "autoload": { 311 | "psr-0": { 312 | "Doctrine\\Common\\Inflector\\": "lib/" 313 | } 314 | }, 315 | "notification-url": "https://packagist.org/downloads/", 316 | "license": [ 317 | "MIT" 318 | ], 319 | "authors": [ 320 | { 321 | "name": "Jonathan Wage", 322 | "email": "jonwage@gmail.com", 323 | "homepage": "http://www.jwage.com/", 324 | "role": "Creator" 325 | }, 326 | { 327 | "name": "Guilherme Blanco", 328 | "email": "guilhermeblanco@gmail.com", 329 | "homepage": "http://www.instaclick.com" 330 | }, 331 | { 332 | "name": "Roman Borschel", 333 | "email": "roman@code-factory.org" 334 | }, 335 | { 336 | "name": "Benjamin Eberlei", 337 | "email": "kontakt@beberlei.de" 338 | }, 339 | { 340 | "name": "Johannes Schmitt", 341 | "email": "schmittjoh@gmail.com", 342 | "homepage": "https://github.com/schmittjoh", 343 | "role": "Developer of wrapped JMSSerializerBundle" 344 | } 345 | ], 346 | "description": "Common String Manipulations with regard to casing and singular/plural rules.", 347 | "homepage": "http://www.doctrine-project.org", 348 | "keywords": [ 349 | "inflection", 350 | "pluarlize", 351 | "singuarlize", 352 | "string" 353 | ], 354 | "time": "2013-01-10 21:49:15" 355 | }, 356 | { 357 | "name": "doctrine/lexer", 358 | "version": "v1.0", 359 | "source": { 360 | "type": "git", 361 | "url": "https://github.com/doctrine/lexer.git", 362 | "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb" 363 | }, 364 | "dist": { 365 | "type": "zip", 366 | "url": "https://api.github.com/repos/doctrine/lexer/zipball/2f708a85bb3aab5d99dab8be435abd73e0b18acb", 367 | "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb", 368 | "shasum": "" 369 | }, 370 | "require": { 371 | "php": ">=5.3.2" 372 | }, 373 | "type": "library", 374 | "autoload": { 375 | "psr-0": { 376 | "Doctrine\\Common\\Lexer\\": "lib/" 377 | } 378 | }, 379 | "notification-url": "https://packagist.org/downloads/", 380 | "license": [ 381 | "MIT" 382 | ], 383 | "authors": [ 384 | { 385 | "name": "Guilherme Blanco", 386 | "email": "guilhermeblanco@gmail.com", 387 | "homepage": "http://www.instaclick.com" 388 | }, 389 | { 390 | "name": "Roman Borschel", 391 | "email": "roman@code-factory.org" 392 | }, 393 | { 394 | "name": "Johannes Schmitt", 395 | "email": "schmittjoh@gmail.com", 396 | "homepage": "https://github.com/schmittjoh", 397 | "role": "Developer of wrapped JMSSerializerBundle" 398 | } 399 | ], 400 | "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", 401 | "homepage": "http://www.doctrine-project.org", 402 | "keywords": [ 403 | "lexer", 404 | "parser" 405 | ], 406 | "time": "2013-01-12 18:59:04" 407 | } 408 | ], 409 | "packages-dev": [ 410 | { 411 | "name": "doctrine/dbal", 412 | "version": "v2.4.3", 413 | "source": { 414 | "type": "git", 415 | "url": "https://github.com/doctrine/dbal.git", 416 | "reference": "0368bc031976126e5d36d37d2c56155092b6575b" 417 | }, 418 | "dist": { 419 | "type": "zip", 420 | "url": "https://api.github.com/repos/doctrine/dbal/zipball/0368bc031976126e5d36d37d2c56155092b6575b", 421 | "reference": "0368bc031976126e5d36d37d2c56155092b6575b", 422 | "shasum": "" 423 | }, 424 | "require": { 425 | "doctrine/common": "~2.4", 426 | "php": ">=5.3.2" 427 | }, 428 | "require-dev": { 429 | "phpunit/phpunit": "3.7.*", 430 | "symfony/console": "~2.0" 431 | }, 432 | "suggest": { 433 | "symfony/console": "For helpful console commands such as SQL execution and import of files." 434 | }, 435 | "type": "library", 436 | "autoload": { 437 | "psr-0": { 438 | "Doctrine\\DBAL\\": "lib/" 439 | } 440 | }, 441 | "notification-url": "https://packagist.org/downloads/", 442 | "license": [ 443 | "MIT" 444 | ], 445 | "authors": [ 446 | { 447 | "name": "Roman Borschel", 448 | "email": "roman@code-factory.org" 449 | }, 450 | { 451 | "name": "Benjamin Eberlei", 452 | "email": "kontakt@beberlei.de" 453 | }, 454 | { 455 | "name": "Guilherme Blanco", 456 | "email": "guilhermeblanco@gmail.com" 457 | }, 458 | { 459 | "name": "Jonathan Wage", 460 | "email": "jonwage@gmail.com" 461 | } 462 | ], 463 | "description": "Database Abstraction Layer", 464 | "homepage": "http://www.doctrine-project.org", 465 | "keywords": [ 466 | "database", 467 | "dbal", 468 | "persistence", 469 | "queryobject" 470 | ], 471 | "time": "2014-10-16 11:56:49" 472 | }, 473 | { 474 | "name": "doctrine/orm", 475 | "version": "v2.4.6", 476 | "source": { 477 | "type": "git", 478 | "url": "https://github.com/doctrine/doctrine2.git", 479 | "reference": "bebacf79d8d4dae9168f0f9bc6811e6c2cb6a4d9" 480 | }, 481 | "dist": { 482 | "type": "zip", 483 | "url": "https://api.github.com/repos/doctrine/doctrine2/zipball/bebacf79d8d4dae9168f0f9bc6811e6c2cb6a4d9", 484 | "reference": "bebacf79d8d4dae9168f0f9bc6811e6c2cb6a4d9", 485 | "shasum": "" 486 | }, 487 | "require": { 488 | "doctrine/collections": "~1.1", 489 | "doctrine/dbal": "~2.4", 490 | "ext-pdo": "*", 491 | "php": ">=5.3.2", 492 | "symfony/console": "~2.0" 493 | }, 494 | "require-dev": { 495 | "satooshi/php-coveralls": "dev-master", 496 | "symfony/yaml": "~2.1" 497 | }, 498 | "suggest": { 499 | "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" 500 | }, 501 | "bin": [ 502 | "bin/doctrine", 503 | "bin/doctrine.php" 504 | ], 505 | "type": "library", 506 | "extra": { 507 | "branch-alias": { 508 | "dev-master": "2.4.x-dev" 509 | } 510 | }, 511 | "autoload": { 512 | "psr-0": { 513 | "Doctrine\\ORM\\": "lib/" 514 | } 515 | }, 516 | "notification-url": "https://packagist.org/downloads/", 517 | "license": [ 518 | "MIT" 519 | ], 520 | "authors": [ 521 | { 522 | "name": "Roman Borschel", 523 | "email": "roman@code-factory.org" 524 | }, 525 | { 526 | "name": "Benjamin Eberlei", 527 | "email": "kontakt@beberlei.de" 528 | }, 529 | { 530 | "name": "Guilherme Blanco", 531 | "email": "guilhermeblanco@gmail.com" 532 | }, 533 | { 534 | "name": "Jonathan Wage", 535 | "email": "jonwage@gmail.com" 536 | } 537 | ], 538 | "description": "Object-Relational-Mapper for PHP", 539 | "homepage": "http://www.doctrine-project.org", 540 | "keywords": [ 541 | "database", 542 | "orm" 543 | ], 544 | "time": "2014-10-06 13:22:50" 545 | }, 546 | { 547 | "name": "phpunit/php-code-coverage", 548 | "version": "1.2.18", 549 | "source": { 550 | "type": "git", 551 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 552 | "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b" 553 | }, 554 | "dist": { 555 | "type": "zip", 556 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", 557 | "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", 558 | "shasum": "" 559 | }, 560 | "require": { 561 | "php": ">=5.3.3", 562 | "phpunit/php-file-iterator": ">=1.3.0@stable", 563 | "phpunit/php-text-template": ">=1.2.0@stable", 564 | "phpunit/php-token-stream": ">=1.1.3,<1.3.0" 565 | }, 566 | "require-dev": { 567 | "phpunit/phpunit": "3.7.*@dev" 568 | }, 569 | "suggest": { 570 | "ext-dom": "*", 571 | "ext-xdebug": ">=2.0.5" 572 | }, 573 | "type": "library", 574 | "extra": { 575 | "branch-alias": { 576 | "dev-master": "1.2.x-dev" 577 | } 578 | }, 579 | "autoload": { 580 | "classmap": [ 581 | "PHP/" 582 | ] 583 | }, 584 | "notification-url": "https://packagist.org/downloads/", 585 | "include-path": [ 586 | "" 587 | ], 588 | "license": [ 589 | "BSD-3-Clause" 590 | ], 591 | "authors": [ 592 | { 593 | "name": "Sebastian Bergmann", 594 | "email": "sb@sebastian-bergmann.de", 595 | "role": "lead" 596 | } 597 | ], 598 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 599 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 600 | "keywords": [ 601 | "coverage", 602 | "testing", 603 | "xunit" 604 | ], 605 | "time": "2014-09-02 10:13:14" 606 | }, 607 | { 608 | "name": "phpunit/php-file-iterator", 609 | "version": "1.3.4", 610 | "source": { 611 | "type": "git", 612 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 613 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" 614 | }, 615 | "dist": { 616 | "type": "zip", 617 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", 618 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", 619 | "shasum": "" 620 | }, 621 | "require": { 622 | "php": ">=5.3.3" 623 | }, 624 | "type": "library", 625 | "autoload": { 626 | "classmap": [ 627 | "File/" 628 | ] 629 | }, 630 | "notification-url": "https://packagist.org/downloads/", 631 | "include-path": [ 632 | "" 633 | ], 634 | "license": [ 635 | "BSD-3-Clause" 636 | ], 637 | "authors": [ 638 | { 639 | "name": "Sebastian Bergmann", 640 | "email": "sb@sebastian-bergmann.de", 641 | "role": "lead" 642 | } 643 | ], 644 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 645 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 646 | "keywords": [ 647 | "filesystem", 648 | "iterator" 649 | ], 650 | "time": "2013-10-10 15:34:57" 651 | }, 652 | { 653 | "name": "phpunit/php-text-template", 654 | "version": "1.2.0", 655 | "source": { 656 | "type": "git", 657 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 658 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" 659 | }, 660 | "dist": { 661 | "type": "zip", 662 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", 663 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", 664 | "shasum": "" 665 | }, 666 | "require": { 667 | "php": ">=5.3.3" 668 | }, 669 | "type": "library", 670 | "autoload": { 671 | "classmap": [ 672 | "Text/" 673 | ] 674 | }, 675 | "notification-url": "https://packagist.org/downloads/", 676 | "include-path": [ 677 | "" 678 | ], 679 | "license": [ 680 | "BSD-3-Clause" 681 | ], 682 | "authors": [ 683 | { 684 | "name": "Sebastian Bergmann", 685 | "email": "sb@sebastian-bergmann.de", 686 | "role": "lead" 687 | } 688 | ], 689 | "description": "Simple template engine.", 690 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 691 | "keywords": [ 692 | "template" 693 | ], 694 | "time": "2014-01-30 17:20:04" 695 | }, 696 | { 697 | "name": "phpunit/php-timer", 698 | "version": "1.0.5", 699 | "source": { 700 | "type": "git", 701 | "url": "https://github.com/sebastianbergmann/php-timer.git", 702 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" 703 | }, 704 | "dist": { 705 | "type": "zip", 706 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 707 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 708 | "shasum": "" 709 | }, 710 | "require": { 711 | "php": ">=5.3.3" 712 | }, 713 | "type": "library", 714 | "autoload": { 715 | "classmap": [ 716 | "PHP/" 717 | ] 718 | }, 719 | "notification-url": "https://packagist.org/downloads/", 720 | "include-path": [ 721 | "" 722 | ], 723 | "license": [ 724 | "BSD-3-Clause" 725 | ], 726 | "authors": [ 727 | { 728 | "name": "Sebastian Bergmann", 729 | "email": "sb@sebastian-bergmann.de", 730 | "role": "lead" 731 | } 732 | ], 733 | "description": "Utility class for timing", 734 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 735 | "keywords": [ 736 | "timer" 737 | ], 738 | "time": "2013-08-02 07:42:54" 739 | }, 740 | { 741 | "name": "phpunit/php-token-stream", 742 | "version": "1.2.2", 743 | "source": { 744 | "type": "git", 745 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 746 | "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" 747 | }, 748 | "dist": { 749 | "type": "zip", 750 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", 751 | "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", 752 | "shasum": "" 753 | }, 754 | "require": { 755 | "ext-tokenizer": "*", 756 | "php": ">=5.3.3" 757 | }, 758 | "type": "library", 759 | "extra": { 760 | "branch-alias": { 761 | "dev-master": "1.2-dev" 762 | } 763 | }, 764 | "autoload": { 765 | "classmap": [ 766 | "PHP/" 767 | ] 768 | }, 769 | "notification-url": "https://packagist.org/downloads/", 770 | "include-path": [ 771 | "" 772 | ], 773 | "license": [ 774 | "BSD-3-Clause" 775 | ], 776 | "authors": [ 777 | { 778 | "name": "Sebastian Bergmann", 779 | "email": "sb@sebastian-bergmann.de", 780 | "role": "lead" 781 | } 782 | ], 783 | "description": "Wrapper around PHP's tokenizer extension.", 784 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 785 | "keywords": [ 786 | "tokenizer" 787 | ], 788 | "time": "2014-03-03 05:10:30" 789 | }, 790 | { 791 | "name": "phpunit/phpunit", 792 | "version": "3.7.38", 793 | "source": { 794 | "type": "git", 795 | "url": "https://github.com/sebastianbergmann/phpunit.git", 796 | "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6" 797 | }, 798 | "dist": { 799 | "type": "zip", 800 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6", 801 | "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6", 802 | "shasum": "" 803 | }, 804 | "require": { 805 | "ext-ctype": "*", 806 | "ext-dom": "*", 807 | "ext-json": "*", 808 | "ext-pcre": "*", 809 | "ext-reflection": "*", 810 | "ext-spl": "*", 811 | "php": ">=5.3.3", 812 | "phpunit/php-code-coverage": "~1.2", 813 | "phpunit/php-file-iterator": "~1.3", 814 | "phpunit/php-text-template": "~1.1", 815 | "phpunit/php-timer": "~1.0", 816 | "phpunit/phpunit-mock-objects": "~1.2", 817 | "symfony/yaml": "~2.0" 818 | }, 819 | "require-dev": { 820 | "pear-pear.php.net/pear": "1.9.4" 821 | }, 822 | "suggest": { 823 | "phpunit/php-invoker": "~1.1" 824 | }, 825 | "bin": [ 826 | "composer/bin/phpunit" 827 | ], 828 | "type": "library", 829 | "extra": { 830 | "branch-alias": { 831 | "dev-master": "3.7.x-dev" 832 | } 833 | }, 834 | "autoload": { 835 | "classmap": [ 836 | "PHPUnit/" 837 | ] 838 | }, 839 | "notification-url": "https://packagist.org/downloads/", 840 | "include-path": [ 841 | "", 842 | "../../symfony/yaml/" 843 | ], 844 | "license": [ 845 | "BSD-3-Clause" 846 | ], 847 | "authors": [ 848 | { 849 | "name": "Sebastian Bergmann", 850 | "email": "sebastian@phpunit.de", 851 | "role": "lead" 852 | } 853 | ], 854 | "description": "The PHP Unit Testing framework.", 855 | "homepage": "http://www.phpunit.de/", 856 | "keywords": [ 857 | "phpunit", 858 | "testing", 859 | "xunit" 860 | ], 861 | "time": "2014-10-17 09:04:17" 862 | }, 863 | { 864 | "name": "phpunit/phpunit-mock-objects", 865 | "version": "1.2.3", 866 | "source": { 867 | "type": "git", 868 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 869 | "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875" 870 | }, 871 | "dist": { 872 | "type": "zip", 873 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", 874 | "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875", 875 | "shasum": "" 876 | }, 877 | "require": { 878 | "php": ">=5.3.3", 879 | "phpunit/php-text-template": ">=1.1.1@stable" 880 | }, 881 | "suggest": { 882 | "ext-soap": "*" 883 | }, 884 | "type": "library", 885 | "autoload": { 886 | "classmap": [ 887 | "PHPUnit/" 888 | ] 889 | }, 890 | "notification-url": "https://packagist.org/downloads/", 891 | "include-path": [ 892 | "" 893 | ], 894 | "license": [ 895 | "BSD-3-Clause" 896 | ], 897 | "authors": [ 898 | { 899 | "name": "Sebastian Bergmann", 900 | "email": "sb@sebastian-bergmann.de", 901 | "role": "lead" 902 | } 903 | ], 904 | "description": "Mock Object library for PHPUnit", 905 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 906 | "keywords": [ 907 | "mock", 908 | "xunit" 909 | ], 910 | "time": "2013-01-13 10:24:48" 911 | }, 912 | { 913 | "name": "symfony/console", 914 | "version": "v2.5.6", 915 | "target-dir": "Symfony/Component/Console", 916 | "source": { 917 | "type": "git", 918 | "url": "https://github.com/symfony/Console.git", 919 | "reference": "6f177fca24200a5b97aef5ce7a5c98124a0f0db0" 920 | }, 921 | "dist": { 922 | "type": "zip", 923 | "url": "https://api.github.com/repos/symfony/Console/zipball/6f177fca24200a5b97aef5ce7a5c98124a0f0db0", 924 | "reference": "6f177fca24200a5b97aef5ce7a5c98124a0f0db0", 925 | "shasum": "" 926 | }, 927 | "require": { 928 | "php": ">=5.3.3" 929 | }, 930 | "require-dev": { 931 | "psr/log": "~1.0", 932 | "symfony/event-dispatcher": "~2.1" 933 | }, 934 | "suggest": { 935 | "psr/log": "For using the console logger", 936 | "symfony/event-dispatcher": "" 937 | }, 938 | "type": "library", 939 | "extra": { 940 | "branch-alias": { 941 | "dev-master": "2.5-dev" 942 | } 943 | }, 944 | "autoload": { 945 | "psr-0": { 946 | "Symfony\\Component\\Console\\": "" 947 | } 948 | }, 949 | "notification-url": "https://packagist.org/downloads/", 950 | "license": [ 951 | "MIT" 952 | ], 953 | "authors": [ 954 | { 955 | "name": "Symfony Community", 956 | "homepage": "http://symfony.com/contributors" 957 | }, 958 | { 959 | "name": "Fabien Potencier", 960 | "email": "fabien@symfony.com" 961 | } 962 | ], 963 | "description": "Symfony Console Component", 964 | "homepage": "http://symfony.com", 965 | "time": "2014-10-05 13:57:04" 966 | }, 967 | { 968 | "name": "symfony/yaml", 969 | "version": "v2.5.6", 970 | "target-dir": "Symfony/Component/Yaml", 971 | "source": { 972 | "type": "git", 973 | "url": "https://github.com/symfony/Yaml.git", 974 | "reference": "2d9f527449cabfa8543dd7fa3a466d6ae83d6726" 975 | }, 976 | "dist": { 977 | "type": "zip", 978 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/2d9f527449cabfa8543dd7fa3a466d6ae83d6726", 979 | "reference": "2d9f527449cabfa8543dd7fa3a466d6ae83d6726", 980 | "shasum": "" 981 | }, 982 | "require": { 983 | "php": ">=5.3.3" 984 | }, 985 | "type": "library", 986 | "extra": { 987 | "branch-alias": { 988 | "dev-master": "2.5-dev" 989 | } 990 | }, 991 | "autoload": { 992 | "psr-0": { 993 | "Symfony\\Component\\Yaml\\": "" 994 | } 995 | }, 996 | "notification-url": "https://packagist.org/downloads/", 997 | "license": [ 998 | "MIT" 999 | ], 1000 | "authors": [ 1001 | { 1002 | "name": "Symfony Community", 1003 | "homepage": "http://symfony.com/contributors" 1004 | }, 1005 | { 1006 | "name": "Fabien Potencier", 1007 | "email": "fabien@symfony.com" 1008 | } 1009 | ], 1010 | "description": "Symfony Yaml Component", 1011 | "homepage": "http://symfony.com", 1012 | "time": "2014-10-01 05:50:18" 1013 | } 1014 | ], 1015 | "aliases": [], 1016 | "minimum-stability": "stable", 1017 | "stability-flags": [], 1018 | "prefer-stable": false, 1019 | "platform": { 1020 | "php": ">=5.3.2" 1021 | }, 1022 | "platform-dev": [] 1023 | } 1024 | --------------------------------------------------------------------------------