Active Record
ActiveRecord es un patrón de diseño el cual permite crear un objeto que "envuelve" una tabla SQL, agregándole la lógica del modelo y el control de acceso. Este patrón de diseño permite unir el mundo de la programación orientada a objetos (OOP), que es un mundo intuitivo y "natural", con el mundo matemático y rígido de los datos relacionales (SQL).
Usar POO y SQL dentro de una aplicación es como si un arquitecto quisiese usar dos tipos totalmente diferentes de materiales (con propiedades y pesos diferentes) en una misma estructura: la mezcla sería una gran y continua molestia al momento de diseñar los espacios, calcular los pesos y las distancias.
En Cake la convención es crear la tabla en plural dentro la BD y usar el singular como nombre del modelo, es decir para la tabla 'users' el modelo es 'User', para la tabla 'dogs' el modelo es 'Dog', etcétera. Active Record permite diseñar los programas usando la OOP de una manera homogénea sin tener que pensar las tablas SQL como elementos "insertados" y fuera de la lógica del modelo. Es decir, los updates, selects y deletes son propiedades del objeto User o Dog, los cuales se manipulan a través de sus métodos. Esto es muy conveniente pues si, por ejemplo, necesitamos ordenar los registros del modelo User podemos extender nuestra clase creando el método ordenar($field).
Otra aproximación para salvar la división Relacional-Objetal es usar ORM (object-relational mapping), que son archivos XML que mapean las tablas y crean objetos manipulables. Propel es un ORM para PHP5 que usa esta aproximación. Sin embargo este método es más engorroso (generalmente hay que crear los XML a mano), por ello la mayoría de los frameworks como CakePHP o Ruby on Rails usan ActiveRecord.
Veamos ahora el modelo User como un ejemplo de modelo típico:
<?php
//AppModel gives you all of Cake's Model functionality
class User extends AppModel
{
// Its always good practice to include this variable.
public $name = 'User';
// This is used for validation, see Chapter "Data Validation".
public $validate = array();
// You can also define associations.
// See section 6.3 for more information.
public $hasMany = array('Image' =>
array('className' => 'Image')
);
// You can also include your own functions:
public function makeInactive($uid)
{
//Put your own logic here...
}
}
?>
Entre los métodos de la clase padre Model (que es la implementación ActiveRecord de CakePHP), los más usados son:
$Model->findAll(); // encuentra todos los registros
$Model->find(); // encuentra un row
$Model->field(); // trae sólo un campo
$Model->generateList(); // esta función se usa cuando en la vista vamos a construir un formulario con un select a partir de una tabla
$Model->findAllBy // función que encuentra todos los registros con el nombre del campo
Por ejemplo, cuando usamos el modelo Page (de la tabla 'pages'):
$Page->findAll();
Es equivalente a:
SELECT * FROM pages;
pero podemos alterar el comportamiento y los resultados de los métodos de Model cambiando sus atributos:
$Page->findAll(
$conditions = null,
$fields = null,
$order = null,
$limit = null,
$page = 1,
$recursive = null
);
Esto es:
$conditions = array("id"=>"> 50");
$fields = array("id", "title", "body", "created");
$order = "Page.id DESC";
$limit = 15;
$page = 1;
$recursive = null;
$data = $Page->findAll($conditions, $fields, $order, $limit);
Es equivalente a:
SELECT id, title, body, created FROM pages WHERE id > 50 ORDER BY id DESC LIMIT 15;
El atributo $conditions (que es un array relacional), puede ser más complejo:
$conditions = array("Author.name" => "Bob", "or" => array
(
"Page.title" => "LIKE %magic%",
"Page.created" => "> " . date('Y-m-d', strtotime("-2 weeks")
)
)
Existe un método de la clase Model: findbySQL() que permite hacer queries SQL, pero es un método depreciado que debemos evitar, pues el objetivo es pensar más en una arquitectura de objetos "pura" y menos en tablas.
El atributo $page de findAll() es muy útil para realizar la paginación de los resultados, mientras $recursive señala si la tabla tiene relaciones con otras tablas. Esto lo veremos a continuación.
Nota: CakePHP supone que la llave primaria en todas las tablas es el campo "id", pero si no es asi, hay que indicarlo en el modelo con la variable $primaryKey
Relaciones (Binds)
Hasta ahora sólo hemos visto modelos de tablas de PostgreSQL solas, pero ¿cómo se combinan las tablas usando ActiveRecord? En SQL usaríamos un INNER JOIN o una selección múltiple:
$sql= "SELECT w.estilo, w.urlbase, w.nombre, w.email, w.keywords, w.author, p.titulo, p.disc FROM website as w, paginas as p WHERE p.id = " . $idp;
CakePHP usa relaciones o "binds" que se declaran en los modelos usando palabras (atributos) clave:
$hasMany = ("tiene muchas") , es decir que tiene muchos registros en esa tabla, por ejemplo, el modelo User tiene muchas entradas en el modelo Photos.
$hasOne = ("tiene uno") por ejemplo el modelo Automovil solo puede tener un registro en el modelo Motor, pues no puede tener dos o más
$belongsTo = ("pertenece a") el modelo User es poseedor del modelo Project
$hasAndBelongsToMany = ("tiene muchos y pertenece a muchos o HABTM") <-- en apariencia un poco caótico
Estos binds son una de las características más poderosas de CakePHP. Por ejemplo, veamos el modelo de la tabla 'pages':
Archivo: app/models/page.php
class Page extends AppModel
{
public $name = 'Page';
public $belongsTo = 'User';
public $validate = array(
'title' => '/[a-z0-9-]{3,}$/i',
'body' => VALID_NOT_EMPTY,
'email' => VALID_EMAIL,
'born' => VALID_NUMBER
);
?>
La indicación de relación $belongsTo ("pertenece a") indica que el modelo User es propietario de los datos que están en el modelo Page, por lo tanto la tabla 'pages' debe tener un campo user_id para vincularlo con 'users'. Esto es, debemos tener las tablas como sigue:
Model: User
CREATE TABLE users (
id serial PRIMARY KEY,
username character varying(50) DEFAULT ''::character varying NOT NULL UNIQUE, --login
passwd character varying(50) DEFAULT ''::character varying NOT NULL,
name character varying(50) DEFAULT ''::character varying NOT NULL UNIQUE, --real name
email character varying(45) DEFAULT ''::character varying NOT NULL UNIQUE,
last_visit timestamp(0) with time zone DEFAULT now() NOT NULL,
group_id integer NOT NULL, -- Admin, editor, normal user
active integer DEFAULT 0 NOT NULL,
created timestamp(0) with time zone DEFAULT now() NOT NULL
);
Model: Page
CREATE TABLE pages ( -- static pages
id serial PRIMARY KEY,
title varchar(90) NOT NULL,
body text NOT NULL,
created timestamp(0) with time zone DEFAULT now() NOT NULL,
modified timestamp(0) with time zone DEFAULT now() NOT NULL,
disc int NOT NULL DEFAULT 0, --discution (comments) actived
state int NOT NULL DEFAULT 0,
user_id int NOT NULL REFERENCES users(id) ON DELETE CASCADE
);
Recuerda: en PostgreSQL, MySQL o Sqlite la tabla es en plural (pages) pero en CakePHP el modelo es singular: Page. Nota también que hay un campo user_id en la tabla "pages" pues lo que hay en Page pertenece al modelo User.
Ahora si podemos relacionar estos dos modelos:
$conditions = array("Page.state"=>1);
$fields = array("id", "title", "body", "created", "user_id");
$order = "Page.created DESC";
$limit = 15;
$page = 1;
$recursive = 2;
$data = $Page->findAll($conditions, $fields, $order, $limit, $page, $recursive);
El valor de $recursive es importante pues indica a CakePHP que busque los datos en los "binds" del modelo Page.
Tip: $data recoge el resultado de la consulta al modelo, usa: die(print_r($data)); y también die(var_dump($data)); en las vistas para ver que es lo que el array $data contiene.
bind() y unbind()
Los binds son soluciones poderosas, pero a veces necesitamos un diferente tipo de bind para cierto escenario o quizás la tabla tiene varios binds y solo necesitamos poca información de ella y no requerimos un query tan grande.
La clase Model posee los métodos $Model->unbind() y $Model->bind() que "desligan" y "religan" las asociaciones "al vuelo" en el controlador. Por ejemplo si usamos:
$this->Gallery->unbindModel(array('hasMany' => array('Photo')));
$this->set('data', $this->Gallery->findAll());
Estro evitara que la información del modelo Photo relacionada con Gallery sea traida al hacer el findAll(), en lugar de ello sólo obtendremos la información contenida en el modelo Gallery. $Model->unbindModel() debe usarse cuando sea posible.
<< Capítulo Anterior
Ficha del autor:
aarkerioNotice (8): Undefined index: email [APP/View/Pages/display.ctp, line 26]Code Context echo $this->Html->div('cv');
echo "Ficha del autor:<br /><span class=\"login\">".$data['User']['username'].'</span><br />';
echo "<b>". str_replace('@', '_ARRROBA_', $data['User']['email'])."</b><br />".$data['User']['cv'].'<br />';
$___viewFn = "/var/chipotle/sites/cakephp/centauro/View/Pages/display.ctp"
$___dataForView = array(
"data" => array(
"Page" => array(),
"User" => array(),
"Section" => array(),
"Discution" => array()
)
)
$data = array(
"Page" => array(
"id" => 747,
"section_id" => 28,
"title" => "CakePHP II Active Record",
"body" => "<h1><strong>Active Record</strong></h1>
<p><a href="http://en.wikipedia.org/wiki/Active_record">ActiveRecord</a> es un patrón de diseño el cual permite crear un objeto que "envuelve" una tabla SQL, agregándole la lógica del modelo y el control de acceso. Este patrón de diseño permite unir el mundo de la programación orientada a objetos (OOP), que es un mundo intuitivo y "natural", con el mundo matemático y rígido de los datos relacionales (SQL). <br />
</p>
<p>Usar POO y SQL dentro de una aplicación es como si un arquitecto quisiese usar dos tipos totalmente diferentes de materiales (con propiedades y pesos diferentes) en una misma estructura: la mezcla sería una gran y continua molestia al momento de diseñar los espacios, calcular los pesos y las distancias. </p>
<p>En Cake la convención es crear la tabla en plural dentro la BD y usar el singular como nombre del modelo, es decir para la tabla 'users' el modelo es 'User', para la tabla 'dogs' el modelo es 'Dog', etcétera. Active Record permite diseñar los programas usando la OOP de una manera homogénea sin tener que pensar las tablas SQL como elementos "insertados" y fuera de la lógica del modelo. Es decir, los <em>updates</em>, <em>selects</em> y <em>deletes</em> son propiedades del objeto User o Dog, los cuales se manipulan a través de sus métodos. Esto es muy conveniente pues si, por ejemplo, necesitamos ordenar los registros del modelo User podemos extender nuestra clase creando el método ordenar($field).<br />
</p>
<p>Otra aproximación para salvar la división <strong>Relacional-Objetal</strong> es usar ORM (object-relational mapping), que son archivos XML que <em>mapean</em> las tablas y crean objetos manipulables. <a href="http://propel.phpdb.org/trac/">Propel</a> es un ORM para PHP5 que usa esta aproximación. Sin embargo este método es más engorroso (generalmente hay que crear los XML a mano), por ello la mayoría de los frameworks como CakePHP o Ruby on Rails usan ActiveRecord.</p>
<p>Veamos ahora el modelo User como un ejemplo de modelo típico:</p>
<p><font color="#993300"><span><?php<br />
//AppModel gives you all of Cake's Model functionality<br />
</span></font></p>
<p><font color="#993300"><span>class User extends AppModel<br />
{<br />
// Its always good practice to include this variable.<br />
public $name = 'User';<br />
<br />
// This is used for validation, see Chapter "Data Validation".<br />
public $validate = array();<br />
<br />
// You can also define associations.<br />
// See section 6.3 for more information.<br />
<br />
public $hasMany = array('Image' =><br />
array('className' => 'Image')<br />
);<br />
<br />
// You can also include your own functions: <br />
public function makeInactive($uid)<br />
{<br />
//Put your own logic here...<br />
}<br />
}<br />
?></span></font> </p>
<p>Entre los métodos de la clase padre <a href="http://api.cakephp.org/class_model.html">Model</a> (que es la implementación ActiveRecord de CakePHP), los más usados son:<br />
</p>
<p>$Model->findAll(); // encuentra todos los registros<br />
</p>
<p>$Model->find(); // encuentra un row<br />
</p>
<p>$Model->field(); // trae sólo un campo</p>
<p>$Model->generateList(); // esta función se usa cuando en la vista vamos a construir un formulario con un select a partir de una tabla<br />
</p>
<p>$Model->findAllBy // función que encuentra todos los registros con el nombre del campo<br />
</p>
<p>Por ejemplo, cuando usamos el modelo Page (de la tabla 'pages'):</p>
<p> <strong>$Page->findAll();</strong></p>
<p>Es equivalente a:</p>
<p>SELECT * FROM pages; </p>
<p>pero podemos alterar el comportamiento y los resultados de los métodos de Model cambiando sus atributos:</p>
<p>$Page->findAll(<br />
$conditions = null,<br />
$fields = null,<br />
$order = null,<br />
$limit = null,<br />
$page = 1,<br />
$recursive = null<br />
); <br />
</p>
<p>Esto es: </p>
<p> <font color="#330099"> $conditions = array("id"=>"> 50");<br />
$fields = array("id", "title", "body", "created");<br />
$order = "Page.id DESC";<br />
$limit = 15;<br />
$page = 1; <br />
$recursive = null; <br />
</font></p>
<p><font color="#330099"> $data = $Page->findAll($conditions, $fields, $order, $limit); </font></p>
<p>Es equivalente a:</p>
<p><font color="#990000">SELECT id, title, body, created FROM pages WHERE id > 50 ORDER BY id DESC LIMIT 15;</font><br />
</p>
<p>El atributo $conditions (que es un array relacional), puede ser más complejo:</p>
<font color="#330099">$conditions = array("Author.name" => "Bob", "or" => array<br /> (<br /> "Page.title" => "LIKE %magic%",<br /> "Page.created" => "> " . date('Y-m-d', strtotime("-2 weeks")<br /> )<br />)</font>
<p>Existe un método de la clase Model: findbySQL() que permite hacer queries SQL, pero es un método depreciado que debemos evitar, pues el objetivo es pensar más en una arquitectura de objetos "pura" y menos en tablas.<br />
</p>
<p>El atributo <em>$page</em> de findAll() es muy útil para realizar la paginación de los resultados, mientras <em>$recursive</em> señala si la tabla tiene relaciones con otras tablas. Esto lo veremos a continuación.</p>
<p>Nota: CakePHP supone que la llave primaria en todas las tablas es el campo "id", pero si no es asi, hay que indicarlo en el modelo con la variable <strong>$primaryKey </strong></p>
<h1><strong>Relaciones (Binds)</strong><br />
</h1>
<p>Hasta ahora sólo hemos visto modelos de tablas de PostgreSQL solas, pero ¿cómo se combinan las tablas usando ActiveRecord? En SQL usaríamos un INNER JOIN o una selección múltiple:</p>
<p>$sql= "SELECT w.estilo, w.urlbase, w.nombre, w.email, w.keywords, w.author, p.titulo, p.disc FROM website as w, paginas as p WHERE p.id = " . $idp; </p>
<p>CakePHP usa relaciones o "binds" que se declaran en los modelos usando palabras (atributos) clave:</p>
<p><font face="couriernew,courier">$hasMany = ("tiene muchas") , es decir que tiene muchos registros en esa tabla, por ejemplo, el modelo User tiene muchas entradas en el modelo Photos. <br />
</font></p>
<p><font face="couriernew,courier">$hasOne = ("tiene uno") por ejemplo el modelo <strong>Automovil</strong> solo puede tener un registro en el modelo <strong>Motor</strong>, pues no puede tener dos o más </font></p>
<p><font face="couriernew,courier">$belongsTo = ("pertenece a") el modelo User es poseedor del modelo Project<br />
</font></p>
<p><font face="couriernew,courier">$hasAndBelongsToMany = ("tiene muchos y pertenece a muchos o HABTM") <-- en apariencia un poco caótico</font><br />
</p>
<p>Estos <strong>binds</strong> son una de las características más poderosas de CakePHP. Por ejemplo, veamos el modelo de la tabla 'pages':</p>
<p><strong>Archivo: app/models/page.php</strong><br />
</p>
<p><font color="#993300"> <span>class Page extends AppModel<br />
{</span> <br />
</font></p>
<p><font color="#993300"><span> public $name = 'Page';<br />
</span></font></p>
<p><font color="#993300"><span> public $belongsTo = 'User'; <br />
<br />
</span><span>public</span><span> $validate = array(<br />
'title' => '/[a-z0-9-]{3,}$/i',<br />
'body' => VALID_NOT_EMPTY,<br />
'email' => VALID_EMAIL,<br />
'born' => VALID_NUMBER<br />
);</span></font></p>
<p><font color="#993300">?> </font></p>
<p>La indicación de relación <span>$belongsTo ("pertenece a") indica que el modelo User es propietario de los datos que están en el modelo Page, por lo tanto la tabla 'pages' debe tener un campo user_id para vincularlo con 'users'. Esto es, debemos tener las tablas como sigue:</span></p>
<p><strong>Model: User</strong><br />
CREATE TABLE users (<br />
id serial PRIMARY KEY,<br />
username character varying(50) DEFAULT ''::character varying NOT NULL UNIQUE, --login<br />
passwd character varying(50) DEFAULT ''::character varying NOT NULL,<br />
name character varying(50) DEFAULT ''::character varying NOT NULL UNIQUE, --real name<br />
email character varying(45) DEFAULT ''::character varying NOT NULL UNIQUE,<br />
last_visit timestamp(0) with time zone DEFAULT now() NOT NULL,<br />
group_id integer NOT NULL, -- Admin, editor, normal user<br />
active integer DEFAULT 0 NOT NULL,<br />
created timestamp(0) with time zone DEFAULT now() NOT NULL<br />
);<br />
</p>
<p><strong>Model: Page</strong><br />
CREATE TABLE pages ( -- static pages <br />
id serial PRIMARY KEY,<br />
title varchar(90) NOT NULL,<br />
body text NOT NULL,<br />
created timestamp(0) with time zone DEFAULT now() NOT NULL,<br />
modified timestamp(0) with time zone DEFAULT now() NOT NULL,<br />
disc int NOT NULL DEFAULT 0, --discution (comments) actived<br />
state int NOT NULL DEFAULT 0,<br />
user_id int NOT NULL REFERENCES users(id) ON DELETE CASCADE<br />
);</p>
<p>Recuerda: en PostgreSQL, MySQL o Sqlite la tabla es en plural (pages) pero en CakePHP el modelo es singular: Page. Nota también que hay un campo <strong>user_id</strong> en la tabla "pages" pues lo que hay en Page pertenece al modelo User. <br />
</p>
<p>Ahora si podemos relacionar estos dos modelos:<br />
</p>
<p><font color="#330099"> $conditions = array("Page.state"=>1);<br />
$fields = array("id", "title", "body", "created", "user_id");<br />
$order = "Page.created DESC";<br />
$limit = 15;<br />
$page = 1; <br />
$recursive = 2; <br />
</font></p>
<p><font color="#330099"> $data = $Page->findAll($conditions, $fields, $order, $limit, $page, $recursive); </font></p>
<p>El valor de $recursive es importante pues indica a CakePHP que busque los datos en los "binds" del modelo Page. </p>
<p><strong>Tip:</strong> $data recoge el resultado de la consulta al modelo, usa: die(print_r($data)); y también die(var_dump($data)); en las vistas para ver que es lo que el array $data contiene.<br />
</p>
<h1>bind() y unbind()</h1>
<p> Los binds son soluciones poderosas, pero a veces necesitamos un diferente tipo de bind para cierto escenario o quizás la tabla tiene varios binds y solo necesitamos poca información de ella y no requerimos un query tan grande. </p>
<p>La clase Model posee los métodos $Model->unbind() y $Model->bind() que "desligan" y "religan" las asociaciones "al vuelo" en el controlador. Por ejemplo si usamos: </p>
<p> <span> $this->Gallery->unbindModel(array('hasMany' => array('Photo')));<br />
$this->set('data', </span><span>$this->Gallery->findAll()</span><span>);</span></p>
<p><span>Estro evitara que la información del modelo Photo relacionada con Gallery sea traida al hacer el findAll(), en lugar de ello sólo obtendremos la información contenida en el modelo Gallery. $Model->unbindModel() debe usarse cuando sea posible.<br />
</span></p>
<p><a href="http://www.mononeurona.org/pages/display/738"><< Capítulo Anterior</a> </p>",
"created" => "2007-04-29 10:56:59-05",
"discution" => 1,
"display" => 2,
"status" => 1,
"user_id" => 1,
"cv" => 1,
"visits" => 594,
"rank" => 7651,
"editor" => 1,
"updated" => "2009-08-20 00:32:14-05"
),
"User" => array(
"id" => 1,
"username" => "aarkerio",
"avatar" => "aarkerio_avatar.png"
),
"Section" => array(
"id" => 28,
"description" => "Server Side",
"img" => "sec-servidor.jpg"
),
"Discution" => array(
array(),
array(),
array()
)
)include - APP/View/Pages/display.ctp, line 26
View::_render() - CORE/Cake/View/View.php, line 598
View::render() - CORE/Cake/View/View.php, line 365
Controller::render() - CORE/Cake/Controller/Controller.php, line 900
Dispatcher::_invoke() - CORE/Cake/Routing/Dispatcher.php, line 114
Dispatcher::dispatch() - CORE/Cake/Routing/Dispatcher.php, line 89
[main] - APP/webroot/index.php, line 81
Notice (8): Undefined index: cv [APP/View/Pages/display.ctp, line 26]Code Context echo $this->Html->div('cv');
echo "Ficha del autor:<br /><span class=\"login\">".$data['User']['username'].'</span><br />';
echo "<b>". str_replace('@', '_ARRROBA_', $data['User']['email'])."</b><br />".$data['User']['cv'].'<br />';
$___viewFn = "/var/chipotle/sites/cakephp/centauro/View/Pages/display.ctp"
$___dataForView = array(
"data" => array(
"Page" => array(),
"User" => array(),
"Section" => array(),
"Discution" => array()
)
)
$data = array(
"Page" => array(
"id" => 747,
"section_id" => 28,
"title" => "CakePHP II Active Record",
"body" => "<h1><strong>Active Record</strong></h1>
<p><a href="http://en.wikipedia.org/wiki/Active_record">ActiveRecord</a> es un patrón de diseño el cual permite crear un objeto que "envuelve" una tabla SQL, agregándole la lógica del modelo y el control de acceso. Este patrón de diseño permite unir el mundo de la programación orientada a objetos (OOP), que es un mundo intuitivo y "natural", con el mundo matemático y rígido de los datos relacionales (SQL). <br />
</p>
<p>Usar POO y SQL dentro de una aplicación es como si un arquitecto quisiese usar dos tipos totalmente diferentes de materiales (con propiedades y pesos diferentes) en una misma estructura: la mezcla sería una gran y continua molestia al momento de diseñar los espacios, calcular los pesos y las distancias. </p>
<p>En Cake la convención es crear la tabla en plural dentro la BD y usar el singular como nombre del modelo, es decir para la tabla 'users' el modelo es 'User', para la tabla 'dogs' el modelo es 'Dog', etcétera. Active Record permite diseñar los programas usando la OOP de una manera homogénea sin tener que pensar las tablas SQL como elementos "insertados" y fuera de la lógica del modelo. Es decir, los <em>updates</em>, <em>selects</em> y <em>deletes</em> son propiedades del objeto User o Dog, los cuales se manipulan a través de sus métodos. Esto es muy conveniente pues si, por ejemplo, necesitamos ordenar los registros del modelo User podemos extender nuestra clase creando el método ordenar($field).<br />
</p>
<p>Otra aproximación para salvar la división <strong>Relacional-Objetal</strong> es usar ORM (object-relational mapping), que son archivos XML que <em>mapean</em> las tablas y crean objetos manipulables. <a href="http://propel.phpdb.org/trac/">Propel</a> es un ORM para PHP5 que usa esta aproximación. Sin embargo este método es más engorroso (generalmente hay que crear los XML a mano), por ello la mayoría de los frameworks como CakePHP o Ruby on Rails usan ActiveRecord.</p>
<p>Veamos ahora el modelo User como un ejemplo de modelo típico:</p>
<p><font color="#993300"><span><?php<br />
//AppModel gives you all of Cake's Model functionality<br />
</span></font></p>
<p><font color="#993300"><span>class User extends AppModel<br />
{<br />
// Its always good practice to include this variable.<br />
public $name = 'User';<br />
<br />
// This is used for validation, see Chapter "Data Validation".<br />
public $validate = array();<br />
<br />
// You can also define associations.<br />
// See section 6.3 for more information.<br />
<br />
public $hasMany = array('Image' =><br />
array('className' => 'Image')<br />
);<br />
<br />
// You can also include your own functions: <br />
public function makeInactive($uid)<br />
{<br />
//Put your own logic here...<br />
}<br />
}<br />
?></span></font> </p>
<p>Entre los métodos de la clase padre <a href="http://api.cakephp.org/class_model.html">Model</a> (que es la implementación ActiveRecord de CakePHP), los más usados son:<br />
</p>
<p>$Model->findAll(); // encuentra todos los registros<br />
</p>
<p>$Model->find(); // encuentra un row<br />
</p>
<p>$Model->field(); // trae sólo un campo</p>
<p>$Model->generateList(); // esta función se usa cuando en la vista vamos a construir un formulario con un select a partir de una tabla<br />
</p>
<p>$Model->findAllBy // función que encuentra todos los registros con el nombre del campo<br />
</p>
<p>Por ejemplo, cuando usamos el modelo Page (de la tabla 'pages'):</p>
<p> <strong>$Page->findAll();</strong></p>
<p>Es equivalente a:</p>
<p>SELECT * FROM pages; </p>
<p>pero podemos alterar el comportamiento y los resultados de los métodos de Model cambiando sus atributos:</p>
<p>$Page->findAll(<br />
$conditions = null,<br />
$fields = null,<br />
$order = null,<br />
$limit = null,<br />
$page = 1,<br />
$recursive = null<br />
); <br />
</p>
<p>Esto es: </p>
<p> <font color="#330099"> $conditions = array("id"=>"> 50");<br />
$fields = array("id", "title", "body", "created");<br />
$order = "Page.id DESC";<br />
$limit = 15;<br />
$page = 1; <br />
$recursive = null; <br />
</font></p>
<p><font color="#330099"> $data = $Page->findAll($conditions, $fields, $order, $limit); </font></p>
<p>Es equivalente a:</p>
<p><font color="#990000">SELECT id, title, body, created FROM pages WHERE id > 50 ORDER BY id DESC LIMIT 15;</font><br />
</p>
<p>El atributo $conditions (que es un array relacional), puede ser más complejo:</p>
<font color="#330099">$conditions = array("Author.name" => "Bob", "or" => array<br /> (<br /> "Page.title" => "LIKE %magic%",<br /> "Page.created" => "> " . date('Y-m-d', strtotime("-2 weeks")<br /> )<br />)</font>
<p>Existe un método de la clase Model: findbySQL() que permite hacer queries SQL, pero es un método depreciado que debemos evitar, pues el objetivo es pensar más en una arquitectura de objetos "pura" y menos en tablas.<br />
</p>
<p>El atributo <em>$page</em> de findAll() es muy útil para realizar la paginación de los resultados, mientras <em>$recursive</em> señala si la tabla tiene relaciones con otras tablas. Esto lo veremos a continuación.</p>
<p>Nota: CakePHP supone que la llave primaria en todas las tablas es el campo "id", pero si no es asi, hay que indicarlo en el modelo con la variable <strong>$primaryKey </strong></p>
<h1><strong>Relaciones (Binds)</strong><br />
</h1>
<p>Hasta ahora sólo hemos visto modelos de tablas de PostgreSQL solas, pero ¿cómo se combinan las tablas usando ActiveRecord? En SQL usaríamos un INNER JOIN o una selección múltiple:</p>
<p>$sql= "SELECT w.estilo, w.urlbase, w.nombre, w.email, w.keywords, w.author, p.titulo, p.disc FROM website as w, paginas as p WHERE p.id = " . $idp; </p>
<p>CakePHP usa relaciones o "binds" que se declaran en los modelos usando palabras (atributos) clave:</p>
<p><font face="couriernew,courier">$hasMany = ("tiene muchas") , es decir que tiene muchos registros en esa tabla, por ejemplo, el modelo User tiene muchas entradas en el modelo Photos. <br />
</font></p>
<p><font face="couriernew,courier">$hasOne = ("tiene uno") por ejemplo el modelo <strong>Automovil</strong> solo puede tener un registro en el modelo <strong>Motor</strong>, pues no puede tener dos o más </font></p>
<p><font face="couriernew,courier">$belongsTo = ("pertenece a") el modelo User es poseedor del modelo Project<br />
</font></p>
<p><font face="couriernew,courier">$hasAndBelongsToMany = ("tiene muchos y pertenece a muchos o HABTM") <-- en apariencia un poco caótico</font><br />
</p>
<p>Estos <strong>binds</strong> son una de las características más poderosas de CakePHP. Por ejemplo, veamos el modelo de la tabla 'pages':</p>
<p><strong>Archivo: app/models/page.php</strong><br />
</p>
<p><font color="#993300"> <span>class Page extends AppModel<br />
{</span> <br />
</font></p>
<p><font color="#993300"><span> public $name = 'Page';<br />
</span></font></p>
<p><font color="#993300"><span> public $belongsTo = 'User'; <br />
<br />
</span><span>public</span><span> $validate = array(<br />
'title' => '/[a-z0-9-]{3,}$/i',<br />
'body' => VALID_NOT_EMPTY,<br />
'email' => VALID_EMAIL,<br />
'born' => VALID_NUMBER<br />
);</span></font></p>
<p><font color="#993300">?> </font></p>
<p>La indicación de relación <span>$belongsTo ("pertenece a") indica que el modelo User es propietario de los datos que están en el modelo Page, por lo tanto la tabla 'pages' debe tener un campo user_id para vincularlo con 'users'. Esto es, debemos tener las tablas como sigue:</span></p>
<p><strong>Model: User</strong><br />
CREATE TABLE users (<br />
id serial PRIMARY KEY,<br />
username character varying(50) DEFAULT ''::character varying NOT NULL UNIQUE, --login<br />
passwd character varying(50) DEFAULT ''::character varying NOT NULL,<br />
name character varying(50) DEFAULT ''::character varying NOT NULL UNIQUE, --real name<br />
email character varying(45) DEFAULT ''::character varying NOT NULL UNIQUE,<br />
last_visit timestamp(0) with time zone DEFAULT now() NOT NULL,<br />
group_id integer NOT NULL, -- Admin, editor, normal user<br />
active integer DEFAULT 0 NOT NULL,<br />
created timestamp(0) with time zone DEFAULT now() NOT NULL<br />
);<br />
</p>
<p><strong>Model: Page</strong><br />
CREATE TABLE pages ( -- static pages <br />
id serial PRIMARY KEY,<br />
title varchar(90) NOT NULL,<br />
body text NOT NULL,<br />
created timestamp(0) with time zone DEFAULT now() NOT NULL,<br />
modified timestamp(0) with time zone DEFAULT now() NOT NULL,<br />
disc int NOT NULL DEFAULT 0, --discution (comments) actived<br />
state int NOT NULL DEFAULT 0,<br />
user_id int NOT NULL REFERENCES users(id) ON DELETE CASCADE<br />
);</p>
<p>Recuerda: en PostgreSQL, MySQL o Sqlite la tabla es en plural (pages) pero en CakePHP el modelo es singular: Page. Nota también que hay un campo <strong>user_id</strong> en la tabla "pages" pues lo que hay en Page pertenece al modelo User. <br />
</p>
<p>Ahora si podemos relacionar estos dos modelos:<br />
</p>
<p><font color="#330099"> $conditions = array("Page.state"=>1);<br />
$fields = array("id", "title", "body", "created", "user_id");<br />
$order = "Page.created DESC";<br />
$limit = 15;<br />
$page = 1; <br />
$recursive = 2; <br />
</font></p>
<p><font color="#330099"> $data = $Page->findAll($conditions, $fields, $order, $limit, $page, $recursive); </font></p>
<p>El valor de $recursive es importante pues indica a CakePHP que busque los datos en los "binds" del modelo Page. </p>
<p><strong>Tip:</strong> $data recoge el resultado de la consulta al modelo, usa: die(print_r($data)); y también die(var_dump($data)); en las vistas para ver que es lo que el array $data contiene.<br />
</p>
<h1>bind() y unbind()</h1>
<p> Los binds son soluciones poderosas, pero a veces necesitamos un diferente tipo de bind para cierto escenario o quizás la tabla tiene varios binds y solo necesitamos poca información de ella y no requerimos un query tan grande. </p>
<p>La clase Model posee los métodos $Model->unbind() y $Model->bind() que "desligan" y "religan" las asociaciones "al vuelo" en el controlador. Por ejemplo si usamos: </p>
<p> <span> $this->Gallery->unbindModel(array('hasMany' => array('Photo')));<br />
$this->set('data', </span><span>$this->Gallery->findAll()</span><span>);</span></p>
<p><span>Estro evitara que la información del modelo Photo relacionada con Gallery sea traida al hacer el findAll(), en lugar de ello sólo obtendremos la información contenida en el modelo Gallery. $Model->unbindModel() debe usarse cuando sea posible.<br />
</span></p>
<p><a href="http://www.mononeurona.org/pages/display/738"><< Capítulo Anterior</a> </p>",
"created" => "2007-04-29 10:56:59-05",
"discution" => 1,
"display" => 2,
"status" => 1,
"user_id" => 1,
"cv" => 1,
"visits" => 594,
"rank" => 7651,
"editor" => 1,
"updated" => "2009-08-20 00:32:14-05"
),
"User" => array(
"id" => 1,
"username" => "aarkerio",
"avatar" => "aarkerio_avatar.png"
),
"Section" => array(
"id" => 28,
"description" => "Server Side",
"img" => "sec-servidor.jpg"
),
"Discution" => array(
array(),
array(),
array()
)
)include - APP/View/Pages/display.ctp, line 26
View::_render() - CORE/Cake/View/View.php, line 598
View::render() - CORE/Cake/View/View.php, line 365
Controller::render() - CORE/Cake/Controller/Controller.php, line 900
Dispatcher::_invoke() - CORE/Cake/Routing/Dispatcher.php, line 114
Dispatcher::dispatch() - CORE/Cake/Routing/Dispatcher.php, line 89
[main] - APP/webroot/index.php, line 81

Notice (8): Undefined index: quote [APP/View/Pages/display.ctp, line 30]Code Contextecho $this->Html->link($this->Html->image('avatars/'.$data['User']['avatar'], array('class'=>'imgborder', 'alt'=>$data['User']['username'], 'title'=>$data['User']['username'])), '/blog/'.$data['User']['username'], array('escape'=>False)) . "<br />";
echo '<i>'.$data['User']['quote'].'</i><br />';
$___viewFn = "/var/chipotle/sites/cakephp/centauro/View/Pages/display.ctp"
$___dataForView = array(
"data" => array(
"Page" => array(),
"User" => array(),
"Section" => array(),
"Discution" => array()
)
)
$data = array(
"Page" => array(
"id" => 747,
"section_id" => 28,
"title" => "CakePHP II Active Record",
"body" => "<h1><strong>Active Record</strong></h1>
<p><a href="http://en.wikipedia.org/wiki/Active_record">ActiveRecord</a> es un patrón de diseño el cual permite crear un objeto que "envuelve" una tabla SQL, agregándole la lógica del modelo y el control de acceso. Este patrón de diseño permite unir el mundo de la programación orientada a objetos (OOP), que es un mundo intuitivo y "natural", con el mundo matemático y rígido de los datos relacionales (SQL). <br />
</p>
<p>Usar POO y SQL dentro de una aplicación es como si un arquitecto quisiese usar dos tipos totalmente diferentes de materiales (con propiedades y pesos diferentes) en una misma estructura: la mezcla sería una gran y continua molestia al momento de diseñar los espacios, calcular los pesos y las distancias. </p>
<p>En Cake la convención es crear la tabla en plural dentro la BD y usar el singular como nombre del modelo, es decir para la tabla 'users' el modelo es 'User', para la tabla 'dogs' el modelo es 'Dog', etcétera. Active Record permite diseñar los programas usando la OOP de una manera homogénea sin tener que pensar las tablas SQL como elementos "insertados" y fuera de la lógica del modelo. Es decir, los <em>updates</em>, <em>selects</em> y <em>deletes</em> son propiedades del objeto User o Dog, los cuales se manipulan a través de sus métodos. Esto es muy conveniente pues si, por ejemplo, necesitamos ordenar los registros del modelo User podemos extender nuestra clase creando el método ordenar($field).<br />
</p>
<p>Otra aproximación para salvar la división <strong>Relacional-Objetal</strong> es usar ORM (object-relational mapping), que son archivos XML que <em>mapean</em> las tablas y crean objetos manipulables. <a href="http://propel.phpdb.org/trac/">Propel</a> es un ORM para PHP5 que usa esta aproximación. Sin embargo este método es más engorroso (generalmente hay que crear los XML a mano), por ello la mayoría de los frameworks como CakePHP o Ruby on Rails usan ActiveRecord.</p>
<p>Veamos ahora el modelo User como un ejemplo de modelo típico:</p>
<p><font color="#993300"><span><?php<br />
//AppModel gives you all of Cake's Model functionality<br />
</span></font></p>
<p><font color="#993300"><span>class User extends AppModel<br />
{<br />
// Its always good practice to include this variable.<br />
public $name = 'User';<br />
<br />
// This is used for validation, see Chapter "Data Validation".<br />
public $validate = array();<br />
<br />
// You can also define associations.<br />
// See section 6.3 for more information.<br />
<br />
public $hasMany = array('Image' =><br />
array('className' => 'Image')<br />
);<br />
<br />
// You can also include your own functions: <br />
public function makeInactive($uid)<br />
{<br />
//Put your own logic here...<br />
}<br />
}<br />
?></span></font> </p>
<p>Entre los métodos de la clase padre <a href="http://api.cakephp.org/class_model.html">Model</a> (que es la implementación ActiveRecord de CakePHP), los más usados son:<br />
</p>
<p>$Model->findAll(); // encuentra todos los registros<br />
</p>
<p>$Model->find(); // encuentra un row<br />
</p>
<p>$Model->field(); // trae sólo un campo</p>
<p>$Model->generateList(); // esta función se usa cuando en la vista vamos a construir un formulario con un select a partir de una tabla<br />
</p>
<p>$Model->findAllBy // función que encuentra todos los registros con el nombre del campo<br />
</p>
<p>Por ejemplo, cuando usamos el modelo Page (de la tabla 'pages'):</p>
<p> <strong>$Page->findAll();</strong></p>
<p>Es equivalente a:</p>
<p>SELECT * FROM pages; </p>
<p>pero podemos alterar el comportamiento y los resultados de los métodos de Model cambiando sus atributos:</p>
<p>$Page->findAll(<br />
$conditions = null,<br />
$fields = null,<br />
$order = null,<br />
$limit = null,<br />
$page = 1,<br />
$recursive = null<br />
); <br />
</p>
<p>Esto es: </p>
<p> <font color="#330099"> $conditions = array("id"=>"> 50");<br />
$fields = array("id", "title", "body", "created");<br />
$order = "Page.id DESC";<br />
$limit = 15;<br />
$page = 1; <br />
$recursive = null; <br />
</font></p>
<p><font color="#330099"> $data = $Page->findAll($conditions, $fields, $order, $limit); </font></p>
<p>Es equivalente a:</p>
<p><font color="#990000">SELECT id, title, body, created FROM pages WHERE id > 50 ORDER BY id DESC LIMIT 15;</font><br />
</p>
<p>El atributo $conditions (que es un array relacional), puede ser más complejo:</p>
<font color="#330099">$conditions = array("Author.name" => "Bob", "or" => array<br /> (<br /> "Page.title" => "LIKE %magic%",<br /> "Page.created" => "> " . date('Y-m-d', strtotime("-2 weeks")<br /> )<br />)</font>
<p>Existe un método de la clase Model: findbySQL() que permite hacer queries SQL, pero es un método depreciado que debemos evitar, pues el objetivo es pensar más en una arquitectura de objetos "pura" y menos en tablas.<br />
</p>
<p>El atributo <em>$page</em> de findAll() es muy útil para realizar la paginación de los resultados, mientras <em>$recursive</em> señala si la tabla tiene relaciones con otras tablas. Esto lo veremos a continuación.</p>
<p>Nota: CakePHP supone que la llave primaria en todas las tablas es el campo "id", pero si no es asi, hay que indicarlo en el modelo con la variable <strong>$primaryKey </strong></p>
<h1><strong>Relaciones (Binds)</strong><br />
</h1>
<p>Hasta ahora sólo hemos visto modelos de tablas de PostgreSQL solas, pero ¿cómo se combinan las tablas usando ActiveRecord? En SQL usaríamos un INNER JOIN o una selección múltiple:</p>
<p>$sql= "SELECT w.estilo, w.urlbase, w.nombre, w.email, w.keywords, w.author, p.titulo, p.disc FROM website as w, paginas as p WHERE p.id = " . $idp; </p>
<p>CakePHP usa relaciones o "binds" que se declaran en los modelos usando palabras (atributos) clave:</p>
<p><font face="couriernew,courier">$hasMany = ("tiene muchas") , es decir que tiene muchos registros en esa tabla, por ejemplo, el modelo User tiene muchas entradas en el modelo Photos. <br />
</font></p>
<p><font face="couriernew,courier">$hasOne = ("tiene uno") por ejemplo el modelo <strong>Automovil</strong> solo puede tener un registro en el modelo <strong>Motor</strong>, pues no puede tener dos o más </font></p>
<p><font face="couriernew,courier">$belongsTo = ("pertenece a") el modelo User es poseedor del modelo Project<br />
</font></p>
<p><font face="couriernew,courier">$hasAndBelongsToMany = ("tiene muchos y pertenece a muchos o HABTM") <-- en apariencia un poco caótico</font><br />
</p>
<p>Estos <strong>binds</strong> son una de las características más poderosas de CakePHP. Por ejemplo, veamos el modelo de la tabla 'pages':</p>
<p><strong>Archivo: app/models/page.php</strong><br />
</p>
<p><font color="#993300"> <span>class Page extends AppModel<br />
{</span> <br />
</font></p>
<p><font color="#993300"><span> public $name = 'Page';<br />
</span></font></p>
<p><font color="#993300"><span> public $belongsTo = 'User'; <br />
<br />
</span><span>public</span><span> $validate = array(<br />
'title' => '/[a-z0-9-]{3,}$/i',<br />
'body' => VALID_NOT_EMPTY,<br />
'email' => VALID_EMAIL,<br />
'born' => VALID_NUMBER<br />
);</span></font></p>
<p><font color="#993300">?> </font></p>
<p>La indicación de relación <span>$belongsTo ("pertenece a") indica que el modelo User es propietario de los datos que están en el modelo Page, por lo tanto la tabla 'pages' debe tener un campo user_id para vincularlo con 'users'. Esto es, debemos tener las tablas como sigue:</span></p>
<p><strong>Model: User</strong><br />
CREATE TABLE users (<br />
id serial PRIMARY KEY,<br />
username character varying(50) DEFAULT ''::character varying NOT NULL UNIQUE, --login<br />
passwd character varying(50) DEFAULT ''::character varying NOT NULL,<br />
name character varying(50) DEFAULT ''::character varying NOT NULL UNIQUE, --real name<br />
email character varying(45) DEFAULT ''::character varying NOT NULL UNIQUE,<br />
last_visit timestamp(0) with time zone DEFAULT now() NOT NULL,<br />
group_id integer NOT NULL, -- Admin, editor, normal user<br />
active integer DEFAULT 0 NOT NULL,<br />
created timestamp(0) with time zone DEFAULT now() NOT NULL<br />
);<br />
</p>
<p><strong>Model: Page</strong><br />
CREATE TABLE pages ( -- static pages <br />
id serial PRIMARY KEY,<br />
title varchar(90) NOT NULL,<br />
body text NOT NULL,<br />
created timestamp(0) with time zone DEFAULT now() NOT NULL,<br />
modified timestamp(0) with time zone DEFAULT now() NOT NULL,<br />
disc int NOT NULL DEFAULT 0, --discution (comments) actived<br />
state int NOT NULL DEFAULT 0,<br />
user_id int NOT NULL REFERENCES users(id) ON DELETE CASCADE<br />
);</p>
<p>Recuerda: en PostgreSQL, MySQL o Sqlite la tabla es en plural (pages) pero en CakePHP el modelo es singular: Page. Nota también que hay un campo <strong>user_id</strong> en la tabla "pages" pues lo que hay en Page pertenece al modelo User. <br />
</p>
<p>Ahora si podemos relacionar estos dos modelos:<br />
</p>
<p><font color="#330099"> $conditions = array("Page.state"=>1);<br />
$fields = array("id", "title", "body", "created", "user_id");<br />
$order = "Page.created DESC";<br />
$limit = 15;<br />
$page = 1; <br />
$recursive = 2; <br />
</font></p>
<p><font color="#330099"> $data = $Page->findAll($conditions, $fields, $order, $limit, $page, $recursive); </font></p>
<p>El valor de $recursive es importante pues indica a CakePHP que busque los datos en los "binds" del modelo Page. </p>
<p><strong>Tip:</strong> $data recoge el resultado de la consulta al modelo, usa: die(print_r($data)); y también die(var_dump($data)); en las vistas para ver que es lo que el array $data contiene.<br />
</p>
<h1>bind() y unbind()</h1>
<p> Los binds son soluciones poderosas, pero a veces necesitamos un diferente tipo de bind para cierto escenario o quizás la tabla tiene varios binds y solo necesitamos poca información de ella y no requerimos un query tan grande. </p>
<p>La clase Model posee los métodos $Model->unbind() y $Model->bind() que "desligan" y "religan" las asociaciones "al vuelo" en el controlador. Por ejemplo si usamos: </p>
<p> <span> $this->Gallery->unbindModel(array('hasMany' => array('Photo')));<br />
$this->set('data', </span><span>$this->Gallery->findAll()</span><span>);</span></p>
<p><span>Estro evitara que la información del modelo Photo relacionada con Gallery sea traida al hacer el findAll(), en lugar de ello sólo obtendremos la información contenida en el modelo Gallery. $Model->unbindModel() debe usarse cuando sea posible.<br />
</span></p>
<p><a href="http://www.mononeurona.org/pages/display/738"><< Capítulo Anterior</a> </p>",
"created" => "2007-04-29 10:56:59-05",
"discution" => 1,
"display" => 2,
"status" => 1,
"user_id" => 1,
"cv" => 1,
"visits" => 594,
"rank" => 7651,
"editor" => 1,
"updated" => "2009-08-20 00:32:14-05"
),
"User" => array(
"id" => 1,
"username" => "aarkerio",
"avatar" => "aarkerio_avatar.png"
),
"Section" => array(
"id" => 28,
"description" => "Server Side",
"img" => "sec-servidor.jpg"
),
"Discution" => array(
array(),
array(),
array()
)
)include - APP/View/Pages/display.ctp, line 30
View::_render() - CORE/Cake/View/View.php, line 598
View::render() - CORE/Cake/View/View.php, line 365
Controller::render() - CORE/Cake/Controller/Controller.php, line 900
Dispatcher::_invoke() - CORE/Cake/Routing/Dispatcher.php, line 114
Dispatcher::dispatch() - CORE/Cake/Routing/Dispatcher.php, line 89
[main] - APP/webroot/index.php, line 81
Ver todos los articulos de aarkerio