Os behaviors são um modo de organizar e habilitar o reuso de lógica da camada do Model (Modelo). Conceitualmente, eles são semelhantes a traits. No entanto, os behaviors são implementados como classes separadas. Isso permite que eles se connectem aos callbacks de ciclo de vida que os modelos emitem, ao mesmo tempo que fornecem recursos semelhantes a traits.
Os behaviors fornecem uma maneira conveniente de compor comportamentos que são
comuns em vários modelos. Por exemplo, CakePHP inclui um TimestampBehavior
.
Vários modelos irão querer campos de timestamp, e a lógica para gerenciar esses
campos não é especifica para nenhum modelo. São esses tipos de cenários em que
os behaviors são perfeitos.
Behaviors fornecem uma maneira fácil de criar partes de lógica horizontalmente reutilizáveis relacionadas às classes de tabela. Você pode estar se perguntando por que os behaviors são classes regulares e não traits. O principal motivo para isso é event listeners. Enquanto as traits permitiriam partes reutilizáveis de lógica, eles complicariam o uso de eventos.
Para adicionar um behavior à sua tabela, você pode chamar o método addBehavior()
.
Geralmente o melhor lugar para fazer isso é no método initialize()
:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
}
}
Como acontece com as associações, você pode usar sintaxe plugin e fornecer opções de configuração adicionais:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp', [
'events' => [
'Model.beforeSave' => [
'created_at' => 'new',
'modified_at' => 'always'
]
]
]);
}
}
Nos exemplos a seguir, criaremos um bem simples SluggableBehavior
. Esse
behavior nos permitirá preencher um campo slug com o resultado de
Text::slug()
baseado em outro campo.
Antes de criar nosso behavior, devemos entender as convensão para behaviors:
Behavior estão localizados em src/Model/Behavior, ou
MyPlugin\Model\Behavior
.
Classes de Behavior devem estar no namespace App\Model\Behavior
, ou
no namespace MyPlugin\Model\Behavior
.
Classes de Behavior terminam com Behavior
.
Behaviors estendem Cake\ORM\Behavior
.
Para criar nosso behavior sluggable. Coloque o seguinte em src/Model/Behavior/SluggableBehavior.php:
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
class SluggableBehavior extends Behavior
{
}
Semelhante a classes de tabela (table classes), behaviors também tem um método
initialize()
onde você pode colocar o código de inicialização do seu
behavior, se necessário:
public function initialize(array $config)
{
// Algum código de inicialização aqui
}
Agora nós podemos adicionar esse behavior a uma de nossas classes de tabela
(table classes). Neste exemplo, nós usaremos um ArticlesTable
, pois artigos
normalmente tem propriedades de slug para criar URLs amigáveis:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Sluggable');
}
}
Nosso novo behavior não faz muita coisa no momento. Em seguida, iremos adicionar um método de mixin e um event listener para que, quando salvarmos entidades nós podemos realizar slug automaticamento de um campo.
Qualquer método público definido em um behavior será adicionado como um método ‘mixin’ no objeto de tabela que está anexado. Se você anexar dois behavior que fornecem os mesmos métodos uma exceção será lançada. Se um behavior fornecer o mesmo método que uma classe de tabela, o método de behavior não será chamado pela tabela. Os métodos de mixin receberão exatamente os mesmo argumentos fornecidos à tabela. Por exemplo, se o nosso SluggableBehavior definiu o seguinte método:
public function slug($value)
{
return Text::slug($value, $this->_config['replacement']);
}
Isto poderia ser invocado usando:
$slug = $articles->slug('My article name');
Ao criar behaviors, pode haver situações em que você não deseja expor métodos
públicos como métodos de ‘mixin’. Nesses casos, você pode usar a chave de
configuração implementedMethods
para renomear ou excluir métodos de ‘mixin’.
Por exemplo, se quisermos prefixar nosso método slug(), nós poderíamos fazer o
seguinte:
protected $_defaultConfig = [
'implementedMethods' => [
'superSlug' => 'slug',
]
];
Aplicando essa configuração deixará slug()
como não callable, no entanto,
ele adicionará um método ‘mixin’ superSlug
à tabela. Notavelmente, se nosso
behavior implementasse outros métodos públicos eles não estariam disponíveis
como métodos ‘mixin’ com a configuração acima.
Desde que os métodos expostos são decididos por configuração, você também pode renomear/remover métodos de ‘mixin’ ao adicionar um behavior à tabela. Por exemplo:
// In a table's initialize() method.
$this->addBehavior('Sluggable', [
'implementedMethods' => [
'superSlug' => 'slug',
]
]);
Agora que nosso behavior tem um método de ‘mixin’ para campos de slug, nós podemos implementar um listener de callback para automaticamente gerar slug de um campo quando entidades são salvas. Nós também iremos modificar nosso método de slug para aceitar uma entidade ao invéz de apenas um valor simples. Nosso behavior agora deve parecer com:
namespace App\Model\Behavior;
use ArrayObject;
use Cake\Datasource\EntityInterface;
use Cake\Event\Event;
use Cake\ORM\Behavior;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\Utility\Text;
class SluggableBehavior extends Behavior
{
protected $_defaultConfig = [
'field' => 'title',
'slug' => 'slug',
'replacement' => '-',
];
public function slug(Entity $entity)
{
$config = $this->config();
$value = $entity->get($config['field']);
$entity->set($config['slug'], Text::slug($value, $config['replacement']));
}
public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
{
$this->slug($entity);
}
}
O código acima mostra alguns recursos interessantes de behaviors:
Behaviors podem definir métodos de callback definindo métodos que seguem as convensões de Lifecycle Callbacks.
Behaviors podem definir uma propriedade de configuração padrão. Essa propriedade é mesclada com as substituições quando um behavior é anexado à tabela.
Para evitar que o processo de gravação (save) continue, simplesmente pare a propagação do evento em seu callback:
public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
{
if (...) {
$event->stopPropagation();
return;
}
$this->slug($entity);
}
Agora que somos capazes de salvar artigos com valores de slug, nós devemos
implementar um método de ‘finder’(busca) para que possamos obter artigos por
seus slugs. Em métodos de ‘finder’(busca) de behaviors, use as mesmas convenções
que Personalizando Metódos de Consulta usa. Nosso método find('slug')
pareceria com:
public function findSlug(Query $query, array $options)
{
return $query->where(['slug' => $options['slug']]);
}
Uma vez que nosso behavior tem o método acima nós podemos chamá-lo:
$article = $articles->find('slug', ['slug' => $value])->first();
Ao criar behaviors, pode haver situações em que você não deseja expor métodos
finder, ou você precisa renomear o finder para evitar métodos duplicados.
Nesses casos, você pode usar a chave de configuração implementedFinders
para renomear ou excluir métodos finder. Por exemplo, se quisermos renomear
nosso método find(slug)
, nós poderíamos fazer o seguinte:
protected $_defaultConfig = [
'implementedFinders' => [
'slugged' => 'findSlug',
]
];
Aplicando esta configuração fará com que find('slug')
dispare um erro. No entanto,
ela deixara disponível find('slugged')
. Notavelmente, se nosso behavior
implementasse outros métodos finder, eles não estariam disponíveis, pois não
estão incluídos na configuração.
Desde que os métodos expostos são decididos por configuração, você também pode renomear/remover métodos finder ao adicionar um behavior à tabela. Por exemplo:
// No método initialize() da tabela.
$this->addBehavior('Sluggable', [
'implementedFinders' => [
'slugged' => 'findSlug',
]
]);
Behaviors podem definir lógica para como os campos personalizados que eles
fornecem são arrumados (marshalled) implementando a Cake\ORM\PropertyMarshalInterface
.
Esta interface requer um único método para ser implementado:
public function buildMarshalMap($marshaller, $map, $options)
{
return [
'custom_behavior_field' => function ($value, $entity) {
// Transform the value as necessary
return $value . '123';
}
];
}
O TranslateBehavior
tem uma implementação não trivial desta interface
à qual você pode querer referir.
Novo na versão 3.3.0: A capacidade de behaviors para participar do processo de marshalling foi adicionada em in 3.3.0
Para remover um behavior da sua tabela, você pode chamar o método removeBehavior()
:
// Remove the loaded behavior
$this->removeBehavior('Sluggable');
Uma vez que você anexou behaviors à sua instância da Table você pode conferir
os behaviors carregados ou acessar behaviors específicos usando o BehaviorRegistry
:
// Verifica quais behaviors estão carregados
$table->behaviors()->loaded();
// Verifica se um behavior especifico está carregado
// Lembre-se de omitir o prefixo de plugin.
$table->behaviors()->has('CounterCache');
// Obtem um behavior carregado
// Lembre-se de omitir o prefixo de plugin
$table->behaviors()->get('CounterCache');
Para modificar a configuração de um behavior já carregado, você pode combinar
o comando BehaviorRegistry::get
com o comando config
fornecido pela trait
InstanceConfigTrait
.
Por exemplo, se uma classe pai (por exemplo uma, AppTable
) carregasse o
behavior Timestamp
, você poderia fazer o seguinte para adicionar, modificar
ou remover as configurações do behavior. Nesse caso, nós adicionaremos um evento
que queremos que o Timestamp responda:
namespace App\Model\Table;
use App\Model\Table\AppTable; // similar to AppController
class UsersTable extends AppTable
{
public function initialize(array $options)
{
parent::initialize($options);
// e.g. if our parent calls $this->addBehavior('Timestamp');
// and we want to add an additional event
if ($this->behaviors()->has('Timestamp')) {
$this->behaviors()->get('Timestamp')->config([
'events' => [
'Users.login' => [
'last_login' => 'always'
],
],
]);
}
}
}