Drest doesn’t handle the loading of your Entity classes so it’s important this is set up before hand. It’s very likely that if you’re already running Doctrine ORM then you’ve taken care of registering your Entity classes on an autoloader (along with your Proxy or Repository classes). If you’re using composer you can leverage it’s autoloader and register your Entities on it. Like so:
// Register your entities on an autoloader (you may have already taken care of this)
$loader = require '/path/to/vendor/autoload.php';
// Add the entities namespace to the loader (namespace, path)
$loader->add('Entities', __DIR__.'/../');
You may have already done this, and can skip this part.
To instantiate the drest manager object you must pass it an instance of doctrine ORM’s EntityManager. If your application is already using the ORM tool this this will be set up. If not, here’s an example configuration to help.
Please refer to the Doctrine ORM manual for more information.
$ormConfig = new \Doctrine\ORM\Configuration();
$pathToEntities = array(__DIR__ . '/../Entities');
$ORMDriver = $ormConfig->newDefaultAnnotationDriver($pathToEntities, false);
$ormConfig->setMetadataDriverImpl($ORMDriver);
// Other various configuration options..
$ormConfig->setProxyDir(__DIR__ . '/Entities/Proxies');
$ormConfig->setProxyNamespace('Entities\Proxies');
$ormConfig->setAutoGenerateProxyClasses(true);
// Creation of the entity manager
$em = \Doctrine\ORM\EntityManager::create(array(
'host' => 'localhost',
'user' => 'username',
'password' => 'password',
'dbname' => 'drest',
'driver' => 'pdo_mysql'
), $ormConfig);
When setting up your doctrine ORM entity manager ensure that your not using the SimpleAnnotationReader class. This is used to allow the ORM to read annotations without requiring a namespace declaration on the docblock. So for example this would allow @Column instead of the more explicit @ORM\Column. The former example would obviously cause a clash when using multiple drivers to read annotations, so make sure you use the fully namespaced example.
If you use the ORM’s convenience method newDefaultAnnotationDriver(), ensure you pass the second parameter as false. Like so:
$driver = $ORMConfig->newDefaultAnnotationDriver($pathToEntities, false);
Once you have your entity manger set up you can now create the drest manager object. This object is responsible for dispatching your application requests. It has an internal router loaded up with any @Route definitions you’ve specified on your entities. Once the dispatch() method is called any requests to that location are routed accordingly.
One requirement when creating the drest manager is that you provide your configuration object with an array of paths to where your entities are located.
It’s also advised that you provide a caching mechanism for the annotations reader.
In the same fashion as Doctrine’s ORM tool drest interacts with data objects generated from reading the annotations you’ve supplied.
Parsing these are computationally expensive, and so a caching mechanism is imperative for a production environment. You can use any of the cache adapters provided by doctrine.
Drest requires that you pass in an instance (or multiple instances) of the Doctrine Entity Manager. This is used to get knowledge of entity relations. It allows you to annotate a single entity as a rest endpoint, and still use all of its relations without having to explicitly provide knowledge of those related entities to the drest configuration object. To pass in a Doctrine Entity Manager it must be wrapped in a \Drest\EntityManagerRegistry instance. This allows you to use multiple entity managers or connection resources. If you only have one Entity Manager then static method is available to help you get up running quickly.
$drestConfig = new \Drest\Configuration();
$drestConfig->addPathsToConfigFiles($pathToEntities);
$drestConfig->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache()); // use a different adapter for production
$drestManager = \Drest\Manager::create(\Drest\EntityManagerRegistry::getSimpleManagerRegistry($em), $drestConfig);
echo $drestManager->dispatch();
By default Drest will use the annotation driver and attempt to read the configuration from your entity objects. If you’d like to provide this configuration via PHP, Yaml or JSON then just let the drest config object know, and where it can find the configuration files.
$drestConfig->setMetadataDriverClass('\Drest\Mapping\Driver\PhpDriver');
$drestConfig->addPathsToConfigFiles([
__DIR__ .'/../config/php/user.php',
__DIR__ .'/../config/php/profile.php',
]);
Some examples of how these should be formatted can be found here
To be able to effectively route requests drest requires the use of a Request object. These are typically immutable objects that provide information on the HTTP request. When calling the dispatch() method from drest manager object, you can optionally pass in your framework request object (if an adapter has been created for it). The drest manager will the maintain an instance of Drest\Request which will act as a proxy to your actual request object.
echo $drestManager->dispatch($myRequestObj);
If you don’t pass in a request object then Drest\Request will default to creating an adapted instance of Symfony\Component\HttpFoundation\Request. In this instance its required that you have the symfony/http-foundation component installed.
Once drest has determined what content is to be written back to the user after an API request, a response object is required to do so. As with the request object you can pass in your own response object on the dispatch() call to be populated (if an adapter has been created for it).
$response = $drestManager->dispatch($myRequestObj, $myResponseObj);
// echo the response
echo $response;
// Fetch back my original object
$myResponseObj = $response->getResponse();
The drest manager will then maintain an instance of Drest\Response which will act as a proxy to your actual response object. The dispatch() call will return this Drest\Response instance which you can either echo directly (to send the headers and display the content), or manipulate further. You can even retrieve your original request object, updated with respective status, header and content information by calling getResponse().
Again, if no object is passed drest will attempt to create an instance of Symfony\Component\HttpFoundation\Response for use. This will require the installation of the the symfony/http-foundation component.
To begin leveraging drest you need to include the annotation namespace into your entities. It’s likely you’ve already done something similar for the ORM. Once declared you can enable an entity as a resource by using the @Drest\Resource annotation.
namespace Entities;
use Drest\Mapping\Annotation as Drest;
use Doctrine\ORM\Mapping as ORM;
/**
* @Drest\Resource(
* representations={"Json", "Xml"},
* routes={
* @Drest\Route(
* name="get_user",
* routePattern="/user/:id",
* verbs={"GET"}
* ),
* @Drest\Route(...)
* }
* )
*
* @ORM\Table(name="user")
* @ORM\Entity
*/
class User
{
// user properties
}
However the bulk of your configuration will happen on the @Drest\Route annotations. As a minimum these provide mapping information to match client’s request to a respective action. But there are a number of other configuration options available.
@Drest\Route(
name="update_user",
routePattern="/user/:id",
routeConditions={"id": "\d+"},
verbs={"PUT", "PATCH"},
expose={"email_address", "profile" : {"title", "firstname", "lastname"}},
action="MyNamespace\CustomServiceClass",
origin="get_user"
),
@Drest\Route(
name="get_users",
routePattern="/users",
verbs={"GET"},
collection=true,
allowOptions=false
)
(array) routeConditions optional - You can provide an array of conditions to be provided on route variables. For example routeConditions={“id”: “\d+”} would ensure that the id variable passed in the url was a decimal before this route was deemed a match.
(string) action optional - Drest comes shipped with a number of default behaviors that occur based on the HTTP verb used in the request. This however might not quite be the behaviour your after. You can instead use your own service action class. These must extend \Drest\Service\Action\AbstractAction
(boolean) allowOptions optional - When a client performs an options request on route drest will collect all matching routes (excluding a verb check)
and return them on the Allow HTTP header.
Any matching route configured with verb={“OPTIONS”} will take precedence over this.
Instead of specifying this behaviour per route, it can instead be defined across all routes with Drest\Configuration::setAllowOptionsRequest({boolean}).
So for example if you had routes [PUT, PATCH] /user/:id, [POST] /users and [GET] /user:/id and an OPTIONS request was
made to /user/:id, then a response would be sent back to the client with an Allow header containing “PATCH, PUT, GET”.
This feature is on by default.
Drest push requests [POST/PUT/PATCH] are handled very differently to pull [GET] as these need to be able to manipulate the state of an entity (based on what information the client has sent through). For this purpose drest has the ability to register method handles to be triggered. These are optional, but very much advised. You can register a handle method like so:
// If the following route definition was used
@Drest\Route(
name="update_users",
routePattern="/user/:id",
verbs={"PUT"}
)
/**
* @Drest\Handle(for="update_users")
*/
public function myMethod(array $data)
{
// inspect $data array and change entity state
}
For simplicity an array representation of the pushed data is passed as a parameter to the handle method along with the request object. In some instances you may need more information about the request to determine the state of your entity. In this instance you can instruct drest to inject the Drest\Request object instance.
/**
* @Drest\Handle(for="update_users")
*/
public function myMethod(array $data, \Drest\Request $request)
{
}
Any registered handle function will be called when using the default service actions.
If you’ve created your own service action class and you still want to call a registered handle then you’ll need to fire it off manually. See creating your own service actions for information on how this can be done.