Models (Modelos) ¶
Os models (modelos) fazem parte da arquitetura MVC. Eles representam os dados, as regras e a lógica de negócio.
Você pode criar uma classe model estendendo de yii\base\Model ou de seus filhos. A classe base yii\base\Model suporta muitos recursos úteis:
- Atributos: representa os dados de negócio e podem ser acessados normalmente como uma propriedade de objeto ou como um elemento de array;
- Labels dos atributos: especifica os labels de exibição dos atributos;
- Atribuição em massa: suporta popular vários atributos em uma única etapa;
- Regras de validação: garante que os dados de entrada sejam baseadas nas regras de validação que foram declaradas;
- Data Exporting: permite que os dados de model a serem exportados em array possuam formatos personalizados.
A classe Model
também é a classe base para models mais avançados, como o Active Record.
Por favor, consulte a documentação relevante para mais detalhes sobre estes models mais avançados.
Informação: Você não é obrigado basear suas classe model em yii\base\Model. No entanto, por existir muitos componentes do Yii construídos para suportar o yii\base\Model, normalmente é a classe base preferível para um model.
Atributos ¶
Os models representam dados de negócio por meio de atributos. Cada atributo é uma propriedade publicamente acessível de um model. O método yii\base\Model::attributes() especifica quais atributos de uma classe model possuirá.
Você pode acessar um atributo como fosse uma propriedade normal de um objeto:
$model = new \app\models\ContactForm;
// "name" é um atributo de ContactForm
$model->name = 'example';
echo $model->name;
Você também pode acessar os atributos como elementos de um array, graças ao suporte de ArrayAccess e ArrayIterator pelo yii\base\Model:
$model = new \app\models\ContactForm;
// acessando atributos como elementos de array
$model['name'] = 'example';
echo $model['name'];
// iterando sobre os atributos
foreach ($model as $name => $value) {
echo "$name: $value\n";
}
Definindo Atributos ¶
Por padrão, se a classe model estender diretamente de yii\base\Model, todas
as suas variáveis públicas e não estáticas serão atributos. Por exemplo, a classe
model ContactForm
a seguir possui quatro atributos: name
, email
, subject
e body
. O model ContactForm
é usado para representar os dados de entrada obtidos
a partir de um formulário HTML.
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
Você pode sobrescrever o método yii\base\Model::attributes() para definir
atributos de uma forma diferente. Este método deve retornar os nomes dos atributos
em um model. Por exemplo, o yii\db\ActiveRecord faz com que o método retorne
os nomes das colunas da tabela do banco de dados como nomes de atributos.
Observe que também poderá sobrescrever os métodos mágicos tais como __get()
e
__set()
, para que os atributos poderem ser acessados como propriedades normais
de objetos.
Labels dos Atributos ¶
Ao exibir valores ou obter dados de entrada dos atributos, muitas vezes é necessário
exibir alguns labels associados aos atributos. Por exemplo, dado um atributo chamado
firstName
, você pode querer exibir um label First Name
que é mais amigável
quando exibido aos usuários finais como em formulários e mensagens de erro.
Você pode obter o label de um atributo chamando o método yii\base\Model::getAttributeLabel(). Por exemplo,
$model = new \app\models\ContactForm;
// displays "Name"
echo $model->getAttributeLabel('name');
Por padrão, os labels dos atributos automaticamente serão gerados com os nomes dos
atributos. Isto é feito pelo método yii\base\Model::generateAttributeLabel().
Ele transforma os nomes camel-case das variáveis em várias palavras, colocando em
caixa alta a primeira letra de cada palavra. Por exemplo, username
torna-se
Username
, enquanto firstName
torna-se First Name
.
Se você não quiser usar esta geração automática do labels, poderá sobrescrever o método yii\base\Model::attributeLabels() declarando explicitamente os labels dos atributos. Por exemplo,
namespace app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
public function attributeLabels()
{
return [
'name' => 'Your name',
'email' => 'Your email address',
'subject' => 'Subject',
'body' => 'Content',
];
}
}
Para aplicações que suportam vários idiomas, você pode querer traduzir os labels dos atributos. Isto também é feito no método attributeLabels(), conforme o exemplo a seguir:
public function attributeLabels()
{
return [
'name' => \Yii::t('app', 'Your name'),
'email' => \Yii::t('app', 'Your email address'),
'subject' => \Yii::t('app', 'Subject'),
'body' => \Yii::t('app', 'Content'),
];
}
Você pode até definir condicionalmente os labels dos atributos. Por exemplo, baseado no cenário que o model estiver utilizando, você pode retornar diferentes labels para o mesmo atributo.
Informação: Estritamente falando, os labels dos atributos fazem parte das views (visões). Mas ao declarar os labels em models (modelos), frequentemente tornam-se mais convenientes e podem resultar um código mais limpo e reutilizável.
Cenários ¶
Um model (modelo) pode ser usado em diferentes cenários. Por exemplo, um model
User
pode ser usado para obter dados de entrada de login, mas também pode ser
usado com a finalidade de registrar o usuário. Em diferentes cenários, um model
pode usar diferentes regras e lógicas de negócio. Por exemplo, um atributo email
pode ser obrigatório durante o cadastro do usuário, mas não durante ao login.
Um model (modelo) usa a propriedade yii\base\Model::scenario para identificar
o cenário que está sendo usado.
Por padrão, um model (modelo) suporta apenas um único cenário chamado default
.
O código a seguir mostra duas formas de definir o cenário de um model (modelo):
// o cenário é definido pela propriedade
$model = new User;
$model->scenario = 'login';
// o cenário é definido por meio de configuração
$model = new User(['scenario' => 'login']);
Por padrão, os cenários suportados por um model (modelo) são determinados pelas regras de validação declaradas no próprio model (modelo). No entanto, você pode personalizar este comportamento sobrescrevendo o método yii\base\Model::scenarios(), conforme o exemplo a seguir:
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
}
Informação: Nos exemplos anteriores, as classes model (model) são estendidas de yii\db\ActiveRecord por usarem diversos cenários para auxiliarem as classes Active Record classes.
O método scenarios()
retorna um array cujas chaves são os nomes dos cenários e
os valores que correspondem aos active attributes (atributo ativo). Um atributo
ativo podem ser atribuídos em massa e é sujeito a
validação. No exemplo anterior, os atributos username
e
password
são ativos no cenário login
; enquanto no cenário register
, além
dos atribitos username
e password
, o atributo email
passará a ser ativo.
A implementação padrão do método scenarios()
retornará todos os cenários encontrados
nas regras de validação declaradas no método yii\base\Model::rules(). Ao
sobrescrever o método scenarios()
, se quiser introduzir novos cenários, além
dos cenários padrão, poderá escrever um código conforme o exemplo a seguir:
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['login'] = ['username', 'password'];
$scenarios['register'] = ['username', 'email', 'password'];
return $scenarios;
}
}
O recurso de cenários são usados principalmente para validação e para atribuição em massa. Você pode, no entanto, usá-lo para outros fins. Por exemplo, você pode declarar diferentes labels para os atributos baseados no cenário atual.
Regras de Validação ¶
Quando os dados para um model (modelo) são recebidos de usuários finais, devem ser
validados para garantir que satisfazem as regras (regras de validação, também
conhecidos como regras de negócio). Por exemplo, considerando um model (modelo)
ContactForm
, você pode querer garantir que todos os atributos não sejam vazios e
que o atributo email
contenha um e-mail válido.
Se o valor de algum atributo não satisfizer a regra de negócio correspondente,
mensagens apropriadas de erros serão exibidas para ajudar o usuário a corrigi-los.
Você pode chamar o método yii\base\Model::validate() para validar os dados
recebidos. O método usará as regras de validação declaradas em yii\base\Model::rules()
para validar todos os atributos relevantes. Se nenhum erro for encontrado, o método
retornará true
. Caso contrário, o método irá manter os erros na propriedade
yii\base\Model::errors e retornará false
. Por exemplo,
$model = new \app\models\ContactForm;
// os atributos do model serão populados pelos dados fornecidos pelo usuário
$model->attributes = \Yii::$app->request->post('ContactForm');
if ($model->validate()) {
// todos os dados estão válidos
} else {
// a validação falhou: $errors é um array contendo as mensagens de erro
$errors = $model->errors;
}
Para declarar as regras de validação em um model (modelo), sobrescreva o método
yii\base\Model::rules() retornando as regras que os atributos do model (modelo)
devem satisfazer. O exemplo a seguir mostra as regras de validação sendo declaradas
no model (modelo) ContactForm
:
public function rules()
{
return [
// os atributos name, email, subject e body são obrigatórios
[['name', 'email', 'subject', 'body'], 'required'],
// o atributo email deve ter um e-mail válido
['email', 'email'],
];
}
Uma regra pode ser usada para validar um ou vários atributos e, um atributo pode ser validado por uma ou várias regras. Por favor, consulte a seção Validação de Dados para mais detalhes sobre como declarar regras de validação.
Às vezes, você pode querer que uma regra se aplique apenas em determinados
cenários. Para fazer isso, você pode especificar a propriedade
on
de uma regra, como o seguinte:
public function rules()
{
return [
// os atributos username, email e password são obrigatórios no cenario "register"
[['username', 'email', 'password'], 'required', 'on' => 'register'],
// os atributos username e password são obrigatórios no cenario "login"
[['username', 'password'], 'required', 'on' => 'login'],
];
}
Se você não especificar a propriedade on
, a regra será aplicada em todos os
cenários. Uma regra é chamada de active rule (regra ativa), se ela puder ser
aplicada no yii\base\Model::scenario atual.
Um atributo será validado, se e somente se, for um atributo ativo declarado no
método scenarios()
e estiver associado a uma ou várias regras declaradas no método rules()
.
Atribuição em Massa ¶
Atribuição em massa é a forma conveniente para popular um model (modelo) com os
dados de entrada do usuário usando uma única linha de código.
Ele popula os atributos de um model (modelo) atribuindo os dados de entrada diretamente
na propriedade yii\base\Model::$attributes. Os dois códigos a seguir são
equivalentes, ambos tentam atribuir os dados do formulário enviados pelos usuários
finais para os atributos do model (modelo) ContactForm
. Evidentemente, a
primeira forma, que utiliza a atribuição em massa, é a mais limpa e o menos
propenso a erros do que a segunda forma:
$model = new \app\models\ContactForm;
$model->attributes = \Yii::$app->request->post('ContactForm');
$model = new \app\models\ContactForm;
$data = \Yii::$app->request->post('ContactForm', []);
$model->name = isset($data['name']) ? $data['name'] : null;
$model->email = isset($data['email']) ? $data['email'] : null;
$model->subject = isset($data['subject']) ? $data['subject'] : null;
$model->body = isset($data['body']) ? $data['body'] : null;
Atributos Seguros ¶
A atribuição em massa só se aplica aos chamados safe attributes (atributos seguros),
que são os atributos listados no yii\base\Model::scenarios() para o
yii\base\Model::scenario atual de um model (modelo).
Por exemplo, se o model (modelo) User
declarar o cenário como o código a seguir,
quando o cenário atual for login
, apenas os atributos username
e password
podem ser atribuídos em massa. Todos os outros atributos permanecerão inalterados.
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
Informação: A razão da atribuição em massa só se aplicar para os atributos seguros é para que você tenha o controle de quais atributos podem ser modificados pelos dados dos usuário finais. Por exemplo, se o model (modelo) tiver um atributo
permission
que determina a permissão atribuída ao usuário, você gostará que apenas os administradores possam modificar este atributo através de uma interface backend.
Como a implementação do método yii\base\Model::scenarios() retornará todos os cenários e atributos encontrados em yii\base\Model::rules(), se não quiser sobrescrever este método, isto significa que um atributo é seguro desde que esteja mencionado em uma regra de validação ativa.
Por esta razão, uma alias especial de validação chamada safe
, será fornecida
para que você possa declarar um atributo seguro, sem ser validado. Por exemplo,
a declaração da regra a seguir faz com que tanto o atributo title
quanto o
description
sejam seguros.
public function rules()
{
return [
[['title', 'description'], 'safe'],
];
}
Atributos não Seguros ¶
Como descrito anteriormente, o método yii\base\Model::scenarios() serve para
dois propósitos: determinar quais atributos devem ser validados e quais atributos
são seguros. Em alguns casos raros, você pode quer validar um atributo sem marca-lo
como seguro. Para fazer isto, acrescente um ponto de exclamação !
como prefixo
do nome do atributo ao declarar no método scenarios()
, como o que foi feito no
atributo secret
no exemplo a seguir:
public function scenarios()
{
return [
'login' => ['username', 'password', '!secret'],
];
}
Quando o model (modelo) estiver no cenário login
, todos os três atributos serão
validados. No entanto, apenas os atributos username
e password
poderão ser
atribuídos em massa. Para atribuir um valor de entrada no atributo secret
, terá
que fazer isto explicitamente da seguinte forma:
$model->secret = $secret;
Exportação de Dados ¶
Muitas vezes os models (modelos) precisam ser exportados em diferentes tipos de formatos. Por exemplo, você pode querer converter um conjunto de models (modelos) no formato JSON ou Excel. O processo de exportação pode ser divido em duas etapas independentes. Na primeira etapa, os models (modelos) serão convertidos em arrays; na segunda etapa, os arrays serão convertidos em um determinado formato. Se concentre apenas na primeira etapa, uma vez que a segunda etapa pode ser alcançada por formatadores de dados genéricos, tais como o yii\web\JsonResponseFormatter.
A maneira mais simples de converter um model (modelo) em um array consiste no uso da propriedade yii\base\Model::$attributes. Por exemplo,
$post = \app\models\Post::findOne(100);
$array = $post->attributes;
Por padrão, a propriedade yii\base\Model::$attributes retornará os valores de todos os atributos declarados no método yii\base\Model::attributes().
Uma maneira mais flexível e poderosa de converter um model (modelo) em um array é através do método yii\base\Model::toArray(). O seu comportamento padrão é o mesmo do yii\base\Model::$attributes. No entanto, ele permite que você escolha quais itens de dados, chamados de fields (campos), devem ser mostrados no array resultante e como eles devem vir formatados. Na verdade, é a maneira padrão de exportação de models (modelos) no desenvolvimento de Web services RESTful, como descrito na seção Formatando Respostas.
Campos ¶
Um campo é simplesmente um elemento nomeado no array obtido pela chamada do método yii\base\Model::toArray() de um model (modelo).
Por padrão, os nomes dos campos são iguais aos nomes dos atributos. No entanto,
você pode alterar este comportamento sobrescrevendo os métodos
fields() e/ou extraFields().
Ambos os métodos devem retornar uma lista dos campos definidos. Os campos definidos
pelo método fields()
são os campos padrão, o que significa que o toArray()
retornará estes campos por padrão. O método extraFields()
define, de forma adicional,
os campos disponíveis que também podem ser retornados pelo toArray()
, contanto
que sejam especificados através do parâmetro $expand
. Por exemplo, o código a
seguir retornará todos os campos definidos em fields()
incluindo os campos
prettyName
e fullAddress
, a menos que estejam definidos no extraFields()
.
$array = $model->toArray([], ['prettyName', 'fullAddress']);
Você poderá sobrescrever o método fields()
para adicionar, remover, renomear ou
redefinir os campos. O valor de retorno do fields()
deve ser um array. As chaves
do array não os nomes dos campos e os valores correspondem ao nome do atributo
definido, na qual, podem ser tanto os nomes de propriedades/atributos quanto funções
anônimas que retornam o valor dos campos correspondentes. Em um caso especial,
quando o nome do campo for igual ao nome do atributo definido, você poderá omitir
a chave do array. Por exemplo,
// usar uma lista explicita de todos os campos lhe garante que qualquer mudança
// em sua tabela do banco de dados ou atributos do model (modelo) não altere os
// nomes de seus campos (para manter compatibilidade com versões anterior da API).
public function fields()
{
return [
// o nome do campos é igual ao nome do atributo
'id',
// o nome do campo é "email", o nome do atributo correspondente é "email_address"
'email' => 'email_address',
// o nome do campo é "name", o seu valor é definido por uma função call-back do PHP
'name' => function () {
return $this->first_name . ' ' . $this->last_name;
},
];
}
// filtra alguns campos, é bem usado quando você quiser herdar a implementação
// da classe pai e remover alguns campos delicados.
public function fields()
{
$fields = parent::fields();
// remove os campos que contém informações delicadas
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
return $fields;
}
Atenção: Como, por padrão, todos os atributos de um model (modelo) serão >incluídos no array exportado, você deve examinar seus dados para ter certeza >que não possuem informações delicadas. Se existir, deverá sobrescrever o método >
fields()
para remove-los. No exemplo anterior, nós decidimos remover os >camposauth_key
,password_hash
epassword_reset_token
.
Boas Práticas ¶
A representação dos dados, regras e lógicas de negócios estão centralizados nos models (modelos). Muitas vezes precisam ser reutilizadas em lugares diferentes. Em um aplicativo bem projetado, models (modelos) geralmente são muitos maiores que os controllers
Em resumo, os models (modelos):
- podem conter atributos para representar os dados de negócio;
- podem conter regras de validação para garantir a validade e integridade dos dados;
- podem conter métodos para implementar lógicas de negócio;
- NÃO devem acessar diretamente as requisições, sessões ou quaisquer dados do ambiente do usuário. Os models (modelos) devem receber estes dados a partir dos controllers (controladores);
- devem evitar inserir HTML ou outros códigos de apresentação – isto deve ser feito nas views (visões);
- devem evitar ter muitos cenários em um único model (modelo).
Você deve considerar em utilizar com mais frequência a última recomendação acima quando desenvolver sistemas grandes e complexos. Nestes sistemas, os models (modelos) podem ser bem grandes, pois são usados em muitos lugares e podendo, assim, conter muitas regras e lógicas de negócio. Nestes casos, a manutenção do código de um model (modelo) pode se transformar em um pesadelo, na qual uma simples mudança no código pode afetar vários lugares diferentes. Para desenvolver um model (modelo) manutenível, você pode seguir a seguinte estratégia:
- Definir um conjunto de classes model (modelo) base que são compartilhados por diferentes aplicações ou módulos. Estas classes model (modelo) base deve contem um conjunto mínimo de regras e lógicas de negocio que são comuns entre os locais que as utilizem.
- Em cada aplicação ou módulo que usa um model (modelo), deve definir uma classe model (modelo) concreta que estenderá a classe model (modelo) base que a corresponde. A classe model (modelo) concreta irá conter apenas as regras e lógicas que são específicas de uma aplicação ou módulo.
Por exemplo, no Template Avançado de Projetos, você
pode definir uma classe model (modelo) base common\models\Post
. Em seguida,
para a aplicação front-end, você define uma classe model (modelo) concreta
frontend\models\Post
que estende de common\models\Post
. E de forma similar
para a aplicação back-end, você define a backend\models\Post
. Com essa estratégia,
você garantirá que o frontend\models\Post
terá apenas códigos específicos da
aplicação front-end e, se você fizer qualquer mudança nele, não precisará se
preocupar se esta mudança causará erros na aplicação back-end.