Yii2 framework backup
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

579 lines
24 KiB

Models (Modelos)
================
Os models (modelos) fazem parte da arquitetura [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
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](#attributes): 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](#attribute-labels): especifica os labels de exibição dos
atributos;
* [Atribuição em massa](#massive-assignment): suporta popular vários atributos em
uma única etapa;
* [Regras de validação](#validation-rules): garante que os dados de entrada sejam
baseadas nas regras de validação que foram declaradas;
* [Data Exporting](#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](db-active-record.md).
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 <span id="attributes"></span>
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:
```php
$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](https://www.php.net/manual/en/class.arrayaccess.php) e
[ArrayIterator](https://www.php.net/manual/en/class.arrayiterator.php) pelo
[[yii\base\Model]]:
```php
$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 <span id="defining-attributes"></span>
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.
```php
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 <span id="attribute-labels"></span>
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,
```php
$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,
```php
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 [[yii\base\Model::attributeLabels()|attributeLabels()]],
conforme o exemplo a seguir:
```php
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](#scenarios) 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](structure-views.md) (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 <span id="scenarios"></span>
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):
```php
// 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](#validation-rules) 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:
```php
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](db-active-record.md) 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](#massive-assignment) e é sujeito a
[validação](#validation-rules). 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:
```php
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](#validation-rules)
e para [atribuição em massa](#massive-assignment).
Você pode, no entanto, usá-lo para outros fins. Por exemplo, você pode declarar
diferentes [labels para os atributos](#attribute-labels) baseados no cenário atual.
## Regras de Validação <span id="validation-rules"></span>
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,
```php
$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`:
```php
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](input-validation.md) 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](#scenarios). Para fazer isso, você pode especificar a propriedade
`on` de uma regra, como o seguinte:
```php
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|cenário]] 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 <span id="massive-assignment"></span>
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:
```php
$model = new \app\models\ContactForm;
$model->attributes = \Yii::$app->request->post('ContactForm');
```
```php
$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 <span id="safe-attributes"></span>
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|cenário]] 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.
```php
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.
```php
public function rules()
{
return [
[['title', 'description'], 'safe'],
];
}
```
### Atributos não Seguros <span id="unsafe-attributes"></span>
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:
```php
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:
```php
$model->secret = $secret;
```
## Exportação de Dados <span id="data-exporting"></span>
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,
```php
$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](rest-response-formatting.md).
### Campos <span id="fields"></span>
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
[[yii\base\Model::fields()|fields()]] e/ou [[yii\base\Model::extraFields()|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()`.
```php
$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,
```php
// 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
>campos `auth_key`, `password_hash` e `password_reset_token`.
## Boas Práticas <span id="best-practices"></span>
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](structure-controllers.md)
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)](structure-controllers.md);
* devem evitar inserir HTML ou outros códigos de apresentação – isto deve ser
feito nas [views (visões)](structure-views.md);
* devem evitar ter muitos [cenários](#scenarios) 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](structure-applications.md) ou [módulos](structure-modules.md).
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](structure-applications.md) ou [módulo](structure-modules.md)
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](tutorial-advanced-app.md), 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.