Modelos
=======

Los modelos forman parte de la arquitectura 
[MVC](http://es.wikipedia.org/wiki/Modelo%E2%80%93vista%E2%80%93controlador). Son objetos que representan datos de 
negocio, reglas y lógica.

Se pueden crear clases modelo extendiendo a [[yii\base\Model]] o a sus clases hijas. La clase base [[yii\base\Model]] 
soporta muchas características útiles:

* [Atributos](#attributes): representan los datos de negocio y se puede acceder a ellos como propiedades normales de 
  un objeto o como elementos de un array;
* [Etiquetas de atributo](#attribute-labels): especifica la etiqueta a mostrar para los atributos;
* [Asignación masiva](#massive-assignment): soporta la asignación múltiple de atributos en un único paso;
* [validación](#validation-rules): asegura la validez de los datos de entrada basándose en reglas declaradas;
* [Exportación de datos](#data-exporting): permite que los datos del modelo sean exportados en términos de arrays con 
  formatos personalizables.

La clase 'modelo' también es una base para modelos más avanzados, tales como [Active Records](db-active-record.md).

> Info: No es obligatorio basar las clases modelo en [[yii\base\Model]]. Sin embargo, debido a que hay muchos 
  componentes de Yii construidos para dar soporte a [[yii\base\Model]], por lo general, es la clase base preferible 
  para un modelo.

### Atributos <span id="attributes"></span>

Los modelos representan los datos de negocio en términos de *atributos*. Cada atributos es como una propiedad 
públicamente accesible de un modelo. El método [[yii\base\Model::attributes()]] especifica qué atributos tiene la 
clase modelo.

Se puede acceder a un atributo como se accede a una propiedad de un objeto normal.

```php
$model = new \app\models\ContactForm;

// "name" es un atributo de ContactForm
$model->name = 'example';
echo $model->name;
```

También se puede acceder a los atributos como se accede a los elementos de un array, gracias al soporte para 
[ArrayAccess](https://secure.php.net/manual/es/class.arrayaccess.php) y 
[ArrayIterator](https://secure.php.net/manual/es/class.arrayiterator.php) que brinda [[yii\base\Model]]:

```php
$model = new \app\models\ContactForm;

// acceder a atributos como elementos de array
$model['name'] = 'example';
echo $model['name'];

// iterar entre atributos
foreach ($model as $name => $value) {
    echo "$name: $value\n";
}
```

### Definir Atributos <span id="defining-attributes"></span>

Por defecto, si un modelo extiende directamente a [[yii\base\Model]], todas sus variables miembro no estáticas son 
atributos. Por ejemplo, la siguiente clase modelo 'ContactForm' tiene cuatro atributos: 'name', 'email', 'subject', 
'body'. El modelo 'ContactForm' se usa para representar los datos de entrada recibidos desde un formulario HTML.

```php
namespace app\models;

use yii\base\Model;

class ContactForm extends Model
{
    public $name;
    public $email;
    public $subject;
    public $body;
}
```

Se puede sobrescribir [[yii\base\Model::attributes()]] para definir los atributos de diferente manera. El método debe 
devolver los nombres de los atributos de un modelo. Por ejemplo [[yii\db\ActiveRecord]] lo hace devolviendo el nombre 
de las columnas de la tabla de la base de datos asociada como el nombre de sus atributos. Hay que tener en cuenta que 
también puede necesitar sobrescribir los métodos mágicos como `__get()`, `__set()` de modo que se puede acceder a los 
atributos como a propiedades de objetos normales.

### Etiquetas de atributo <span id="attribute-labels"></span>

Cuando se muestran valores o se obtienen entradas para atributos, normalmente se necesita mostrar etiquetas asociadas 
a los atributos. Por ejemplo, dado un atributo con nombre 'segundoApellido', es posible que se quiera mostrar la 
etiqueta 'Segundo Apellido' ya que es más fácil de interpretar por el usuario final en lugares como campos de 
formularios y en mensajes de error.

Se puede obtener la etiqueta de un atributo llamando a [[yii\base\Model::getAttributeLabel()]]. Por ejemplo:

```php
$model = new \app\models\ContactForm;

// muestra "Name"
echo $model->getAttributeLabel('name');
```

Por defecto, una etiqueta de atributo se genera automáticamente a partir del nombre de atributo. La generación se hace 
con el método [[yii\base\Model::generateAttributeLabel()]]. Este convertirá los nombres de variables de tipo 
camel-case en múltiples palabras con la primera letra de cada palabra en mayúsculas. Por ejemplo 'usuario' se 
convertirá en 'Nombre', y 'primerApellido' se convertirá en 'Primer Apellido'.
Si no se quieren usar las etiquetas generadas automáticamente, se puede sobrescribir 
[[yii\base\Model::attributeLabels()]] a una declaración de etiquetas de atributo especifica. Por ejemplo:

```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 aplicaciones con soporte para múltiples idiomas, se puede querer traducir las etiquetas de los atributos. Esto se 
puede hacer en el método [[yii\base\Model::attributeLabels()|attributeLabels()]], como en el siguiente ejemplo:

```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'),
    ];
}
```

Incluso se puede definir etiquetas de atributo condicionales. Por ejemplo, basándose en el [escenario](#scenarios) en 
que se esta usando el modelo, se pueden devolver diferentes etiquetas para un mismo atributo.

> Info: Estrictamente hablando, los atributos son parte de las [vistas](structure-views.md). Pero declarar las 
  etiquetas en los modelos, a menudo, es muy conveniente y puede generar a un código muy limpio y reutilizable.

## Escenarios <span id="scenarios"></span>

Un modelo puede usarse en diferentes *escenarios*. Por ejemplo, un modelo 'Usuario', puede ser utilizado para recoger 
entradas de inicio de sesión de usuarios, pero también puede usarse para generar usuarios. En diferentes escenarios, 
un modelo puede usar diferentes reglas de negocio y lógica. Por ejemplo, un atributo 'email' puede ser requerido 
durante un registro de usuario, pero no ser necesario durante el inicio de sesión del mismo.

Un modelo utiliza la propiedad [[yii\base\Model::scenario]] para mantener saber en qué escenario se esta usando. Por 
defecto, un modelo soporta sólo un escenario llamado 'default'. El siguiente código muestra dos maneras de establecer 
el escenario en un modelo.

```php
// el escenario se establece como una propiedad
$model = new User;
$model->scenario = 'login';

// el escenario se establece mediante configuración
$model = new User(['scenario' => 'login']);
```

Por defecto, los escenarios soportados por un modelo se determinan por las [reglas de validación](#validation-rules) 
declaradas en el modelo. Sin embargo, se puede personalizar este comportamiento sobrescribiendo el método 
[[yii\base\Model::scenarios()]], como en el siguiente ejemplo:

```php
namespace app\models;

use yii\db\ActiveRecord;

class User extends ActiveRecord
{
    public function scenarios()
    {
        return [
            'login' => ['username', 'password'],
            'register' => ['username', 'email', 'password'],
        ];
    }
}
```

> Info: En el anterior y en los siguientes ejemplos, las clases modelo extienden a [[yii\db\ActiveRecord]] 
  porque el uso de múltiples escenarios normalmente sucede con clases de [Active Records](db-active-record.md).

El método 'scenarios()' devuelve un array cuyas claves son el nombre de escenario y los valores correspondientes a los 
*atributos activos*. Un atributo activo puede ser [asignado masivamente](#massive-assignment) y esta sujeto a 
[validación](#validation-rules). En el anterior ejemplo, los atributos 'username' y 'password' están activados en el 
escenario 'login'; mientras que en el escenario 'register', el atributo 'email' esta activado junto con 'username' y 
'password'.

La implementación por defecto de los 'scenarios()' devolverá todos los escenarios encontrados en el método de 
declaración de las reglas de validación [[yii\base\Model::rules()]]. Cuando se sobrescribe 'scenarios()', si se quiere 
introducir nuevos escenarios además de los predeterminados, se puede hacer como en el siguiente ejemplo:

```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;
    }
}
```

La característica escenario se usa principalmente en las [validaciones](#validation-rules) y por la 
[asignación masiva de atributos](#massive-assignment). Aunque también se puede usar para otros propósitos. Por 
ejemplo, se pueden declarar [etiquetas de atributo](#attribute-labels) diferentes basándose en el escenario actual.

## Reglas de Validación <span id="validation-rules"></span>

Cuando un modelo recibe datos del usuario final, estos deben ser validados para asegurar que cumplan ciertas reglas 
(llamadas *reglas de validación*, también conocidas como *reglas de negocio*). Por ejemplo, dado un modelo 
'ContactForm', se puede querer asegurar que ningún atributo este vacío y que el atributo 'email' contenga una 
dirección de correo válida. Si algún valor no cumple con las reglas, se debe mostrar el mensaje de error apropiado 
para ayudar al usuario a corregir estos errores.

Se puede llamar a [[yii\base\Model::validate()]] para validar los datos recibidos. El método se usará para validar las 
reglas declaradas en [[yii\base\Model::rules()]] para validar cada atributo relevante. Si no se encuentran errores, se 
devolverá `true`. De otro modo, este almacenará los errores en la propiedad [[yii\base\Model::errors]] y devolverá falso.
 Por ejemplo:

```php
$model = new \app\models\ContactForm;

// establece los atributos del modelo con la entrada de usuario
$model->attributes = \Yii::$app->request->post('ContactForm');

if ($model->validate()) {
    // todas las entradas validadas
} else {
    // validación fallida: $errors es un array que contiene los mensajes de error
    $errors = $model->errors;
}
```

Para declarar reglas de validación asociadas a un modelo, se tiene que sobrescribir el método 
[[yii\base\Model::rules()]] para que devuelva las reglas que los atributos del modelo deben satisfacer. El siguiente 
ejemplo muestra las reglas de validación declaradas para el modelo 'ContactForm'.

```php
public function rules()
{
    return [
        // name, email, subject y body son atributos requeridos
        [['name', 'email', 'subject', 'body'], 'required'],

        // el atribuido email debe ser una dirección de correo electrónico válida
        ['email', 'email'],
    ];
}
```

Una regla puede usarse para validar uno o más atributos, y un atributo puede validarse por una o múltiples reglas. Por 
favor refiérase a la sección [Validación de entrada](input-validation.md) para obtener más detalles sobre cómo 
declarar reglas de validación.

A veces, solamente se quiere aplicar una regla en ciertos [escenarios](#scenarios). Para hacerlo, se puede especificar 
la propiedad 'on' de una regla, como en el siguiente ejemplo:

```php
public function rules()
{
    return [
        // username, email y password son obligatorios en el escenario “register”
        [['username', 'email', 'password'], 'required', 'on' => 'register'],

        // username y password son obligatorios en el escenario “login”
        [['username', 'password'], 'required', 'on' => 'login'],
    ];
}
```

Si no se especifica la propiedad 'on', la regla se aplicará en todos los escenarios. Se llama a una regla 
*regla activa* si esta puede aplicarse en el [[yii\base\Model::scenario|scenario]] actual.

Un atributo será validado si y sólo si es un atributo activo declarado en 'scenarios()' y esta asociado con una o más 
reglas activas declaradas en 'rules()'.

## Asignación Masiva <span id="massive-assignment"></span>

La asignación masiva es una buena forma de rellenar los atributos de un modelo con las entradas de usuario en una 
única línea de código. Rellena los atributos de un modelo asignando los datos de entrada directamente a las 
propiedades de [[yii\base\Model::$attributes]]. Los siguientes dos ejemplos son equivalentes, ambos intentan asignar 
los datos enviados por el usuario final a través de un formulario a los atributos del modelo 'ContactForm'. 
Claramente, el primero, que usa la asignación masiva, es más claro y menos propenso a errores que el segundo:

```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>

La asignación masiva sólo se aplica a los llamados *atributos seguros* qué son los atributos listados en 
[[yii\base\Model::scenarios()]] para el actual [[yii\base\Model::scenario|scenario]] del modelo. Por ejemplo, si en el 
modelo 'User' tenemos la siguiente declaración de escenario, entonces cuando el escenario actual sea 'login', sólo los 
atributos 'username' y 'password' podrán ser asignados masivamente. Cualquier otro atributo permanecerá intacto 

```php
public function scenarios()
{
    return [
        'login' => ['username', 'password'],
        'register' => ['username', 'email', 'password'],
    ];
}
```

> Info: La razón de que la asignación masiva sólo se aplique a los atributos seguros es debida a que se quiere 
controlar qué atributos pueden ser modificados por los datos del usuario final. Por ejemplo, si el modelo 'User' tiene 
un atributo 'permission' que determina los permisos asignados al usuario, se quiere que estos atributos sólo sean 
modificados por administradores desde la interfaz backend.

Debido a que la implementación predeterminada de [[yii\base\Model::scenarios()]] devolverá todos los escenarios y 
atributos encontrados en [[yii\base\Model::rules()]], si no se sobrescribe este método, significa que un atributo es 
seguro mientras aparezca en una de las reglas de validación activas.

Por esta razón, se proporciona un validador especial con alias 'safe' con el que se puede declarar un atributo como 
seguro sin llegar a validarlo. Por ejemplo, las siguientes reglas declaran que los atributos 'title' y 'description' 
son atributos seguros.

```php
public function rules()
{
    return [
        [['title', 'description'], 'safe'],
    ];
}
```

### Atributos Inseguros <span id="unsafe-attributes"></span>

Como se ha descrito anteriormente, el método [[yii\base\Model::scenarios()]] sirve para dos propósitos: determinar qué 
atributos deben ser validados y determinar qué atributos son seguros. En situaciones poco comunes, se puede querer 
validar un atributo pero sin marcarlo como seguro. Se puede hacer prefijando el signo de exclamación '!' delante del 
nombre del atributo cuando se declaran en 'scenarios()', como el atributo 'secret' del siguiente ejemplo:

```php
public function scenarios()
{
    return [
        'login' => ['username', 'password', '!secret'],
    ];
}
```

Cuando el modelo esté en el escenario 'login', los tres atributos serán validados. Sin embargo, sólo los atributos 
'username' y 'password' se asignarán masivamente. Para asignar un valor de entrada al atribuido 'secret', se tendrá 
que hacer explícitamente como en el ejemplo:

```php
$model->secret = $secret;
```

## Exportación de Datos <span id="data-exporting"></span>

A menudo necesitamos exportar modelos a diferentes formatos. Por ejemplo, se puede querer convertir un conjunto de 
modelos a formato JSON o Excel. El proceso de exportación se puede dividir en dos pasos independientes. En el primer 
paso, se convierten los modelos en arrays; en el segundo paso, los arrays se convierten a los formatos deseados. Nos 
puede interesar fijarnos en el primer paso, ya que el segundo paso se puede lograr mediante un formateador de datos 
genérico, tal como [[yii\web\JsonResponseFormatter]].
La manera más simple de convertir un modelo en un array es usar la propiedad [[yii\base\Model::$attributes]]. Por 
ejemplo:

```php
$post = \app\models\Post::findOne(100);
$array = $post->attributes;
```

Por defecto, la propiedad [[yii\base\Model::$attributes]] devolverá los valores de *todos* los atributos declarados en 
[[yii\base\Model::attributes()]].

Una manera más flexible y potente de convertir un modelo en un array es usar el método [[yii\base\Model::toArray()]]. 
Su funcionamiento general es el mismo que el de [[yii\base\Model::$attributes]]. Sin embargo, este permite elegir que 
elementos de datos, llamados *campos*, queremos poner en el array resultante y elegir como debe ser formateado. De 
hecho, es la manera por defecto de exportar modelos en desarrollo de servicios Web RESTful, tal y como se describe en 
[Formatos de Respuesta](rest-response-formatting.md).

### Campos <span id="fields"></span>

Un campo es simplemente un elemento nombrado en el array resultante de ejecutar el método [[yii\base\Model::toArray()]]
 de un modelo.
Por defecto, los nombres de los campos son equivalentes a los nombres de los atributos. Sin embargo, se puede 
modificar este comportamiento sobrescribiendo el método [[yii\base\Model::fields()|fields()]] y/o el método 
[[yii\base\Model::extraFields()|extraFields()]]. Ambos métodos deben devolver una lista de las definiciones de los 
campos. Los campos definidos mediante 'fields()' son los campos por defecto, esto significa que 'toArray()' devolverá 
estos campos por defecto. El método 'extraFields()' define campos adicionalmente disponibles que también pueden 
devolverse mediante 'toArray()' siempre y cuando se especifiquen a través del parámetro '$expand'. Por ejemplo, el 
siguiente código devolverá todos los campos definidos en 'fields()' y los campos 'prettyName' y 'fullAdress' si estos 
están definidos en 'extraFields()'.

```php
$array = $model->toArray([], ['prettyName', 'fullAddress']);
```

Se puede sobrescribir 'fields()' para añadir, eliminar, renombrar o redefinir campos. El valor devuelto por 'fields()' 
debe se un array. Las claves del array son los nombres de los campos, y los valores son las correspondientes 
definiciones de los campos que pueden ser nombres de propiedades/atributos o funciones anónimas que devuelvan los 
correspondientes valores de campo. En el caso especial en que un nombre de un campo es el mismo a su definición de 
nombre de atributo, se puede omitir la clave del array. Por ejemplo:

```php
// lista explícitamente cada campo, es mejor usarlo cuando nos queremos asegurar 
// de que los cambios en la tabla de la base de datos o los atributos del modelo 
// no modifiquen los campos(para asegurar compatibilidades para versiones anteriores de API)
public function fields()
{
    return [
        // el nombre del campo es el mismo que el nombre de atributo
        'id',

        // el nombre del campo es “email”, el nombre de atributo correspondiente es “email_address”
        'email' => 'email_address',

        // El nombre del campo es “name”, su valor esta definido por una llamada de retorno PHP
        'name' => function () {
            return $this->first_name . ' ' . $this->last_name;
        },
    ];
}

// filtrar algunos campos, es mejor usarlo cuando se quiere heredar la implementación del padre
// y discriminar algunos campos sensibles.
public function fields()
{
    $fields = parent::fields();

    // elimina campos que contengan información sensible.
    unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);

    return $fields;
}
```

> Warning: debido a que por defecto todos los atributos de un modelo serán incluidos en el array exportado, se debe 
examinar los datos para asegurar que no contienen información sensible. Si existe dicha información, se debe 
sobrescribir 'fields()' para filtrarla. En el anterior ejemplo, se filtra 'aut_key', 'password_hash' y 
'password_reset_token'.

## Mejores Prácticas <span id="best-practices"></span>

Los modelos son los lugares centrales para representar datos de negocio, reglas y lógica. Estos a menudo necesitan ser 
reutilizados en diferentes lugares. En una aplicación bien diseñada, los modelos normalmente son más grandes que los 
[controladores](structure-controllers.md).

En resumen, los modelos:
* pueden contener atributos para representar los datos de negocio;
* pueden contener reglas de validación para asegurar la validez e integridad de los datos;
* pueden contener métodos que para implementar la lógica de negocio;
* NO deben acceder directamente a peticiones, sesiones, u otro tipo de datos de entorno. Estos datos deben ser 
  inyectados por los [controladores](structure-controllers.md) en los modelos.
* deben evitar embeber HTML u otro código de presentación – esto es mejor hacerlo en las [vistas](structure-views.md);
* evitar tener demasiados [escenarios](#scenarios) en un mismo modelo.

Generalmente se puede considerar la última recomendación cuando se estén desarrollando grandes sistemas complejos. En 
estos sistemas, los modelos podrían ser muy grandes debido a que podrían ser usados en muchos lugares y por tanto 
contener muchos conjuntos de reglas y lógicas de negocio. A menudo esto desemboca en un código muy difícil de mantener 
ya que una simple modificación en el código puede afectar a muchos sitios diferentes. Para mantener el código más 
fácil de mantener, se puede seguir la siguiente estrategia:

* Definir un conjunto de clases modelo base que sean compartidas por diferentes 
  [aplicaciones](structure-applications.md) o [módulos](structure-modules.md). Estas clases modelo deben contener el 
  conjunto mínimo de reglas y lógica que sean comunes para todos sus usos.
* En cada [aplicación](structure-applications.md) o [módulo](structure-modules.md) que use un modelo, definir una 
  clase modelo concreta que extienda a la correspondiente clase modelo base. La clase modelo concreta debe contener 
  reglas y lógica que sean específicas para esa aplicación o módulo.

Por ejemplo, en la [Plantilla de Aplicación Avanzada](tutorial-advanced-app.md), definiendo una clase modelo base 
'common\models\Post'. Después en la aplicación front end, definiendo y usando una clase modelo concreta 
'frontend\models\Post' que extienda a 'common\models\Post'. Y de forma similar en la aplicación back end, definiendo 
'backend\models\Post'. Con esta estrategia, nos aseguramos que el código de 'frontend\models\Post' es específico para 
la aplicación front end, y si se efectúa algún cambio en el, no nos tenemos que preocupar de si el cambio afectará a 
la aplicación back end.
