Service Actions

What are they?

Service actions are responsible for handling what behaviours occur during a request.

Drest comes with a number of default service actions that are triggered unless a custom action is specified on the @Drest\Route annotation. The default action is determined on the HTTP verb used, and whether the matched route is a collection or element endpoint (ie user vs users).

Default Behaviours

All default behaviours operate directly on doctrine’s entity manager. Pull requests [GET] have their data fetched using doctrine’s array hydration for speed. This means that it’s the persisted data that is exposed to your endpoint. So any any tranformations / augmentations you may have written into lifecycle events on your entities will not be executed. If you need to manipulate the data from it’s persisted state before sending to the user you may have to create your own custom service action.

Error handling..

GET Element

  • Create a doctrine query builder instance
  • Register any expose definitions to the query builder, ensuring we only select the data that’s needed
  • Use any URL parameters as filters
  • Attempt to fetch a single result
  • Return the result set
Possible errors
  • Unable to retrieve a result - returns HTTP status 404
Example matching annotation
@Drest\Route(
   name="get_user",
   routePattern="/user/:id",
   verbs={"GET"}
)

GET Collection

  • Create a doctrine query builder instance
  • Register any expose definitions to the query builder, ensuring we only select the data that’s needed
  • Register any available URL parameters as filters
  • Attempt to fetch a collection of results
  • Return the result set
Possible errors
  • Unable to retrieve any results - returns HTTP status 404
Example matching annotation
@Drest\Route(
   name="get_users",
   routePattern="/users",
   verbs={"GET"},
   collection=true
)

POST Element

  • Create a new instance of the entity (the one the matched route was annotated on)
  • Run the registered handle method (passing in an array representation)
  • Persist the new object
  • Return the location of the new entity in both the response body and HTTP header ‘Location’ and set response status to 201.
Possible errors
  • Unable to save element - returns HTTP status 500
Example matching annotation
@Drest\Route(
    name="post_user", 
    routePattern="/user", 
    verbs={"POST"}
)

PUT/PATCH Element

  • Create a doctrine query builder instance
  • Register any available URL parameters as filters
  • Attempt to fetch a single result
  • Run the registered handle method (passing in an array representation)
  • Flush the entity manager to persist any changes
  • Return a status 200 with result set containing location information
Possible errors
  • Unable to fetch the element - returns HTTP status 404
  • Unable to save element changes - returns HTTP status 500
Example matching annotation
@Drest\Route(
    name="update_user", 
    routePattern="/user/:id", 
    verbs={"PUT", "PATCH"}
)

DELETE Element

  • Create a doctrine query builder instance
  • Register any available URL parameters as filters
  • Attempt to fetch a single result
  • Register resulting object to be removed and flush the entity manager
  • Return a status 200 with result set containing response: successfully deleted
Possible errors
  • Unable to fetch the element - returns HTTP status 404
  • Unable to execute the delete query - returns HTTP status 500
Example matching annotation
@Drest\Route(
    name="delete_user", 
    routePattern="/user/:id", 
    verbs={"DELETE"}
)

DELETE Collection

  • Create a doctrine query builder instance to delete all types on that entity class name
  • Register any available URL parameters as filters
  • Execute the query
  • Return a status 200 with result set containing response: successfully deleted
Possible errors
  • Unable to execute the delete query - returns HTTP status 500
Example matching annotation
@Drest\Route(
    name="delete_user", 
    routePattern="/users", 
    verbs={"DELETE"},
    collection=true
)

Creating your own

You very easily build your own service actions by extending the \Drest\Service\Action\AbstractAction class. You must then implement the method execute().
There are a number of objects at your disposal. You can use the entity manger to start constructing queries, inspect the request, manipulate the response object and more.

namespace Action;
class Custom extends \Drest\Service\Action\AbstractAction
{
    public function execute()
    {
        // Get the Doctrine Entity Manager (Doctrine\ORM\EntityManager)
        $this->service->getEntityManager();
        
        // Get Drest Manager (Drest\Manager)
        $this->service->getDrestManager();
        
        // Get the request object (Drest\Request)
        $this->service->getRequest();
        
        // Get the response object (Drest\Response)
        $this->service->getResponse();
        
        // Get the route that was matched (Drest\Mapping\RouteMetaData)
        $this->service->getMatchedRoute();
        
        // Get the representation type that's required - XML / JSON (Drest\Representation\AbstractRepresentation)
        $this->service->getRepresentation();           
        
        // .. execute my own logic, return a custom result set ..
         return ResultSet::create(array('name' => 'lee', 'email' => 'lee@somedomain.com'), 'user');
    }
}

Once you’ve created your class you simply need to register it on the service action registry when setting up your drest manager. Note that you must use the fully qualified class name of your entity class (include namespace).

$customAction = new Action\Custom()
$actionRegistry = new Drest\Service\Action\Registry();
$actionRegistry->register(
    $customAction,
    ['Entities\User::get_user']
);

$drestManager = \Drest\Manager::create($emr, $drestConfig, $evm, $actionRegistry);

// Alternatively you can register / unregister service actions at any point using the drest manager

// Remove this action and all routes that are registered to use it
$drestManager->getServiceActionRegistry()->unregisterByAction($customAction);

// Remove any registered action for this route
$drestManager->getServiceActionRegistry()->unregisterByRoute('Entities\User::get_user');

Even though you could directly manipulate the response object, if the execute method returns an object of type \Drest\Query\ResultSet then this is automatically written to the document body in requested representation. Any other return types are ignored.

Using requested expose settings

When creating a custom pull action [GET] you may still want to adhere to the expose filtering that you set up, or the client has requested. On the matched route object will be a pre-determined expose array which you’ll need to apply to your query builder instance.

This can be done using the method registerExpose($exposeArray, $queryBuilder, $doctrineORMMetaData).

$classMetaData = $this->getMatchedRoute()->getClassMetaData();
$elementName = $classMetaData->getEntityAlias();

// Note this will return the default entity manager.
$em = $this->getEntityManager();

$qb = $this->registerExpose(
    $this->getMatchedRoute()->getExpose(),
    $em->createQueryBuilder()->from($classMetaData->getClassName(), $elementName),
    $em->getClassMetadata($classMetaData->getClassName())
);

The example above will fetch the default entity manager that was registered with \Drest\EntityManagerRegistry. If you have multiple entity managers or connection resources within your application then you can retrieve the one you require like so:

$this->getEntityManagerRegistry()->getManager({name you registered it with})

Triggering a handle

For PUT/PATCH/POST requests that have a handle function registered then you may want to trigger this from your service action. To so this simply pass in your entity object instance into the runHandle($entity) method. If a handle isn’t registered, nothing will be called.

// this can be either an newly created instance of the entity object, 
// or one fetched through the doctrine entity manager using "ORM\Query::HYDRATE_OBJECT"  
$this->runHandle($object);

Note: The default “postElement” action will instantiate an instance of your entity, be sure not to mix behaviours in a handle method that should go into __construct().