Annotations Parser


Annotations - 图1

Overview

It is the first time that an annotations parser component is written in C for the PHP world. Phalcon\Annotations is a general purpose component that provides ease of parsing and caching annotations in PHP classes to be used in applications.

Annotations are read from docblocks in classes, methods and properties. An annotation can be placed at any position in the docblock:

  1. <?php
  2. /**
  3. * This is the class description
  4. *
  5. * @AmazingClass(true)
  6. */
  7. class Example
  8. {
  9. /**
  10. * This a property with a special feature
  11. *
  12. * @SpecialFeature
  13. */
  14. protected $someProperty;
  15. /**
  16. * This is a method
  17. *
  18. * @SpecialFeature
  19. */
  20. public function someMethod()
  21. {
  22. // ...
  23. }
  24. }

An annotation has the following syntax:

  1. /**
  2. * @Annotation-Name
  3. * @Annotation-Name(param1, param2, ...)
  4. */

Also, an annotation can be placed at any part of a docblock:

  1. <?php
  2. /**
  3. * This a property with a special feature
  4. *
  5. * @SpecialFeature
  6. *
  7. * More comments
  8. *
  9. * @AnotherSpecialFeature(true)
  10. */

The parser is highly flexible, the following docblock is valid:

  1. <?php
  2. /**
  3. * This a property with a special feature @SpecialFeature({
  4. someParameter='the value', false
  5. }) More comments @AnotherSpecialFeature(true) @MoreAnnotations
  6. **/

However, to make the code more maintainable and understandable it is recommended to place annotations at the end of the docblock:

  1. <?php
  2. /**
  3. * This a property with a special feature
  4. * More comments
  5. *
  6. * @SpecialFeature({someParameter='the value', false})
  7. * @AnotherSpecialFeature(true)
  8. */

Factory

There are many annotations adapters available (see Adapters). The one you use will depend on the needs of your application. The traditional way of instantiating such an adapter is as follows:

  1. <?php
  2. use Phalcon\Annotations\Adapter\Memory as MemoryAdapter;
  3. $reader = new MemoryAdapter();
  4. // .....

However you can also utilize the factory method to achieve the same thing:

  1. <?php
  2. use Phalcon\Annotations\Factory;
  3. $options = [
  4. 'prefix' => 'annotations',
  5. 'lifetime' => '3600',
  6. 'adapter' => 'memory', // Load the Memory adapter
  7. ];
  8. $annotations = Factory::load($options);

The Factory loader provides more flexibility when dealing with instantiating annotations adapters from configuration files.

Reading Annotations

A reflector is implemented to easily get the annotations defined on a class using an object-oriented interface:

  1. <?php
  2. use Phalcon\Annotations\Adapter\Memory as MemoryAdapter;
  3. $reader = new MemoryAdapter();
  4. // Reflect the annotations in the class Example
  5. $reflector = $reader->get('Example');
  6. // Read the annotations in the class' docblock
  7. $annotations = $reflector->getClassAnnotations();
  8. // Traverse the annotations
  9. foreach ($annotations as $annotation) {
  10. // Print the annotation name
  11. echo $annotation->getName(), PHP_EOL;
  12. // Print the number of arguments
  13. echo $annotation->numberArguments(), PHP_EOL;
  14. // Print the arguments
  15. print_r($annotation->getArguments());
  16. }

The annotation reading process is very fast, however, for performance reasons it is recommended to store the parsed annotations using an adapter. Adapters cache the processed annotations avoiding the need of parse the annotations again and again.

Phalcon\Annotations\Adapter\Memory was used in the above example. This adapter only caches the annotations while the request is running and for this reason the adapter is more suitable for development. There are other adapters to swap out when the application is in production stage.

Types of Annotations

Annotations may have parameters or not. A parameter could be a simple literal (strings, number, boolean, null), an array, a hashed list or other annotation:

  1. <?php
  2. /**
  3. * Simple Annotation
  4. *
  5. * @SomeAnnotation
  6. */
  7. /**
  8. * Annotation with parameters
  9. *
  10. * @SomeAnnotation('hello', 'world', 1, 2, 3, false, true)
  11. */
  12. /**
  13. * Annotation with named parameters
  14. *
  15. * @SomeAnnotation(first='hello', second='world', third=1)
  16. * @SomeAnnotation(first: 'hello', second: 'world', third: 1)
  17. */
  18. /**
  19. * Passing an array
  20. *
  21. * @SomeAnnotation([1, 2, 3, 4])
  22. * @SomeAnnotation({1, 2, 3, 4})
  23. */
  24. /**
  25. * Passing a hash as parameter
  26. *
  27. * @SomeAnnotation({first=1, second=2, third=3})
  28. * @SomeAnnotation({'first'=1, 'second'=2, 'third'=3})
  29. * @SomeAnnotation({'first': 1, 'second': 2, 'third': 3})
  30. * @SomeAnnotation(['first': 1, 'second': 2, 'third': 3])
  31. */
  32. /**
  33. * Nested arrays/hashes
  34. *
  35. * @SomeAnnotation({'name'='SomeName', 'other'={
  36. * 'foo1': 'bar1', 'foo2': 'bar2', {1, 2, 3},
  37. * }})
  38. */
  39. /**
  40. * Nested Annotations
  41. *
  42. * @SomeAnnotation([email protected](1, 2, 3))
  43. */

Practical Usage

Next we will explain some practical examples of annotations in PHP applications:

Cache Enabler with Annotations

