Anotaciones

Resumen
Phalcon introdujo el primer componente analizador de anotaciones escrito en C para PHP. The Phalcon\Annotations
namespace contains general purpose components that offers an easy way to parse and cache annotations in PHP applications.
Uso
Las anotaciones son leídas desde docblocks en clases, métodos y propiedades. Una anotación puede colocarse en cualquier posición del docblock:
<?php
/**
* #01
*
* @AmazingClass(true)
*/
class Example
{
/**
* #02
*
* @SpecialFeature
*/
protected $someProperty;
/**
* #03
*
* @SpecialFeature
*/
public function someMethod()
{
// ...
}
}
Legend
01: This is the class description
02: This a property with a special feature
03: This is a method
Una anotación tiene la siguiente sintaxis:
/**
* @Annotation-Name
* @Annotation-Name(param1, param2, ...)
*/
También, una anotación se puede colocar en cualquier parte de un docblock:
<?php
/**
* #01
*
* @SpecialFeature
*
* #02
*
* @AnotherSpecialFeature(true)
*/
Legend
01: This is a property with a special feature
02: More comments
El analizador es altamente flexible, el siguiente docblock es válido:
<?php
/**
* Esta es una propiedad con una característica especial @SpecialFeature({
someParameter='the value', false
}) Más comentarios @AnotherSpecialFeature(true) @MoreAnnotations
**/
Sin embargo, para hacer el código más mantenible y comprensible se recomienda colocar las anotaciones al final del docblock:
<?php
/**
* #01
* #02
*
* @SpecialFeature({someParameter='the value', false})
* @AnotherSpecialFeature(true)
*/
Legend
01: This is a property with a special feature
02: More comments
Un ejemplo para un modelo es:
<?php
use Phalcon\Mvc\Model;
/**
* #01
*
* #02
*
* @Source('co_customers');
* @HasMany("cst_id", "Invoices", "inv_cst_id")
*/
class Customers extends Model
{
/**
* @Primary
* @Identity
* @Column(type="integer", nullable=false, column="cst_id")
*/
public $id;
/**
* @Column(type="string", nullable=false, column="cst_name_first")
*/
public $nameFirst;
/**
* @Column(type="string", nullable=false, column="cst_name_last")
*/
public $nameLast;
}
Legend
01: Customers
02: Represents a customer record
Tipos
Las anotaciones pueden tener parámetros o no. Un parámetro podría ser un literal simple (cadenas
, número
, booleano
, null
), un vector
, una list codificada u otra anotación:
Anotación Simple
/**
* @SomeAnnotation('hello', 'world', 1, 2, 3, false, true)
*/
Anotación con parámetros
/**
* @SomeAnnotation(first='hello', second='world', third=1)
* @SomeAnnotation(first: 'hello', second: 'world', third: 1)
*/
Anotación con parámetros nombrados
/**
* @SomeAnnotation([1, 2, 3, 4])
* @SomeAnnotation({1, 2, 3, 4})
*/
Pasando un vector
/**
* @SomeAnnotation({first=1, second=2, third=3})
* @SomeAnnotation({'first'=1, 'second'=2, 'third'=3})
* @SomeAnnotation({'first': 1, 'second': 2, 'third': 3})
* @SomeAnnotation(['first': 1, 'second': 2, 'third': 3])
*/
Pasando una codificación como parámetro
/**
* @SomeAnnotation({'name'='SomeName', 'other'={
* 'foo1': 'bar1', 'foo2': 'bar2', {1, 2, 3},
* }})
*/
Vectores/Codificaciones anidadas
/**
* @SomeAnnotation(first=@AnotherAnnotation(1, 2, 3))
*/
Anotaciones Anidadas
Adaptadores
Este componente hace uso de adaptadores para cachear o no las anotaciones analizadas y procesadas mejorando el rendimiento:
Apcu
Phalcon\Annotations\Adapter\Apcu stores the parsed and processed annotations using the APCu cache. Este adaptador es adecuado para sistemas en producción. Sin embargo, una vez que el servidor se reinicia, el caché se limpiará y tendrá que reconstruirse. The adapter accepts two parameters in the constructor’s options
array:
prefix
- the prefix for the key stored
lifetime
- the cache lifetime
<?php
use Phalcon\Annotations\Adapter\Apcu;
$adapter = new Apcu(
[
'prefix' => 'my-prefix',
'lifetime' => 3600,
]
);
Internamente, el adaptador almacena los datos prefijando cada clave con _PHAN
. Este ajuste no se puede cambiar. Sin embargo, esto le da la opción de escanear APCu en busca de claves que tengan el prefijo _PHAN
y limpiarlas si es necesario.
<?php
use APCuIterator;
$result = true;
$pattern = "/^_PHAN/";
$iterator = new APCuIterator($pattern);
if (true === is_object($iterator)) {
return false;
}
foreach ($iterator as $item) {
if (true !== apcu_delete($item["key"])) {
$result = false;
}
}
return $result;
Memory
Phalcon\Annotations\Adapter\Memory stores the parsed and processed annotations in memory. Este adaptador es adecuado para sistemas en desarrollo. The cache is rebuilt on every request, and therefore can immediately reflect changes, while developing your application.
<?php
use Phalcon\Annotations\Adapter\Memory;
$adapter = new Memory();
Flujo (Stream)
Phalcon\Annotations\Adapter\Stream stores the parsed and processed annotations in a file on the server. This adapter can be used in production systems, but it will increase the I/O since for every request the annotations cache files will need to be read from the file system. The adapter accepts one parameter in the constructor’s $options
array:
annotationsDir
- the directory to store the annotations cache
<?php
use Phalcon\Annotations\Adapter\Stream;
$adapter = new Stream(
[
'annotationsDir' => '/app/storage/cache/annotations',
]
);
If there is a problem with storing the data in the folder due to permissions or any other reason, a Phalcon\Annotations\Exception will be thrown.
Personalizado
Phalcon\Annotations\Adapter\AdapterInterface is available. Ampliar esta interfaz le permitirá crear sus propios adaptadores.
Fábrica (Factory)
newInstance
Podemos crear fácilmente una clase adaptador de anotaciones usando la palabra clave new
. However, Phalcon offers the Phalcon\Annotations\AnnotationsFactory class, so that developers can easily instantiate annotations adapters. La fábrica aceptará un vector de opciones que a su vez se usarán para instanciar la clase adaptador necesaria. The factory always returns a new instance that implements the Phalcon\Annotations\Adapter\AdapterInterface. Los nombre de los adaptadores preconfigurados son:
El ejemplo siguiente muestra como puede crear un adaptador de anotaciones Apcu:
<?php
use Phalcon\Annotations\AnnotationsFactory;
$options = [
'prefix' => 'my-prefix',
'lifetime' => 3600,
];
$factory = new AdapterFactory();
$apcu = $factory->newInstance('apcu', $options);
load
The Phalcon\Annotations\AnnotationsFactory also offers the load
method, which accepts a configuration object. Este objeto puede ser un vector o un objeto Phalcon\Config, con directivas que se usarán para configurar el adaptador. El objeto requiere el elemento adapter
, así como el elemento options
con las directivas necesarias.
<?php
use Phalcon\Annotations\AnnotationsFactory;
$options = [
'adapter' => 'apcu',
'options' => [
'prefix' => 'my-prefix',
'lifetime' => 3600,
]
];
$factory = new AdapterFactory();
$apcu = $factory->load($options);
Leyendo anotaciones
Se implementa un reflector para obtener fácilmente las anotaciones definidas en una clase usando una interfaz orientada a objetos. Phalcon\Annotations\Reader is used along with Phalcon\Annotations\Reflection. They also utilize the collection Phalcon\Annotations\Collection that contains Phalcon\Annotations\Annotation objects once the annotations are parsed.
<?php
use Phalcon\Annotations\Adapter\Memory;
$adapter = new Memory();
$reflector = $adapter->get('Invoices');
$annotations = $reflector->getClassAnnotations();
foreach ($annotations as $annotation) {
echo $annotation->getName(), PHP_EOL;
echo $annotation->numberArguments(), PHP_EOL;
print_r($annotation->getArguments());
}
En el ejemplo anterior primero creamos el adaptador de anotaciones de memoria. Luego se llama a get
para cargar las anotaciones de la clase Invoices
. The getClassAnnotations
will return a Phalcon\Annotations\Collection class. Iteramos sobre la colección e imprimimos el nombre (getName
), el número de argumentos (numberArguments
) y luego imprimimos todos los argumentos (getArguments
) por pantalla.
The annotation reading process is very fast, however, for performance reasons it is recommended to store the parsed annotations using an adapter to reduce unnecessary CPU cycles for parsing.
Excepciones
Any exceptions thrown in the Phalcon\Annotations
namespace will be of type Phalcon\Annotations\Exception. Puede usar estas excepciones para capturar selectivamente sólo las excepciones lanzadas desde este componente.
<?php
use Phalcon\Annotations\Adapter\Memory;
use Phalcon\Annotations\Exception;
use Phalcon\Mvc\Controller;
class IndexController extends Controller
{
public function index()
{
try {
$adapter = new Memory();
$reflector = $adapter->get('Invoices');
$annotations = $reflector->getClassAnnotations();
foreach ($annotations as $annotation) {
echo $annotation->getExpression('unknown-expression');
}
} catch (Exception $ex) {
echo $ex->getMessage();
}
}
}
Ejemplos
Acceso basado en controlador
Puede usar anotaciones para definir qué áreas se controlan por la ACL. Podemos hacer esto registrando un plugin en el gestor de eventos que escucha el evento beforeExceuteRoute
, o simplemente implementar el método en nuestro controlador base.
Primero necesitamos configurar el gestor de anotaciones en nuestro contenedor DI:
<?php
use Phalcon\Di\FactoryDefault;
use Phalcon\Annotations\Adapter\Apcu;
$container = new FactoryDefault();
$container->set(
'annotations',
function () {
return new Apcu(
[
'lifetime' => 86400
]
);
}
);
y ahora en el controlador base implementamos el método beforeExceuteRoute
:
<?php
namespace MyApp\Controllers;
use Phalcon\Annotations\Adapter\Apcu;
use Phalcon\Events\Event;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\Controller
use MyApp\Components\Auth;
/**
* @property Apcu $annotations
* @property Auth $auth
*/
class BaseController extends Controller
{
/**
* @param Event $event
* @param Dispatcher $dispatcher
*
* @return bool
*/
public function beforeExceuteRoute(
Dispatcher $dispatcher
) {
$controllerName = $dispatcher->getControllerClass();
$annotations = $this
->annotations
->get($controllerName)
;
$exists = $annotations
->getClassAnnotations()
->has('Private')
;
if (true !== $exists) {
return true;
}
if (true === $this->auth->isLoggedIn()) {
return true;
}
$dispatcher->forward(
[
'controller' => 'session',
'action' => 'login',
]
);
return false;
}
}
NOTE You can also implement the above to a listener and use the beforeDispatch
event if you wish.
y en nuestros controladores podemos especificar:
<?php
namespace MyApp\Controllers;
use MyApp\Controllers\BaseController;
/**
* @Private(true)
*/
class Invoices extends BaseController
{
public function indexAction()
{
}
}
Acceso basado en grupo
Podría querer ampliar lo anterior y ofrecer un control de acceso más granular para su aplicación. Para esto, también usaremos beforeExecuteRoute
en el controlador pero añadiremos los metadatos de acceso en cada acción. If we need a specific controller to be locked we can also use the initialize
method.
Primero necesitamos configurar el gestor de anotaciones en nuestro contenedor DI:
<?php
use Phalcon\Di\FactoryDefault;
use Phalcon\Annotations\Adapter\Apcu;
$container = new FactoryDefault();
$container->set(
'annotations',
function () {
return new Apcu(
[
'lifetime' => 86400
]
);
}
);
y ahora en el controlador base implementamos el método beforeExceuteRoute
:
<?php
namespace MyApp\Controllers;
use Phalcon\Annotations\Adapter\Apcu;
use Phalcon\Events\Event;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\Controller;
use MyApp\Components\Auth;
/**
* @property Apcu $annotations
* @property Auth $auth
*/
class BaseController extends Controller
{
/**
* @param Event $event
* @param Dispatcher $dispatcher
*
* @return bool
*/
public function beforeExceuteRoute(
Dispatcher $dispatcher
) {
$controllerName = $dispatcher->getControllerClass();
$actionName = $dispatcher->getActionName()
. 'Action';
$data = $this
->annotations
->getMethod($controllerName, $actionName)
;
$access = $data->get('Access');
$aclGroups = $access->getArguments();
$user = $this->acl->getUser();
$groups = $user->getRelated('groups');
$userGroups = [];
foreach ($groups as $group) {
$userGroups[] = $group->grp_name;
}
$allowed = array_intersect($userGroups, $aclGroups);
$allowed = (count($allowed) > 0);
if (true === $allowed) {
return true;
}
$dispatcher->forward(
[
'controller' => 'session',
'action' => 'login',
]
);
return false;
}
}
y en nuestros controladores:
<?php
namespace MyApp\Controllers;
use MyApp\Controllers\BaseController;
/**
* @Private(true)
*/
class Invoices extends BaseController
{
/**
* @Access(
* 'Administrators',
* 'Accounting',
* 'Users',
* 'Guests'
* )
*/
public function indexAction()
{
}
/**
* @Access(
* 'Administrators',
* 'Accounting',
* )
*/
public function listAction()
{
}
/**
* @Access(
* 'Administrators',
* 'Accounting',
* )
*/
public function viewAction()
{
}
}
Recursos Adicionales