Phalcon introdujo el primer componente analizador de anotaciones escrito en C para PHP. El espacio de nombres Phalcon\Annotations
contiene componentes de propósito general que ofrecen una forma fácil de analizar y cachear anotaciones en las aplicaciones PHP.
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
/**
* This is the class description
*
* @AmazingClass(true)
*/
class Example
{
/**
* This a property with a special feature
*
* @SpecialFeature
*/
protected $someProperty;
/**
* This is a method
*
* @SpecialFeature
*/
public function someMethod()
{
// ...
}
}
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
/**
* Esta es una propiedad con una característica especial
*
* @SpecialFeature
*
* Mas comentarios
*
* @AnotherSpecialFeature(true)
*/
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
/**
* Esta es una propiedad con una característica especial
* Más comentarios
*
* @SpecialFeature({someParameter='the value', false})
* @AnotherSpecialFeature(true)
*/
Un ejemplo para un modelo es:
<?php
use Phalcon\Mvc\Model;
/**
* Customers
*
* Represents a customer record
*
* @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;
}
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:
/**
* @SomeAnnotation
*/
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([email protected](1, 2, 3))
*/
Anotaciones Anidadas
Este componente hace uso de adaptadores para cachear o no las anotaciones analizadas y procesadas mejorando el rendimiento:
Adaptador | Descripción |
---|---|
Phalcon\Annotations\Adapter\Apcu | Usa APCu para almacenar las anotaciones analizadas y procesadas (producción) |
Phalcon\Annotations\Adapter\Memory | Usa la memoria para almacenar anotaciones (desarrollo) |
Phalcon\Annotations\Adapter\Stream | Usa un flujo de archivo para almacenar anotaciones. Se debe usar con un caché de byte-code. |
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 storedlifetime
- 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;
Phalcon\Annotations\Adapter\Memory stores the parsed and processed annotations in memory. Este adaptador es adecuado para sistemas en desarrollo. El caché es reconstruido en cada petición, y por lo tanto puede reflejar cambios inmediatamente mientras se desarrolla la aplicación.
<?php
use Phalcon\Annotations\Adapter\Memory;
$adapter = new Memory();
Phalcon\Annotations\Adapter\Stream stores the parsed and processed annotations in a file on the server. Este adaptador se puede usar en sistemas de producción pero incrementará las E/S ya que para cada petición se necesita leer los ficheros de caché de las anotaciones desde el sistema de ficheros. 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.
Phalcon\Annotations\Adapter\AdapterInterface is available. Ampliar esta interfaz le permitirá crear sus propios adaptadores.
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:
Nombre | Adaptador |
---|---|
apcu |
Phalcon\Annotations\Adapter\Apcu |
memory |
Phalcon\Annotations\Adapter\Memory |
stream |
Phalcon\Annotations\Adapter\Stream |
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);
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.
El proceso de lectura de anotaciones es muy rápido, sin embargo, por razones de rendimiento se recomienda almacenar las anotaciones analizadas usando un adaptador para reducir ciclos de CPU innecesarios para su análisis.
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();
}
}
}
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()
{
}
}