Let’s pretend we’ve created the following controller and you want to create a plugin that automatically starts the cache if the last action executed is marked as cacheable. First off all, we register a plugin in the Dispatcher service to be notified when a route is executed:

  1. <?php
  2. use Phalcon\Mvc\Dispatcher as MvcDispatcher;
  3. use Phalcon\Events\Manager as EventsManager;
  4. $di['dispatcher'] = function () {
  5. $eventsManager = new EventsManager();
  6. // Attach the plugin to 'dispatch' events
  7. $eventsManager->attach(
  8. 'dispatch',
  9. new CacheEnablerPlugin()
  10. );
  11. $dispatcher = new MvcDispatcher();
  12. $dispatcher->setEventsManager($eventsManager);
  13. return $dispatcher;
  14. };

CacheEnablerPlugin is a plugin that intercepts every action executed in the dispatcher enabling the cache if needed:

  1. <?php
  2. use Phalcon\Events\Event;
  3. use Phalcon\Mvc\Dispatcher;
  4. use Phalcon\Plugin;
  5. /**
  6. * Enables the cache for a view if the latest
  7. * executed action has the annotation @Cache
  8. */
  9. class CacheEnablerPlugin extends Plugin
  10. {
  11. /**
  12. * This event is executed before every route is executed in the dispatcher
  13. */
  14. public function beforeExecuteRoute(Event $event, Dispatcher $dispatcher)
  15. {
  16. // Parse the annotations in the method currently executed
  17. $annotations = $this->annotations->getMethod(
  18. $dispatcher->getControllerClass(),
  19. $dispatcher->getActiveMethod()
  20. );
  21. // Return normally if the method doesn't have a 'Cache' annotation
  22. if (!$annotations->has('Cache')) {
  23. return true;
  24. }
  25. // The method has the annotation 'Cache'
  26. $annotation = $annotations->get('Cache');
  27. // Get the lifetime
  28. $lifetime = $annotation->getNamedParameter('lifetime');
  29. $options = [
  30. 'lifetime' => $lifetime,
  31. ];
  32. // Check if there is a user defined cache key
  33. if ($annotation->hasNamedParameter('key')) {
  34. $options['key'] = $annotation->getNamedParameter('key');
  35. }
  36. // Enable the cache for the current method
  37. $this->view->cache($options);
  38. }
  39. }

Now, we can use the annotation in a controller:

  1. <?php
  2. use Phalcon\Mvc\Controller;
  3. class NewsController extends Controller
  4. {
  5. public function indexAction()
  6. {
  7. }
  8. /**
  9. * This is a comment
  10. *
  11. * @Cache(lifetime=86400)
  12. */
  13. public function showAllAction()
  14. {
  15. $this->view->article = Articles::find();
  16. }
  17. /**
  18. * This is a comment
  19. *
  20. * @Cache(key='my-key', lifetime=86400)
  21. */
  22. public function showAction($slug)
  23. {
  24. $this->view->article = Articles::findFirstByTitle($slug);
  25. }
  26. }

Private/Public areas with Annotations

You can use annotations to tell the ACL which controllers belong to the administrative areas:

  1. <?php
  2. use Phalcon\Acl;
  3. use Phalcon\Acl\Role;
  4. use Phalcon\Acl\Resource;
  5. use Phalcon\Events\Event;
  6. use Phalcon\Plugin;
  7. use Phalcon\Mvc\Dispatcher;
  8. use Phalcon\Acl\Adapter\Memory as AclList;
  9. /**
  10. * This is the security plugin which controls that users only have access to the modules they're assigned to
  11. */
  12. class SecurityAnnotationsPlugin extends Plugin
  13. {
  14. /**
  15. * This action is executed before execute any action in the application
  16. *
  17. * @param Event $event
  18. * @param Dispatcher $dispatcher
  19. *
  20. * @return bool
  21. */
  22. public function beforeDispatch(Event $event, Dispatcher $dispatcher)
  23. {
  24. // Possible controller class name
  25. $controllerName = $dispatcher->getControllerClass();
  26. // Possible method name
  27. $actionName = $dispatcher->getActiveMethod();
  28. // Get annotations in the controller class
  29. $annotations = $this->annotations->get($controllerName);
  30. // The controller is not private? Continue normally
  31. if (!$annotations->getClassAnnotations()->has('Private')) {
  32. return true;
  33. }
  34. // Check if the session variable is active?
  35. if ($this->session->get('auth')) {
  36. return true;
  37. }
  38. // The user is no logged redirect to login
  39. $dispatcher->forward(
  40. [
  41. 'controller' => 'session',
  42. 'action' => 'login',
  43. ]
  44. );
  45. return false;
  46. }
  47. }

Annotations Adapters

This component makes use of adapters to cache or no cache the parsed and processed annotations thus improving the performance or providing facilities to development/testing:

ClassDescription
Phalcon\Annotations\Adapter\MemoryThe annotations are cached only in memory. When the request ends the cache is cleaned reloading the annotations in each request. This adapter is suitable for a development stage
Phalcon\Annotations\Adapter\FilesParsed and processed annotations are stored permanently in PHP files improving performance. This adapter must be used together with a bytecode cache.
Phalcon\Annotations\Adapter\ApcuParsed and processed annotations are stored permanently in the APCu cache improving performance. This is the fastest adapter

Implementing your own adapters

The Phalcon\Annotations\AdapterInterface interface must be implemented in order to create your own annotations adapters or extend the existing ones.

External Resources