<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2spanishfull.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0" xml:base="http://planetcakephp.org">
<channel>
 <title>Planet CakePHP - Español</title>
 <link>http://planetcakephp.org/taxonomy/term/5/0</link>
 <description />
 <language>en</language>
<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/planet-cakephp-aggregator-spanish" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="planet-cakephp-aggregator-spanish" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fplanet-cakephp-aggregator-spanish" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.bloglines.com/sub/http://feeds.feedburner.com/planet-cakephp-aggregator-spanish" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2Fplanet-cakephp-aggregator-spanish" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2Fplanet-cakephp-aggregator-spanish" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2Fplanet-cakephp-aggregator-spanish" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://add.my.yahoo.com/content?lg=es&amp;url=http%3A%2F%2Ffeeds.feedburner.com%2Fplanet-cakephp-aggregator-spanish" src="http://eur.i1.yimg.com/eur.yimg.com/i/es/my/addto1.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" href="http://www.feedness.com/alta/http://feeds.feedburner.com/planet-cakephp-aggregator-spanish" src="http://www.feedness.com/ayuda/wp-content/square_b_sh_feed.gif">Subscribe with Feedness</feedburner:feedFlare><item>
 <title>HTML5 reset</title>
 <link>http://cakephpilia.blogspot.com/2010/08/html5-reset.html</link>
 <description>&lt;p&gt;Se trata de todo un paquete completo de archivos html, css y javascript con los cuales iniciar un proyecto desarrollado en HTML 5, compatible con Internet Explorer.Tiene muy buena pinta para empezar y no parece muy difícil de integrar en CakePHP.&lt;a href="http://html5reset.org/"&gt;HTML5 Reset&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/JniLHqvn6Fg" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Wed, 11 Aug 2010 17:39:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5485 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Hacer tests de un método que crea un objeto que debemos simular (una alternativa a los partial Mock)</title>
 <link>http://cakephpilia.blogspot.com/2010/07/hacer-tests-de-un-metodo-que-crea-un.html</link>
 <description>&lt;p&gt;Supongamos que tenemos un método en una clase y que dentro de ese método se crea un objeto, el cual debemos simular para hacer el test. Algo así como esto:public function getFeed($url) {    $Socket = ClassRegistry::init('HttpSocket');    $Socket-&amp;gt;reset(false);    $response = $Socket-&amp;gt;get($url);    ...}En la &lt;a href="http://www.simpletest.org/en/partial_mocks_documentation.html"&gt;documentación de SimpleTest se analiza ese caso y se proponen varias soluciones&lt;/a&gt;. Pero en el caso de CakePHP he descubierto una que es sencilla y resuelve el problema de una forma muy eficaz y elegante.HttpSocket es una clase muy a propósito para ser simulada, ya que requiere conectarse a un servidor y no podemos garantizar que sea posible hacerlo en la situación de test, por lo que lo lógico sería usar la simulación. Pero tal como está escrito el método, el objeto se crea y se utiliza (e incluso se destruye) en el ámbito del propio método. En la interfaz de éste no hay forma de pasar el objeto, como se puede ver, por lo que habría que reescribir el código para poder escribir el test.ClassRegistry al rescatePara empezar, tendremos que instanciar el objeto con ClassRegistry en lugar de con el tradicional new object() de PHP. ClassRegistry es una factoría de clases que ofrece algunos servicios interesantes. Además de crear los objetos, mantiene un registro, de modo que si una clase es instanciada varias veces, y no indicamos lo contrario, no crea un objeto nuevo cada vez, sino que devuelve el existente, ahorrando memoria.Esto se hace con el método init, de esta forma:$Post = ClassRegistry::init('Post');Con este método se crea una entrada en el registro que pone la clase Post bajo la clave 'Post', devolviendo un objeto de clase Post por referencia.Pero ClassRegistry tiene otro método que nos interesa: addObject. Este método nos permite poner en el registro cualquier objeto bajo la clave que deseemos.ClassRegistry::init('Post', $MockedPost);Para el caso que nos ocupa podemos crear el Mock que necesitamos, instanciarlo y pasarlo a ClassRegistry con la clave de la clase que estamos simulando. La próxima vez que se llame a ClassRegistry::init, devolverá el Mock.El siguiente bloque de código muestra cómo hacerlo:Mock::generate('HttpSocket');$Socket = ClassRegistry::init('MockHttpSocket');ClassRegistry::addObject('HttpSocket', $Socket);La primera línea genera la simulación de HttpSocket con el nombre MockHttpSocket.La segunda línea instancia la clase simulada, no es imprescindible usar ClassRegistry, pero tampoco es mala práctica.Por último, la tercera línea, hace la magia, añadiendo el objeto que acabamos de instanciar en la clave HttpSocket del registro.Lo anterior es la preparación. A continuación habría que establecer los valores de respuesta que necesitemos, etc.Luego vendría la llamada para probar el método:$result = $this-&amp;gt;Feed-&amp;gt;getFeed($url);Este es, de nuevo, el fragmento del método que quería probar:public function getFeed($url) {    $Socket = ClassRegistry::init('HttpSocket');    $Socket-&amp;gt;reset(false);    $response = $Socket-&amp;gt;get($url);    ...}Al "inyectar" en ClassRegistry la clase simulada MockHttpSocket bajo la clave HttpSocket consigo que en situación de test se utilice el Mock.Y todo ello sin tocar el código de la clase.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/WTzsq6fQ3ec" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Mon, 19 Jul 2010 23:04:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5439 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Simulando objetos con Mock Objects</title>
 <link>http://cakephpilia.blogspot.com/2010/06/simulando-objetos-con-mock-objects.html</link>
 <description>&lt;p&gt;Entre otras facilidades, la Test Suite de CakePHP utiliza la capacidad de SimpleTest de crear Mock Objects, o lo que es lo mismo, simulaciones de objetos que no hacen nada, pero que reproducen todos los métodos de los objetos simulados y que puedes programar para que tengan un comportamiento determinado que te interese.Esto es útil cuando quieres probar una clase que usa otras clases. En lugar de emplear las clases reales, utilizamos sus simulaciones. De este modo, nos evitamos las posibles interferencias de su funcionamiento sobre los resultados del test y garantizamos que sólo probamos el código de la clase en cuestión.Un ejemplo sencillo típico sería tratar de probar un Controller que usa un Component.Lo que tenemos que hacer son básicamente dos cosas:
&lt;ul&gt;
&lt;li&gt;Crear una simulación o Mock del Component&lt;/li&gt;
&lt;li&gt;Asociar el Component simulado al Controller&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;p&gt;Para crear una simulación de una clase usamos Mock::generate('Clase'). Esto nos crea una clase que podemos instanciar para asociar a la clase testada.Imagina que quieres probar PostsController, habiendo declarado Auth como Component. El código necesario sería algo así como:App::import('Controller', 'Posts');App::import('Component', 'Auth');class PostsTestCase extends CakeTestCase {       var $Posts;       function startTest() {        $this-&amp;gt;Posts = ClassRegistry::init('PostsController');        Mock::generate('AuthComponent');        $this-&amp;gt;Posts-&amp;gt;Auth = new MockAuthComponent();    }}?&amp;gt;El código de startTest hace lo siguiente:En primer lugar, instancia PostsController y lo pone en la variable Posts de PostsTestCase, así lo tenemos disponible en cualquiera de los métodos del test.Seguidamente, se genera el Mock de AuthComponent.Finalmente, asociamos el AuthComponent simulado a $this-&amp;gt;Posts, de manera que haga el papel que haría el AuthComponent real.Si fuese necesario, tendríamos que simular o hacer a mano cualquier otra operación que fuese necesaria para reproducir el funcionamiento normal de las clases implicadas.Los métodos de AuthComponent están simulados en MockAuthComponent, pero no hacen nada. Para que el Mock sea útil necesitaremos indicarle que devuelva ciertos datos al llamar a alguno de sus métodos. Para esto utilizamos el método setReturnValue(), de este modo:...$this-&amp;gt;Posts-&amp;gt;Auth-&amp;gt;setReturnValue('user', array('User' =&amp;gt; array(...)));...La línea anterior le indica al objeto simulado que cuando se llame a su método 'user' devuelva el valor indicado. Gracias a esto podemos simular determinados escenarios que nos interesa probar.Los parámetros que pueda tener el método son indiferentes en este caso. Sin embargo, es posible indicar a setReturnValue que tenga en cuenta los argumentos del método (esto lo dejaré para otro artículo, pero lo puedes encontrar en la documentación de SimpleTest).Por ejemplo, ¿qué pasa si no hay un usuario autentificado en la acción edit? Para hacerlo, necesitamos indicarle al Auth simulado, que el método 'user' devuelva false:...$this-&amp;gt;Posts-&amp;gt;Auth-&amp;gt;setReturnValue('user', false);$result = $this-&amp;gt;Posts-&amp;gt;edit(123);...Con el código anterior conseguimos simular que Auth-&amp;gt;user no devuelve ningún valor (no hay usuario autentificado) y así probamos cómo responde nuestro método a esa situación. Seguidamente podemos hacer otro test, simulando que sí existe un usuario autentificado.Si fuese necesario podemos "anidar" objetos simulados. Es decir, si tenemos que simular una clase que contiene otras clases, podemos simular estas y asociarlas.Para simular propiedades (variables de clase) no tenemos más que asignarles el valor deseado.Ahora, supón que un método de un objeto simulado es llamado varias veces en el método probado y necesitas que cada vez devuelva un valor distinto. En este caso, debes pasarle primero los valores con setReturnValueAt.$objeto-&amp;gt;setReturnValue('value', false);$objeto-&amp;gt;setReturnValueAt(0, 'value', 'Sample');$objeto-&amp;gt;setReturnValueAt(1, 'value', 'Another sample');En resumen, utilizando Mock Objects pueden prescindir de las dependencias de la clase probada, lo que hará que tus tests sean más flexibles, fiables y precisos.&lt;a href="http://www.simpletest.org/en/mock_objects_documentation.html"&gt;Documentación sobre Mock Objects en Simple Test&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/F7OfY6VpMjs" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Wed, 09 Jun 2010 22:23:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5345 at http://planetcakephp.org</guid>
</item>
<item>
 <title>BeforeRedirect</title>
 <link>http://cakephpilia.blogspot.com/2010/06/beforeredirect.html</link>
 <description>&lt;p&gt;Un recordatorio por si usas el callback beforeRedirect en un Component: recuerda que debe devolver una URL, de otro modo los resultados pueden ser absolutamente desquiciantes.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/e4YwmAyBMZo" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Wed, 09 Jun 2010 21:40:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5333 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Model-&gt;displayField en plugins</title>
 <link>http://cakephpilia.blogspot.com/2010/06/model-displayfield-en-plugins.html</link>
 <description>&lt;p&gt;Al menos en CakePHP 1.3 (me imagino que puede pasar en 1.2) la propiedad displayField de los modelos puede tener un comportamiento un poco errático si te has olvidado de especificar correctamente las relaciones con modelos que están en plugins.En otras palabras. Si tienes un modelo relacionado con otro que se encuentra en un plugin, no olvides indicarlo "prefijándolo" con el nombre del plugin. Por ejemplo, si tienes un modelo que tiene una relación hasMany con el modelo Item que está en el plugin Contents debes expresarlo así:var $hasMany = array('Item' =&amp;gt; array('className' =&amp;gt; 'Contents.Item'));Si no lo haces, métodos como find('list') no serán capaces de usar correctamente la propiedad displayField del modelo relacionado.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/pTCxRWznhSY" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Wed, 02 Jun 2010 14:58:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5304 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Yo, la autorización y Cake (II): Entendiendo los sistemas de autorización</title>
 <link>http://cakephpilia.blogspot.com/2010/05/yo-la-autorizacion-y-cake-ii.html</link>
 <description>&lt;p&gt;Sistemas de permisos "a la Unix"Una forma de afrontar al problema de la autorización es partir del sistema de permisos Unix. En este sistema, cada recurso tiene asociados varios permisos (lectura, escritura, ejecución) que se asignan a un usuario, a un grupo y al resto del mundo. Es bastante fácil empaquetar estos permisos en un sólo atributos y chequearlos usando operaciones binarias (and, or...).Puedes incorporar esta información de permisos en la propia tabla del modelo que quieres controlar, o normalizarlo y guardar la información en una tabla de permisos, lo que te permite hacerlo más flexible.La dificultad puede estar en cómo relacionar esta información de permisos con las acciones de los controladores de tu sistema. Una opción es a través de un mapeado acción-tipo de permiso, o bien creando un permiso específico para cada acción.En el primer caso, las acciones se agruparían en si son de lectura, escritura o cualquiera de los tipos básicos de permiso que hayas establecido.En el segundo caso, tendrías que registrar cada acción de un controlador y relacionarla con el recurso y los usuarios con acceso.El componente ACL de Cake permite una forma de uso que utiliza un sistema de permisos de este estilo.Sistemas basdos en Listas de Control de Acceso (ACL)ACL son las siglas de Access Control List. Las listas de control de acceso son, como su nombre indica, listas que relacionan a los sujetos con objetos. Es decir, nos dicen quién tiene acceso a qué en el sistema, o quién tiene prohibido el acceso a qué.Normalmente, el quién son los usuarios, que pueden estar organizados en grupos. En ese caso, si un grupo tiene ciertos privilegios, sus miembros los heredan gracias a la estructura en árbol de las ACL proporcionadas por CakePHP. Esto nos permite no tener que asignar permisos de forma individual a cada usuario (lo que en una organización grande puede ser un trabajo ímprobo), sino que podemos definirlos con una cantidad mínima de reglas o entradas en la lista de control.A estos sujetos que Reclaman acceso se les llama técnicamente AROs (Access Request Objects: Objetos que Reclaman Acceso).El qué son los recursos y objetos de la aplicación. En este caso el concepto es un poco más difuso, ya que los recursos pueden ser de diversos tipos. Por ejemplo, en una aplicación de blogs podemos pensar que los posts son recursos. Pero también son recursos las acciones, o sea, las url de la aplicación a las que acceden los usuarios.A lo objetos cuyo acceso Controlamos se les llama técnicamente ACOs (Access Controlled Objects: Objetos de Acceso Controlado).En resumen:
&lt;ul&gt;
&lt;li&gt;ACL: Lista de control de acceso.&lt;/li&gt;
&lt;li&gt;ARO: Sujeto que reclama acceso, habitualmente usuarios o grupos (pero no necesariamente).&lt;/li&gt;
&lt;li&gt;ACO: Objetos a los cuales los ACO quieren acceder.&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;p&gt;En consecuencia, una ACL es una lista en la que definimos si un ARO puede o no acceder a un ACO. El sistema de autorización consulta esa lista y toma una decisión en función de los datos obtenidos.Esto es lo que te proporciona CakePHP con el componente ACL.Sistemas basados en Roles (RBAC)Sin embargo, las ACL "puras" no son el único sistema de control de autorización. Otra metodología la componen los sistemas de acceso basados en roles.Un rol define lo que los usuarios que ejercen ese rol pueden hacer en el sistema. Un mismo usuario puede tener varios roles, definidos por el administrador del sistema.Hay un par de roles que podríamos considerar comunes en la base de todo sistema:
&lt;ul&gt;
&lt;li&gt;self: la capacidad de los usuarios para editar los datos de su propio perfil o cuenta.&lt;/li&gt;
&lt;li&gt;root: capaz de acceder a cualquier recurso&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;p&gt;En cierto modo, se puede pensar en los sistemas basados en roles como en otra forma de interpretar la idea de las listas de control de acceso. La diferencia es que, mientras que las ACL tradicionales suelen responder a la pregunta "¿Puede el usuario U hacer lo que solicita", los sistemas basados en roles responden más bien a "¿Qué puede hacer el usuario U estando aquí?".Poder hacer esta pregunta y, sobre todo, poder obtener una respuesta es bastante útil. Entre otras cosas, te permite exponer al usuario sólo aquella parte de la aplicación a la que tiene acceso.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/JaT6zAnq34E" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Sat, 15 May 2010 14:53:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5235 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Yo, la autorización y Cake (I)</title>
 <link>http://cakephpilia.blogspot.com/2010/05/yo-la-autorizacion-y-cake-i.html</link>
 <description>&lt;p&gt;Una de las bases de una aplicación corporativa es un buen sistema de autentificación y de autorización. Este último es uno de los aspectos que me resulta más complicado resolver al desarrollar una aplicación. Hasta ahora mis soluciones en este campo han sido funcionales, pero difíciles de mantener y de escalar.Conceptualmente son procesos bastante sencillos de entender:Autentificación es el proceso por el que el sistema determina que un usuario es quien dice ser, o por lo menos que presenta las credenciales correctas. Un sistema de autentificación básicamente toma las credenciales aportadas (habitualmente un usuario y contraseña) y las contrasta con alguna fuente de referencia, que puede ser una tabla de una base de datos, un servidor de autentificación, etc.Autorización es el proceso por el cual se controla qué puede hacer en el sistema el usuario autentificado, es decir: a qué recursos puede acceder. Un sistema de autorización comprueba si el usuario tiene permiso para realizar las acciones que solicita sobre ciertos recursos disponibles, para lo cual consulta alguna fuente de referencia, como atributos de permisos en los recursos, listas de control de acceso, reglas, etc.CakePHP proporciona algunas herramientas para gestionar estos procesos y construir nuestros sistemas de autentificación y autorización:
&lt;ul&gt;
&lt;li&gt;Componente Auth. Proporciona una forma sencilla de integrar el control de identidad en nuestro desarrollo, permitiendo también bastante flexibilidad.&lt;/li&gt;
&lt;li&gt;Componente ACL. Se trata de un sistema genérico que nos permite construir listas de control de acceso jerárquicas y usarlas para comprobar si un usuario tiene capacidad de acceder al recurso que solicita.&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;p&gt;El componente Auth funciona muy bien y es muy fácil de integrar en nuestros sistemas. No voy a extenderme en él porque hay buenos tutoriales tanto en el CookBook como en otras fuentes.Sin embargo, el componente ACL suele ser considerado como más difícil de usar. Creo que esto es debido a varias razones. Al menos estas son las que yo he identificado que me han impedido utilizarlo con éxito hasta ahora:
&lt;ul&gt;
&lt;li&gt;No tener claro su fundamento y su funcionamiento, para saber qué esperar del sistema y cómo utilizarlo&lt;/li&gt;
&lt;li&gt;Es muy genérico. No está diseñado para un tipo de uso específico, sino que hay que vincularlo a las entidades que van a intervenir en el proceso de autorización y mantenerlo sincronizado&lt;/li&gt;
&lt;li&gt;Se estructura en torno a un árbol jerárquico que impone algunas limitaciones, como que un mismo usuario no puede estar a la vez en varios grupos&lt;/li&gt;
&lt;li&gt;Un problema que no resulta fácil de resolver es cómo determinar los recursos a los que tiene acceso un usuario en un momento dado. Me explico. Si diseñamos un sistema basado en ACL que nos permita saber si un usuario puede ejecutar, o no, una acción, nos queda resolver el problema de a qué objetos puede acceder dentro de ella.&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/nMNxhpE0AAI" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Sat, 15 May 2010 14:50:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5236 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Forzar la actualización de un registro de un Model</title>
 <link>http://cakephpilia.blogspot.com/2010/05/forzar-la-actualizacion-de-un-registro.html</link>
 <description>&lt;p&gt;Mi vida como desarrollador es un poco caótica, ya que no me dedico a tiempo completo y últimamente otras demandas me quitan montones de tiempo y toneladas de concentración. Por eso, a pesar de llevar trabajando con CakePHP desde hace ya un par de años, tengo dudas en conceptos básicos o tengo que empezar desde cero con algunos proyectos ya que "pierdo el hilo".Un ejemplo de estos conceptos básicos que tenía dudosos es lo que pasa cuando creas registros en un modelo y cuando manipulas sus id.Cuando haces un Model-&amp;gt;save() sin indicar un id para el modelo, se crea un nuevo registro. Por tanto, para actualizar un registro existente debes indicar un id.Esto lo puedes hacer tanto en la propiedad Model-&amp;gt;id, como en el array de datos que pases a create o a save. Por ejemplo:array('Model' =&amp;gt; array('id' =&amp;gt; 12));Además, se siguen unas cuantas reglas más:* Si Model-&amp;gt;id tiene un valor y en el array de datos se pasa un valor nuevo, prevalece éste último.* Si el id que has pasado no existe en la base de datos, se crea un registro nuevo con este idSaber esto es interesante cuando nos interesa controlar la creación de id's mediante un sistema propio.Por cierto, ¿qué pasa si nuestro id es de tipo integer pero no tiene auto_increment y no lo especificamos en el Model? Resulta que CakePHP es capaz de emular el auto_increment.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/QFpd2IaKP4A" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Thu, 13 May 2010 10:04:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5222 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Pregunta abierta: ¿conoces alternativas a getID3?</title>
 <link>http://cakephpilia.blogspot.com/2010/04/pregunta-abierta-conoces-alternativas.html</link>
 <description>&lt;p&gt;Funciona bien y td eso, ¿pero conoces alguna alternativa para extraer metadatos de archivos multimedia?&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/q_O5yZkLui8" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Wed, 28 Apr 2010 06:42:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5166 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Obtener la extensión de un archivo</title>
 <link>http://cakephpilia.blogspot.com/2010/04/obtener-la-extension-de-un-archivo.html</link>
 <description>&lt;p&gt;Esta línea nos proporciona la extensión de un archivo conociendo su nombre o su path$extension = substr($filename, strrpos($filename, '.') + 1);&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/-9c4XMGdvoI" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Wed, 21 Apr 2010 08:12:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5120 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Valores por defecto para parámetros de un Element</title>
 <link>http://cakephpilia.blogspot.com/2010/04/valores-por-defecto-para-parametros-de.html</link>
 <description>&lt;p&gt;Esta es una técnica sencilla que nos servirá para definir valores por defecto (y también documentarlos) para los parámetros que pasamos a un element.Como sabrás, los parámetros pasados a los element, están disponibles como variables en el mismo. La idea es definir un array asociativo con los valores por defecto y luego extraer las claves a variables.El quid de la cuestión es usar el parámetro EXTR_SKIP para que extract no extraiga del array los parámetros que han sido pasados. Es decir, en caso de conflicto, el Element debe quedarse con el parámetro pasado y si no ha sido pasado, toma el valor por defecto.Esto nos evita una serie de if(isset(...)), y hace muy legible el código y muy fácil documentar los parámetros del Element.&lt;code&gt;$defaults = array(&lt;/code&gt;&lt;code&gt;    'channel' =&amp;gt; false,    'count' =&amp;gt; 1,    'mode' =&amp;gt; 'public',    'type' =&amp;gt; 'full'    );   extract($defaults, EXTR_SKIP);&lt;/code&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/sUy3SePlYho" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Thu, 15 Apr 2010 09:40:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">5080 at http://planetcakephp.org</guid>
</item>
<item>
 <title>CakePHP, MAMP y el socket de Mysql (actualizado)</title>
 <link>http://cakephpilia.blogspot.com/2010/03/cakephp-mamp-y-el-socket-de-mysql.html</link>
 <description>&lt;p&gt;ActualizaciónComo comenta Nigeon, basta con poner el path al socket en el parámetro port de la configuración de la conexión.Al que, por cierto, &lt;a href="http://book.cakephp.org/view/40/Database-Configuration"&gt;viene documentado en el manual&lt;/a&gt;.'connect' =&amp;gt; 'mysql_connect','host' =&amp;gt; 'localhost','port' =&amp;gt; '/Applications/MAMP/tmp/mysql/mysql.sock',Dejo la entrada anterior porque puede ser útil en algunos casos.En una entrada anterior ya he comentado el tema de como &lt;a href="http://cakephpilia.blogspot.com/2007/09/cake-bake-y-mamp.html"&gt;ajustar las cosas para que CakePHP pueda comunicarse con Mysql&lt;/a&gt;. Para ello, hay que crear un enlace simbólico del socket /Applications/MAMP/tmp/mysql/mysql.sock en el lugar adecuado, que en el artículo señalado era /var/mysql/mysql.sock.Hace poco, tras varias actualizaciones los shells empezaron a "pedir" un socket en /tmp/mysql.sock, por lo que creé un nuevo enlace, pero olvidé la opción -s y creé un enlace duro en lugar de simbólico.Pues bien, que sepas que los enlaces duros no valen para el caso y los shells no eran capaces de conectar a la base de datos. Ha sido cambiarlo a enlace simbólico y volver a funcionar todo como es debido.Por su parte, la aplicación web se conectaba perfectamente.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/Y_ajBOGZDzU" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Sun, 07 Mar 2010 22:14:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4813 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Validación: comparar con otro campo</title>
 <link>http://cakephpilia.blogspot.com/2010/02/validacion-comparar-con-otro-campo.html</link>
 <description>&lt;p&gt;En la validación de datos de formularios, muchas veces nos gustaría comprobar que dos campos coinciden, como pueden ser los casos de contraseñas, emails, etc.Para ello, en los formularios ponemos un segundo campo similar al que queremos comparar a fin de que el usuario introduzca dos veces el valor. Así, por ejemplo, tendríamos el campo User.password y el campo User.confirm_password, o bien User.emal y User.confirm_email.Yo recomendaría que en el formulario usaras los campos de confirmación como si fuesen campos del modelo. A la hora de guardar (save), CakePHP los va a ignorar pues no están en el schema del modelo y nos resulta bastante fácil manipularlos.No hay una regla de validación por defecto para esto, así que me he escrito mi &lt;a href="http://www.assembla.com/code/milhojas/subversion/nodes/app_model.php#ln67"&gt;propio método match&lt;/a&gt; para hacerlo.La comparación de contraseñasUna de las cuestiones problemáticas tiene que ver si usas campos de contraseñas y el AuthComponent, ya que éste intercepta los datos del formulario antes de que lleguen al sistema de validación por lo que el campo de contraseña original vendrá transformado por un hash, mientras que el campo de confirmación no (normalmente). En consecuencia, es imposible que coincidan ambos campos.Es decir, los campos van a llegar más o menos así:['User']['password'] = 'a3f9b5c4....'['User']['confirm_password'] = 'miclave'Por eso, he añadido al método soporte para obtener el hash deseado del campo de confirmación antes de comparar los campos. Si no indicas nada, los campos serán comparados en bruto.UsoPara comparar un campo con otro:'campo' =&amp;gt; array('rule' =&amp;gt; array('match', 'otro_campo')Para comparar un campo transformado por un hash, indicamos el tipo de hash.'password' =&amp;gt; array('rule' =&amp;gt; array('match', 'confirm_password', 'sha1'))Si se usa AuthComponent no hay que olvidar que se debe añadir el valor de Salt, lo que indicamos así'password' =&amp;gt; array('rule' =&amp;gt; array('match', 'confirm_password', 'sha1', true))Aquí un ejemplo con otro tipo de hash, en este caso md5'password' =&amp;gt; array('rule' =&amp;gt; array('match', 'confirm_password', 'md5', true))&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/XrKpm3y0Wzk" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Sun, 28 Feb 2010 12:07:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4750 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Micro-dispatching in CakePHP 1.3</title>
 <link>http://feedproxy.google.com/~r/joselorenzo/~3/G3y0pumclPs/</link>
 <description>&lt;p&gt;One of the cool features CakePHP 1.3 introduced is the ability to have custom route classes to parse URLs. Mark Story has written a very good article showing the potential for this enhancement.&lt;br /&gt;
To be honest, I haven’t tried it until today, when I came up with a very crazy idea after reading this hilarious post [...]&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/Fp-kuWy4oK0" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Wed, 24 Feb 2010 03:54:53 +0000</pubDate>
 <dc:creator>José Lorenzo</dc:creator>
 <guid isPermaLink="false">4707 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Lögica de negocio y lógica de aplicación</title>
 <link>http://cakephpilia.blogspot.com/2010/02/logica-de-negocio-y-logica-de.html</link>
 <description>&lt;p&gt;Cuando se habla del patrón Modelo-Vista-Controlador a veces no queda claro cuál es el papel del Controlador.Veamos:El Modelo lleva la lógica de negocio, que básicamente es la representación de las entidades que la aplicación ha de manejar y sus relaciones. Así en una aplicación de gestión de bibliotecas tendremos modelos para los libros, socios, préstamos, etc., así como métodos para prestar libros, devolverlos, y otros muchos.La Vista lleva la lógica de presentación y cada vista sabe mostrar unos datos que recibe y nada más.¿Y el controlador? Solemos decir que el controlador pide datos al modelo y se los pasa a la vista, pero eso creo que no deja suficientemente claro su papel.El controlador se encarga de la lógica de la aplicación. Esta lógica es la que representa lo que ha de hacer el sistema para solicitar los datos adecuados al modelo y qué hacer con ellos. Eso incluiría también saber si hay un usuario autorizado en la sesión, si hay variables del entorno que se deban tener en cuenta (como límites de paginación, variables de sesión y otros), saber qué hacer si no se obtienen datos del modelo, etc.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/T8hlcObG4Yk" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Mon, 01 Feb 2010 22:19:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4483 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Una idea para no redirigir tanto</title>
 <link>http://cakephpilia.blogspot.com/2010/01/una-idea-para-no-redirigir-tanto.html</link>
 <description>&lt;p&gt;Estaba escribiendo el esqueleto de unas acciones para gestionar el registro y confirmación de usuarios cuando me di cuenta de que hay situaciones en las que puede ser buena idea usar Controller-&amp;gt;render() para dirigir al usuario a páginas que le informen sobre el resultado de sus acciones, en lugar de Controller-&amp;gt;redirect() para hacer lo mismo.Por ejemplo:&lt;code&gt;function register() {&lt;/code&gt;&lt;code&gt; if ($this-&amp;gt;data) {  if ($this-&amp;gt;User-&amp;gt;register($this-&amp;gt;data)) {   $this-&amp;gt;render('registration_ok');  } }}&lt;/code&gt;En esta acción, si el registro se hace correctamente, la acción muestra la vista 'registration_ok', que mostraría un mensaje explicando al usuario que su registro ha sido correcto y lo que debe o puede hacer a continuación.En otro caso, se mostraría la vista por defecto 'register'.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/FaOVZHzgkCA" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Sun, 31 Jan 2010 00:21:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4472 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Paginación y find personalizado. Un problema y ¿una solución?</title>
 <link>http://cakephpilia.blogspot.com/2010/01/paginacion-y-find-personalizado-un.html</link>
 <description>&lt;p&gt;Uno de los temas más habituales en los foros relacionados con CakePHP es el de la paginación de resultados. Surgen bastantes problemas cuando queremos ir un poco más allá de lo básico.En mi caso, el problema que más fastidiado me tiene es la paginación con métodos find personalizados, ya que, por lo que he podido descubrir, cuando CakePHP calcula el número de registros totales no construye la petición correcta y, con frecuencia, ésta falla y o bien no devuelve la cuenta correcta o incluso el SQL falla porque no se incluyen las columnas adecuadas.Esto es especialmente molesto cuando usas los métodos personalizados para hacer búsquedas a través de tablas relacionadas, por ejemplo, forzando los join necesarios, o añadiendo claves 'contain'.Paginación y find personalizadosEs fácil paginar con métodos find personalizados. Basta indicarlo en la variable del controlador paginate, bajo la clave del modelo:&lt;code&gt;$this-&amp;gt;paginate['Modelo'][0] = 'metodo';&lt;/code&gt;Controller-&amp;gt;paginate() hace una llamada a find('count') para obtener la cuenta total de registros y luego a find('el_metodo_que_sea') con los parámetros adecuados para obtener una página de datos. El problema es que cuando llama a find('count') éste recibe las condiciones que se hayan establecido en el controlador (a través de la variable paginate, por ejemplo) e ignora las modificaciones de la Query que se hacen en el método find personalizado.Con frecuencia eso significa que la petición ya fracasa en la base de datos, por errores como "columna desconocida", y en el mejor de los casos nos hace una cuenta incorrecta, como por ejemplo, todos los registros de la tabla, cuando nosotros queremos seleccionar sólo los que tienen el campo "active" igual a 1 o cualquier otra condición que hayamos establecido en el método personalizado.Por ejemplo, yo suelo utilizar mucho los joins en los métodos personalizados para evitar cargar el controlador con querys muy extensas. Por desgracia esto hace que la paginación "estándar" no sirva y haya que recurrir a otros métodos.Métodos alternativos de contarGeneralmente se recomienda sobreescribir los métodos del modelo Model-&amp;gt;paginate y Model-&amp;gt;paginateCount, cosa que no me hace mucha gracia, porque precisamente una de las ventajas de los métodos find personalizados sería disponer de una batería de búsquedas típicas y tener que manejar cada caso en un método del modelo me da la impresión de que favorece un código confuso.Puesto que personalizar los find es una "buena práctica", pienso que debería estar contemplado en Cake que se puedan usar en paginate con todas las consecuencias.Solución 1Mariano Iglesias propuso hace tiempo &lt;a href="http://marianoiglesias.com.ar/cakephp/pagination-with-custom-find-types-in-cakephp/"&gt;una solución&lt;/a&gt;, que no está mal. Consiste en guardar en un array los diferentes conjuntos de opciones para cada tipo de find y sobreescribir el método genérico find en AppModel. En este método se harían dos cosas:En primer lugar, cuando se realiza un find personalizado, se mezclan mediante Set::merge las opciones predefinidas y las que se pasan en la llamada. Luego se ejecuta un find('all') con las nuevas opciones.Cuando la llamada se hace desde Controller-&amp;gt;paginate, también se efectúa una llamada a find('count'). La parte que nos interesa es que en esta llamada también se pasa el nombre del método personalizado, de modo, que se consulta el array de opciones y se pasa la información a find('count') para que pueda construir el SQL correcto.Lo mejor es que &lt;a href="http://marianoiglesias.com.ar/cakephp/pagination-with-custom-find-types-in-cakephp/"&gt;eches un vistazo al código de Mariano&lt;/a&gt;.Solución 2Buceando en el código llegué a la conclusión de que hay otra vía "prometedora".  La verdad es que había leído el artículo de Mariano Iglesias por encima y no me había parado a estudiarlo a fondo, lo que a lo mejor me hubiese ahorrado bastante trabajo, ya que en realidad el enfoque es muy parecido: se trata de asegurarse de que find('count') recibe las opciones correctas para construir su query.El método que pide la cuenta de registros a la base de datos es Model-&amp;gt;_findCount(), el cual toma recibe la query solicitada y la convierte en una query de recuento.Éste método "sabe" si la petición va a ir seguida de una llamada a otro método find y de cuál (en las opciones recibe una clave 'type' que contiene esa información). El caso es que actualmente, _findCount() no hace nada con ese dato, por lo tanto, si nuestro método personalizado modifica la query de un modo u otro, no lo tiene en cuenta.Por otro lado, uno de los sistemas para crear find personalizados se basa en métodos con dos pasadas. En la primera pasada se modifica la query, y devuelve la modificada, y en la segunda se procesan los resultados.Así que podemos saber cuál es la query que se ejecutará "preguntándole" al método adecuado por ella. En principio, esto se puede hacer desde el _findCount. Por lo que con unas pocas líneas se consigue que éste tome exactamente la query adecuada.Y &lt;a href="http://code.assembla.com/milhojas/subversion/nodes/app_model.php"&gt;puedes ver el método _findCount() modificado en mi AppModel&lt;/a&gt;.Aparte de eso, envié la idea para ver si se puede hacer algo en el código del core. &lt;a href="http://cakephp.lighthouseapp.com/projects/42648/tickets/182-paginate-and-custom-find-failing-count"&gt;Puedes ver la discusión en este ticket&lt;/a&gt;.ProblemasAmbas soluciones tienen algunos problemas.El principal de ellos es que puede ocurrir que el find personalizado haga alguna cosa más que modificar la query (es la objeción que pone Mark Story en la discusión del ticket que puse) y puesto que llamaríamos al método dos veces (en la misma fase) podemos tener resultados indeseados.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/SfuwPzQTe54" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Sun, 17 Jan 2010 21:15:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4358 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Join con relaciones muchos a muchos</title>
 <link>http://cakephpilia.blogspot.com/2010/01/join-con-relaciones-muchos-muchos.html</link>
 <description>&lt;p&gt;En la entrada anterior dejaba pendiente el tema de las relaciones muchos a muchos, aunque es probable que ya te hayas dado cuenta de como combinar tablas relacionadas de esta manera.Este tipo de relaciones requiere una tabla intermedia (o join table) que nos permita asociar las parejas de registros. Las tablas izquierda y derecha se relacionan de uno a muchos con la join table. Por lo tanto tendremos que "unir" la tabla izquierda con la join table y ésta con la tabla derecha.Etiquetar librosVamos a seguir con nuestro ejemplo y vamos a añadir una tabla tags a nuestro sistema para poder etiquetar cada libro con diversas palabras clave descriptoras. Por ejemplo, así:&lt;code&gt;CREATE TABLE `tags` (&lt;/code&gt;&lt;code&gt;  `id` int(11) NOT NULL auto_increment,  `tag` varchar(200) default NULL,  PRIMARY KEY  (`id`)&lt;/code&gt;&lt;code&gt;)&lt;/code&gt;Y vamos a introducir algunos valores, para que la tabla quede así:+----+-----------------------+| id | tag                   |+----+-----------------------+|  1 | Novela                | |  2 | Lit. Castellana       | |  3 | Lit. Hispanoamericana | |  4 | Lit. Francesa         | |  5 | Poesia                | +----+-----------------------+5 rows in set (0,00 sec)Ahora creamos la join table:&lt;code&gt;create table books_tags (&lt;/code&gt;&lt;code&gt;books_id int(11) not null,&lt;/code&gt;&lt;code&gt;tags_id int(11) not null);&lt;/code&gt;Y la poblamos para relacionar nuestros libros con sus tags correspondientes, nos quedaría algo así:+----------+---------+| books_id | tags_id |+----------+---------+|        1 |       1 | |        2 |       1 | |        3 |       1 | |        4 |       1 | |        1 |       2 | |        4 |       2 | |        2 |       3 | |        3 |       4 | +----------+---------+8 rows in set (0,00 sec)Ahora podemos empezar a trabajar combinando las tablas. En realidad es muy simple: definimos una cláusula JOIN con cada una de las tablas que queremos unir. Por ejemplo:&lt;code&gt;select * from books join books_tags join tags;&lt;/code&gt;Esta query específicamente se puede abreviar usando ',' en vez de JOIN:&lt;code&gt;select * from books, books_tags, tags;&lt;/code&gt;La petición anterior nos devolverá el producto cartesiano de las tres tablas (nada menos que 160 filas). Ya que las tablas están relacionadas podemos usar las sentencias ON sobre los campos de clave primaria y clave foránea:&lt;code&gt;select title, tag &lt;/code&gt;&lt;code&gt;from books     join books_tags on books.id = books_tags.books_id &lt;/code&gt;&lt;code&gt;    join tags on books_tags.tags_id = tags.id;&lt;/code&gt;Es decir, unimos la tabla books con la books_tags cuando coinciden books.id y books_tags.books_id y ésta a su vez con la tabla tags, cuando coinciden books_tags.tags.id y tags.id.El resultado (lo he restringido a los campos title y tag para que se vea más claro):+---------------------+-----------------------+| title               | tag                   |+---------------------+-----------------------+| El quijote          | Novela                | | El quijote          | Lit. Castellana       | | 100 a?os de soledad | Novela                | | 100 a?os de soledad | Lit. Hispanoamericana | | El Principito       | Novela                | | El Principito       | Lit. Francesa         | | Lazarillo de Tormes | Novela                | | Lazarillo de Tormes | Lit. Castellana       | +---------------------+-----------------------+8 rows in set (0,00 sec)¿Podríamos meter a los autores en esta petición? Vamos a verlo (añado también el campo author y una cláusula para ordenar los registros a fin de apreciar mejor los resultados):&lt;code&gt;select title, author, tag &lt;/code&gt;&lt;code&gt;from books     join authors on books.author_id = authors.id     join books_tags on books.id = books_tags.books_id     join tags on books_tags.tags_id = tags.id order by title;&lt;/code&gt;+---------------------+----------------+-----------------------+| title               | author         | tag                   |+---------------------+----------------+-----------------------+| 100 a?os de soledad | Garc?a M?rquez | Lit. Hispanoamericana | | 100 a?os de soledad | Garc?a M?rquez | Novela                | | El Principito       | Saint_Exupery  | Novela                | | El Principito       | Saint_Exupery  | Lit. Francesa         | | El quijote          | Cervantes      | Novela                | | El quijote          | Cervantes      | Lit. Castellana       | +---------------------+----------------+-----------------------+6 rows in set (0,00 sec)Por supuesto, puedes usar los LEFT JOIN y RIGHT JOIN según tus necesidades, por ejemplo, para obtener el listado completo de libros y sus etiquetas:&lt;code&gt;select title, author, tag &lt;/code&gt;&lt;code&gt;from books     left join authors on books.author_id = authors.id     join books_tags on books.id = books_tags.books_id     join tags on books_tags.tags_id = tags.id order by title;&lt;/code&gt;En qué medida debes usar left o right join depende de si necesitas obtener todos los registros posibles de las tablas izquierda o derecha, o sólo aquellos que tienen datos en ambas tablas.Ahora veamos cómo podemos buscar libros que correspondan a una etiqueta. Empezamos por la combinación de tablas y luego no tenemos más que indicar en WHERE qué etiquetas queremos seleccionar:&lt;code&gt;select title, author, tag &lt;/code&gt;&lt;code&gt;from books     left join authors on books.author_id = authors.id     join books_tags on books.id = books_tags.books_id     join tags on books_tags.tags_id = tags.id where tags.tag = 'Novela'order by title;&lt;/code&gt;+---------------------+----------------+--------+| title               | author         | tag    |+---------------------+----------------+--------+| 100 a?os de soledad | Garc?a M?rquez | Novela | | El Principito       | Saint_Exupery  | Novela | | El quijote          | Cervantes      | Novela | | Lazarillo de Tormes | NULL           | Novela | +---------------------+----------------+--------+4 rows in set (0,00 sec)También puede ser otra etiqueta, claro. Por ejemplo, qué libros tenemos de literatura castellana:&lt;code&gt;select title, author, tag &lt;/code&gt;&lt;code&gt;from books     left join authors on books.author_id = authors.id     join books_tags on books.id = books_tags.books_id     join tags on books_tags.tags_id = tags.id where tags.tag = 'Lit. Castellana'order by title;&lt;/code&gt;Que nos dará este resultado:+---------------------+-----------+-----------------+| title               | author    | tag             |+---------------------+-----------+-----------------+| El quijote          | Cervantes | Lit. Castellana | | Lazarillo de Tormes | NULL      | Lit. Castellana | +---------------------+-----------+-----------------+2 rows in set (0,00 sec)Como puedes ver, aparte del pequeño lío que supone especificar las combinaciones de tablas a través de múltiples join el trabajo es bastante sencillo.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/muwBK6TcrWQ" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Fri, 08 Jan 2010 09:12:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4216 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Uniendo tablas con Join</title>
 <link>http://cakephpilia.blogspot.com/2010/01/uniendo-tablas-con-join.html</link>
 <description>&lt;p&gt;En la entrada anterior dejé caer que explicaría un poco más a fondo los tipos de JOIN que se pueden hacer y qué diferencias hay entre ellos.Una buena manera de entenderlo es practicando, por lo que es recomendable que crees algunas tablas sencillas y lances las querys como forma de ver en vivo los resultados de cada tipo de JOIN y así entender para qué casos te pueden servir. No hace falta que tengan muchos campos, ni muchos registros.Un buen ejemplo puede ser una tabla de libros y una de autores, como las que siguen (en este ejemplo estoy usando MySQL):&lt;code&gt; CREATE TABLE `books` (`id` int(11) NOT NULL auto_increment,`title` varchar(200) default NULL,`author_id` int(11) default NULL,PRIMARY KEY (`id`))CREATE TABLE `authors` (`id` int(11) NOT NULL auto_increment,`author` varchar(200) default NULL,PRIMARY KEY (`id`))&lt;/code&gt;Los datos para las tablas:&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;# Dump of table authors&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;# ------------------------------------------------------------INSERT INTO `authors` (`id`,`author`) VALUES ('1','Cervantes');INSERT INTO `authors` (`id`,`author`) VALUES ('2','García Márquez');INSERT INTO `authors` (`id`,`author`) VALUES ('3','Saint_Exupery');# Dump of table books# ------------------------------------------------------------INSERT INTO `books` (`id`,`title`,`author_id`) VALUES ('1','El quijote','1');INSERT INTO `books` (`id`,`title`,`author_id`) VALUES ('2','100 años de soledad','2');&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;INSERT INTO `books` (`id`,`title`,`author_id`) VALUES ('3','El Principito','3');&lt;/code&gt;Producto cartesianoPara entender cómo funcionan los JOIN tenemos que empezar repasando un concepto que muchos aprendimos en la Primaria: el &lt;a href="http://es.wikipedia.org/wiki/Producto_cartesiano"&gt;producto cartesiano&lt;/a&gt;. Ya sabes: dados dos conjuntos A y B, su producto cartesiano es otro conjunto C (A × B) formado por todos los pares ordenados en los que el primer elemento del par pertenece a A y el segundo elemento del par pertenece a B.Así, JOIN es básicamente el producto cartesiano de las tablas, es decir, una nueva tabla en que cada registro combina un registro de la primera tabla con cada uno de los registros de la segunda tabla.La query&lt;code&gt;SELECT * FROM books JOIN authors&lt;/code&gt;nos dará como resultado todas las posibles combinaciones de books y authors (en nuestro caso 9 registros). Algo así:+----+---------------------+-----------+----+----------------+| id | title               | author_id | id | author         |+----+---------------------+-----------+----+----------------+|  1 | El quijote          |         1 |  1 | Cervantes      | |  2 | 100 a?os de soledad |         2 |  1 | Cervantes      | |  3 | El Principito       |         3 |  1 | Cervantes      | |  1 | El quijote          |         1 |  2 | Garc?a M?rquez | |  2 | 100 a?os de soledad |         2 |  2 | Garc?a M?rquez | |  3 | El Principito       |         3 |  2 | Garc?a M?rquez | |  1 | El quijote          |         1 |  3 | Saint_Exupery  | |  2 | 100 a?os de soledad |         2 |  3 | Saint_Exupery  | |  3 | El Principito       |         3 |  3 | Saint_Exupery  | +----+---------------------+-----------+----+----------------+9 rows in set (0,03 sec)Como puedes suponer, hacer este resultado no es muy útil para este tipo de datos, aunque hay muchos casos en que si que lo puede ser.Por ejemplo, en un campeonato deportivo como una liga de fútbol tendremos una tabla equipos, que recoge el nombre de todos los equipos participantes.+----+------------+| id | equipo     |+----+------------+|  1 | Barcelona  | |  2 | Madrid     | |  3 | Celta      | |  4 | Villarreal | +----+------------+4 rows in set (0,02 sec)Pues bien, un JOIN de la tabla consigo misma, nos permitirá obtener todos los partidos del campeonato. Eso sí, tendremos que recurrir a los alias para evitar un error de MySQL.&lt;code&gt;SELECT * FROM equipos AS Local JOIN equipos as Visitante&lt;/code&gt;+----+------------+----+------------+| id | equipo     | id | equipo     |+----+------------+----+------------+|  1 | Barcelona  |  1 | Barcelona  | |  2 | Madrid     |  1 | Barcelona  | |  3 | Celta      |  1 | Barcelona  | |  4 | Villarreal |  1 | Barcelona  | |  1 | Barcelona  |  2 | Madrid     | |  2 | Madrid     |  2 | Madrid     | |  3 | Celta      |  2 | Madrid     | |  4 | Villarreal |  2 | Madrid     | |  1 | Barcelona  |  3 | Celta      | |  2 | Madrid     |  3 | Celta      | |  3 | Celta      |  3 | Celta      | |  4 | Villarreal |  3 | Celta      | |  1 | Barcelona  |  4 | Villarreal | |  2 | Madrid     |  4 | Villarreal | |  3 | Celta      |  4 | Villarreal | |  4 | Villarreal |  4 | Villarreal | +----+------------+----+------------+16 rows in set (0,03 sec)Con todo, esta query necesita alguna restricción para se perfecta, pues nos empareja cada equipo consigo misma, así que podemos añadir condiciones para eliminar esas parejas del resultado.&lt;code&gt;SELECT * FROM equipos AS Local JOIN equipos AS Visitante WHERE Local.id != Visitante.id&lt;/code&gt;+----+------------+----+------------+| id | equipo     | id | equipo     |+----+------------+----+------------+|  2 | Madrid     |  1 | Barcelona  | |  3 | Celta      |  1 | Barcelona  | |  4 | Villarreal |  1 | Barcelona  | |  1 | Barcelona  |  2 | Madrid     | |  3 | Celta      |  2 | Madrid     | |  4 | Villarreal |  2 | Madrid     | |  1 | Barcelona  |  3 | Celta      | |  2 | Madrid     |  3 | Celta      | |  4 | Villarreal |  3 | Celta      | |  1 | Barcelona  |  4 | Villarreal | |  2 | Madrid     |  4 | Villarreal | |  3 | Celta      |  4 | Villarreal | +----+------------+----+------------+12 rows in set (0,00 sec)INNER JOINEste tipo de JOINS que nos dan el producto cartesiano son del tipo INNER y los resultados que podemos obtener de ellas estan siempre dentro de ese producto cartesiano.Como deciamos antes, este tipo de resultados no es muy útil en algunos casos. Volviendo a nuestro ejemplo de ibros y autores, la query nos empareja obras y autores de todas las maneras posibles, lo que no se corresponde con la realidad. Nuestro sistema tiene que tener más conocimiento del mundo y poder utilizarlo al hacer la combinacion de tablas.Nuestra tabla books cuenta con el campo author_id, la clave foránea que nos indica qué autor corresponde a cada libro. ¿Qué papel puede jugar en la combinacion de tablas?JOIN admite una cláusula ON para definir qué condiciones deben usarse para que dos registros se combinen. En nuestro ejemplo, el cambo books.author_id debe coincidir con el campo author.id y lo expresamos así:&lt;code&gt;SELECT * FROM books INNER JOIN authors ON books.author_id = authors.id&lt;/code&gt;+----+---------------------+-----------+----+----------------+| id | title               | author_id | id | author         |+----+---------------------+-----------+----+----------------+|  1 | El quijote          |         1 |  1 | Cervantes      | |  2 | 100 a?os de soledad |         2 |  2 | Garc?a M?rquez | |  3 | El Principito       |         3 |  3 | Saint_Exupery  | +----+---------------------+-----------+----+----------------+3 rows in set (0,05 sec)De este modo, la query nos devuelve los libros correctamente emparejados con sus autores.Podemos añadir la cláusula WHERE para especificar condiciones que restrinjan la busqueda de datos y esta puede usar campos de las tablas combinadas. Así, podemos buscar un libro por el nombre de su autor, a pesar de que este dato no está en la tabla books.&lt;code&gt;SELECT * FROM books INNER JOIN authors ON books.author_id = authors.id WHERE authors.author = 'Cervantes'&lt;/code&gt;+----+------------+-----------+----+-----------+| id | title      | author_id | id | author    |+----+------------+-----------+----+-----------+|  1 | El quijote |         1 |  1 | Cervantes | +----+------------+-----------+----+-----------+1 row in set (0,00 sec)Cuando combinamos ON y WHERE nos puede surgir la duda de si sería mejor poner las condiciones en ON o en WHERE.La regla práctica sería poner en ON las condiciones para decidir que registros deben emparejarse y en WHERE las condiciones para filtrar o restringir el resultado. La base de datos primero genera la tabla temporal y luego hace el filtrado.LEFT JOINPuede ocurrir que tengamos datos en una tabla que no tengan un registro asociado en la otra. Por ejemplo, añadimos un nuevo libro a nuestra tabla books pero no sabemos su autor (o es anónimo).Ahora si pedimos una lista de todos los libros registrados con sus autores con la query anterior veremos que no aparecen los libros que no tengan autor. ¡Vaya! En muchos casos este comportamiento no nos interesa, querríamos tener toda la lista de libros aunque no sepamos el autor.Para eso utilizamos LEFT JOIN.Este tipo de combinación toma todos los registros válidos de la primera tabla (o tabla izquierda/left) y los combina con los registros de la otra tabla (derecha). Si no hay ningún registro que se pueda combinar lo hace con uno nuevo cuyos campos están todos en NULL.En nuestros datos actuales tenemos tres libros y conocemos a sus autores correspondientes, así que al pedir la información a la base de datos nos devolverá este resultado:&lt;code&gt;SELECT * FROM books LEFT JOIN authors ON books.author_id = authors.id&lt;/code&gt;+----+---------------------+-----------+------+----------------+| id | title               | author_id | id   | author         |+----+---------------------+-----------+------+----------------+|  1 | El quijote          |         1 |    1 | Cervantes      | |  2 | 100 a?os de soledad |         2 |    2 | Garc?a M?rquez | |  3 | El Principito       |         3 |    3 | Saint_Exupery  | +----+---------------------+-----------+------+----------------+3 rows in set (0,00 sec)Ahora introduciremos un nuevo libro del cual no conocemos el autor&lt;code&gt;INSERT INTO books (title) values ('Lazarillo de Tormes');&lt;/code&gt;y repetimos la misma petición anterior. Este es el resultado:+----+---------------------+-----------+------+----------------+| id | title               | author_id | id   | author         |+----+---------------------+-----------+------+----------------+|  1 | El quijote          |         1 |    1 | Cervantes      | |  2 | 100 a?os de soledad |         2 |    2 | Garc?a M?rquez | |  3 | El Principito       |         3 |    3 | Saint_Exupery  | |  4 | Lazarillo de Tormes |      NULL | NULL | NULL           | +----+---------------------+-----------+------+----------------+4 rows in set (0,00 sec)La base de datos no encuentra un registro en authors que pueda emparejar con "Lazarillo de Tormes", pero al utilizar un LEFT JOIN también nos devuelve este libro, aunque deja los campos de authors sin definir. Para nosotros es útil porque de este modo podemos saber qué libros tenemos con independencia de si tenemos los datos de autor o no. La misma petición con un INNER JOIN nos dará el siguiente resultado:&lt;code&gt;SELECT * FROM books INNER JOIN authors ON books.author_id = authors.id;&lt;/code&gt;+----+---------------------+-----------+----+----------------+| id | title               | author_id | id | author         |+----+---------------------+-----------+----+----------------+|  1 | El quijote          |         1 |  1 | Cervantes      | |  2 | 100 a?os de soledad |         2 |  2 | Garc?a M?rquez | |  3 | El Principito       |         3 |  3 | Saint_Exupery  | +----+---------------------+-----------+----+----------------+3 rows in set (0,00 sec)Ves la diferencia, ¿verdad? Con el INNER JOIN sólo se devuelven resultados "dentro" del producto cartesiano (o dicho de otro modo, se devuelven pares de registros de ambas tablas). Es decir, se toman los registros de la tabla "izquierda" y se combinan con el registro correspondiente de la tabla "derecha" que cumpla las condiciones del ON y si no existe se ignora esa fila.Con LEFT JOIN se podría decir que se toman todos los registros de la tabla izquierda relevantes (que cumplan las condiciones de WHERE si está presente) y se combinan con el registro correspondiente de la tabla "derecha" tanto si existe o no un registro en ella que cumpla las condiciones de ON.RIGHT JOINSi entendiste bien el significado de LEFT JOIN seguro que eres capaz de deducir lo que significa RIGHT JOIN. Exacto: en este caso se parte de los registros de la tabla "derecha" y se busca si hay algún registro en la tabla "izquierda" que cumpla las condiciones en ON. En caso de no encontrarlo se ponen sus campos a NULL.Para poder verlo en acción necesitamos añadir un author a nuestra tabla, que no tenga libros.&lt;code&gt;INSERT authors (author) values ('Quevedo')&lt;/code&gt;A continuación ejecutamos una petición con RIGHT JOIN&lt;code&gt;SELECT * FROM books RIGHT JOIN authors ON books.author_id = authors.id;&lt;/code&gt;El resultado es:+------+---------------------+-----------+----+----------------+| id   | title               | author_id | id | author         |+------+---------------------+-----------+----+----------------+|    1 | El quijote          |         1 |  1 | Cervantes      | |    2 | 100 a?os de soledad |         2 |  2 | Garc?a M?rquez | |    3 | El Principito       |         3 |  3 | Saint_Exupery  | | NULL | NULL                |      NULL |  4 | Quevedo        | +------+---------------------+-----------+----+----------------+4 rows in set (0,00 sec)Como era de esperar, el registro correspondiente al author "Quevedo" aparece recogido con los campos de la tabla books en null.Y esto es todo de momento. En próximas entregas me dedicaré a las relaciones "muchos a muchos" y a hacer algunas consideraciones sobre la utilidad de los JOIN en CakePHP:&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/SGqIXG0EVPs" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Thu, 07 Jan 2010 19:39:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4214 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Joins en CakePHP: buscar con registros relacionados</title>
 <link>http://cakephpilia.blogspot.com/2010/01/joins-en-cakephp-buscar-con-registros.html</link>
 <description>&lt;p&gt;Voy a intentar escribir varias entradas sobre el uso de JOIN con CakePHP, porque me parece un tema interesante, demandado, y relativamente poco conocido en el framework.Que es eso de JOINEn SQL la cláusula JOIN nos permite hacer búsquedas en varias tablas de la base de datos que estén relacionadas entre sí.Así, por ejemplo, si tenemos el típico caso de Post hasAndBelongsToMany Tag, usando JOINS podemos hacer una búsqueda de aquellos Post que hayan sido etiquetados con una Tag determinada.Para entender el concepto de JOIN puedes imaginarte que tomas cada registro de la tabla "principal" y le añades los campos del registro relacionado en otra tabla, quedándote una especie de "supertabla", y en ella puedes realizar búsquedas sobre cualquier campo de ambas tablas.Existen varios tipos de JOIN: LEFT, RIGHT e INNER, de los que hablaré en otra entrada, que nos permiten definir cómo queremos que se combinen los datos de las tablas. El resultado puede ser muy diferente en cada caso y el tipo elegido depende de nuestras finalidades.La situación en CakePHPComo bien sabes, CakePHP utiliza una sintaxis especial basada en arrays para crear las querys a la base de datos. La ventaja de este sistema es que nos permite cambiar con relativa facilidad del motor de base de datos. Este "lenguaje super-SQL" &lt;a href="http://book.cakephp.org/view/449/find"&gt;aparece documentado en el manual, pero no menciona los joins.&lt;/a&gt;Por una parte, algunos tipos de asociaciones de Modelos de CakePHP ejecutan finds creando los JOIN necesarios y es posible hacer búsquedas de un modelo, usando campos de modelos relaciones, pero no es el caso de nuestras queridas hasAndBelongsToMany (creo que las hasMany tampoco).Otro aspecto importante tiene que ver con la paginación, pues también hay complicaciones cuando se intenta paginar con relaciones habtm.Hay algunos trucos para hacer estas búsquedas en relaciones habtm (como redefinir las asociaciones según convenga como hasMany y belongsTo de forma temporal, o directamente escribir el query a mano y ejecutarlo con Model-&amp;gt;query()), pero &lt;a href="http://bakery.cakephp.org/articles/view/quick-tip-doing-ad-hoc-joins-in-model-find"&gt;este artículo de Nate Abele en Bakery sobre el uso de joins&lt;/a&gt; me abrió los ojos a una solución que para mí es mucho mejor: cómo forzar joins en CakePHP.Joins en CakePHP, la respuesta corta
&lt;ol&gt;
&lt;li&gt;Para hacer joins hay que usar el método Model-&amp;gt;find($type, $options), donde $type es el tipo de find ('all', 'list', 'first', o uno personalizado) y $options es nuestro array con todos los detalles del query.&lt;/li&gt;
&lt;li&gt;Una de las claves de $options será justamente 'joins'&lt;/li&gt;
&lt;li&gt;Cada 'join' se representa mediante un array que tiene estas claves&lt;/li&gt;
&lt;ol&gt;
&lt;li&gt;'table': el nombre de la tabla&lt;/li&gt;
&lt;li&gt;'alias': normalmente el nombre del modelo asociado a la tabla&lt;/li&gt;
&lt;li&gt;'type': el tipo de join&lt;/li&gt;
&lt;li&gt;'conditions': el array de condiciones que nos dice cómo se relacionan las tablas (básicamente las condiciones que pondríamos en la cláusula ON)&lt;/li&gt;
&lt;li&gt;'foreignKey': es un boolean, aunque no tengo claro cuál es su función y en los ejemplos que he visto se pone a false (echando un vistazo a dbo_source me pregunto si ponerlo en true serviría para hacer algo de automagia con las asociaciones ya existentes, pero no lo tengo nada claro).&lt;/li&gt;
&lt;/ol&gt;
&lt;li&gt;Pones todas las definiciones de joins que necesites.&lt;/li&gt;
&lt;li&gt;Para hacer la búsqueda, añades condiciones en la clave 'conditions' como es habitual.&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;&lt;p&gt;Un ejemploLo siguiente es un fragmento del código que estoy usando en un método find personalizado de un proyecto en el que estoy trabajando. Espero que se entienda.Se trata de un gestor de contenidos, en el que tengo los modelos Channel, Item y User de tal modo que Channel hasMany Item, y Channel hasAndBelongsToMany User.  Es decir, un Channel puede tener varios Item y cada Channel está asociado a varios Usuarios (que pueden estar asociados a varios Channel).Ocurre que algunos Channel son "privados" para los User que están asociados a ellos. Quería un método find capaz de devolverme los Items de los Channel privados a los que un User tiene acceso. Para ello hago los siguientes joinsItem JOIN Channel para poder buscar los Channel marcados como privados (ver Channel.private en la clave 'conditions').Channel JOIN ChannelsUser para poder restringir la búsqueda a los Channel asociados con el User (en este caso conozco el id del usuario, dato que paso a través de una clave propia 'user' y que puedo encontrar en la tabla channels_users, pero podría hacer un JOIN a user para restringir la búsqueda por algún otro campo de User). ChannelsUser es el joinModel de la asociación Channel habtm User, aunque no es necesario que lo definas expresamente (por supuesto, sí que necesitas tener la tabla channels_users par que funcione la relación).&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;/* Aquí definimos los join */$extra['joins'] = array(    array('table' =&amp;gt; 'channels',        'alias' =&amp;gt; 'Channel',        'type' =&amp;gt; 'LEFT',        'foreignKey' =&amp;gt; FALSE,        'conditions' =&amp;gt; array(            'Channel.id = Item.channel_id',        )    ),    array('table' =&amp;gt; 'channels_users',        'alias' =&amp;gt; 'ChannelsUser',        'type' =&amp;gt; 'LEFT',        'foreignKey' =&amp;gt; FALSE,        'conditions' =&amp;gt; array(            'ChannelsUser.channel_id = Channel.id',            'ChannelsUser.user_id' =&amp;gt; $query['user']        )    ));/* Aquí están las condiciones, como puedes ver, ya podemos usar campos de los modelos relacionados, en este caso Channel */$extra['conditions'] = array(    'Channel.private' =&amp;gt; 1,    'Item.publish' =&amp;gt; 1,    'Item.pubDate &amp;lt;= curdate()',    'or' =&amp;gt; array(        'Item.expiration is null',        'Item.expiration &amp;gt; curdate()',    ));$extra['order'] = array('Item.pubDate' =&amp;gt; 'desc','Item.created' =&amp;gt; 'desc');/* Finalmente mezclo las opciones definidas aquí, con las que se hayan pasado como argumento a find */$query = Set::merge($query, $extra);&lt;/code&gt;Algunos detallesFíjate que los arrays que definen los joins no llevan ninguna clave, en todo caso podría ser numérica para definir el orden en que deben unirse las tablas (aunque lo defines simplemente escribiéndolas en el orden adecuado).Otro detalle importante es la forma en que defino las 'conditions' dentro del join, en lugar de utilizar el formato típico de 'Modelo.campo' =&amp;gt; 'OtroModelo.campo', lo hago 'Modelo.campo =&amp;gt; OtroModelo.campo'. CakePHP parece asumir que el valor a la derecha siempre es un valor, no un campo (le pone comillas de valor, no de campo) y la condición nunca se cumple.Si en lugar de un campo es un valor, el join funciona bien.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/3Ff9nt5UW08" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Mon, 04 Jan 2010 20:53:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4171 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Una buena guía para sumergirse en HTML5</title>
 <link>http://cakephpilia.blogspot.com/2010/01/una-buena-guia-para-sumergirse-en-html5.html</link>
 <description>&lt;p&gt;El libro de Mark Pilgrim &lt;a href="http://diveintohtml5.org/"&gt;Dive into HTML5&lt;/a&gt; tiene una pinta estupenda para empezar a familiarizarse con el nuevo (e inacabado) estándar, y empezar a usarlo en la medida en que algunas de sus novedades ya están soportadas por los navegadores modernos.Además, el autor hace varias recomendaciones prácticas acerca de cómo utilizar los nuevos elementos y características, dado a la vez soporte a navegadores que aún no las contemplan. Por ejemplo: cómo puedes empezar a utilizar ya los nuevos types del los input en los formularios, o cómo utilizar el elemento video sin dejar de lago el viejo explorer.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/uhMFtc6ya1I" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Mon, 04 Jan 2010 19:49:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4172 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Diseño web con HTML y CSS</title>
 <link>http://cakephpilia.blogspot.com/2010/01/diseno-web-con-html-y-css.html</link>
 <description>&lt;p&gt;El título suena a perogrullada, pero en &lt;a href="http://24ways.org/"&gt;24 ways&lt;/a&gt; han publicado &lt;a href="http://24ways.org/2009/make-your-mockup-in-markup"&gt;un gran artículo de Meagan Fisher&lt;/a&gt; acerca del proceso de diseño de webs usando código y relegando la costumbre de crear los bocetos mediante un editor de imágenes.Lo cierto es que nunca fui capaz de diseñar una web mediante un editor de imágenes pues lo mío siempre ha sido lápiz y papel, aunque es una práctica muy común abrir el Photoshop o el programa equivalente y trabajar a partir de ahí.El artículo hace hincapié en las capacidades de HTML y CSS 3, cuyas propiedades más avanzadas empiezan a estar soportadas por los navegadores más importantes, ya sean de la rama Mozilla (Firefox, Flock, Camino), de la Webkit (Safari, Chrome) o de Opera. Así que quitando ese que tú sabes, un navegador moderno permite jugar con propiedades como las sombras, opacidad, tipografía e incluso animaciones.En ese sentido, es muy interesante visitar &lt;a href="http://www.css3.info/"&gt;css3.info&lt;/a&gt; para empezar a familiarizarse con CSS3, conocer el soporte en cada familia de navegadores y cómo usar las nuevas propiedades.En muchos casos estas propiedades están soportadas todavía como extensiones propias de cada navegador.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/U9suzvbrScs" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Sun, 03 Jan 2010 00:46:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4152 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Programar en inglés</title>
 <link>http://cakephpilia.blogspot.com/2010/01/programar-en-ingles.html</link>
 <description>&lt;p&gt;Hace tiempo alguien preguntó en el &lt;a href="http://groups.google.com/group/cakephp-esp?hl%3Des"&gt;grupo google de cakephp en español&lt;/a&gt; acerca de la costumbre de programar en inglés y hace un par de días volví a caer en este &lt;a href="http://cakebaker.42dh.com/2006/07/16/hey-programmer-why-dont-you-code-in-english/"&gt;post de CakeBaker&lt;/a&gt; sobre el tema.Bien, primero habría que explicar lo que entiendo por "programar en inglés". Para empezar, PHP, como tantos otros lenguajes de programación, es una especie de dialecto del inglés. Por programar en inglés, quiero decir usar este idioma para nombres de variables, clases, funciones, comentarios e incluso para los textos iniciales de la aplicación.¿Y qué razones tendríamos para hacer esto?Hay algunas bastante prácticas, como las siguientes:
&lt;ul&gt;
&lt;li&gt;Siendo PHP un lenguaje basado en el inglés, evitamos mezclar idiomas, lo que hace más fácil la lectura del código.&lt;/li&gt;
&lt;li&gt;Es más fácil compartir código con otros programadores de todo el mundo. Y en consecuencia:&lt;/li&gt;
&lt;li&gt;Es más fácil que otros programadores te puedan ayudar en foros y grupos de correo, etc.&lt;/li&gt;
&lt;li&gt;En CakePHP, específicamente, te puedes evitar algunos problemas con el Inflector cuando intentas añadir reglas para dar soporte a español y otros idiomas y que interfieren con las establecidas por defecto para el inglés.&lt;/li&gt;
&lt;li&gt;En bastantes casos, la jerga técnica en inglés es más completa, expresiva y precisa que en español, por lo que los comentarios serán mucho más informativos. &lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;p&gt;Y, la más importante:
&lt;ul&gt;
&lt;li&gt;Programando en inglés, pareces mejor programador de lo que eres ;-)&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;p&gt;echo "Happy new year!"&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/HO5sq8NmI7I" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Sat, 02 Jan 2010 09:38:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4153 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Poblar variables de clase a partir de un array</title>
 <link>http://cakephpilia.blogspot.com/2009/12/poblar-variables-de-clase-partir-de-un.html</link>
 <description>&lt;p&gt;Este código nos permite poblar de valores las propiedades de una clase a partir de un array asociativo cuyas claves tengan el mismo nombre. Es algo así como un extract.Puede ser útil al pasar parámetros en forma de array asociativo, una práctica habitual en CakePHP.&lt;code&gt;&amp;lt;?php&lt;/code&gt;&lt;code&gt;class test {  var $prueba; var $control;  function __construct() {    $array = array(   'prueba' =&amp;gt; 'Hola',   'control' =&amp;gt; 'amigos'   );  foreach ($array as $key =&amp;gt; $value) {   $this-&amp;gt;{$key} = $value;  } }} $test = new test(); print_r($test);&lt;/code&gt;&lt;code&gt;?&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/xb0KLJiqrHA" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Thu, 24 Dec 2009 11:12:00 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4108 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Comprobar que un array no es asociativo</title>
 <link>http://cakephpilia.blogspot.com/2009/12/comprobar-que-un-array-no-es-asociativo.html</link>
 <description>&lt;p&gt;Se me ha ocurrido esta función para comprobar que un array no sea asociativo:&lt;code&gt;&lt;/code&gt;&lt;code&gt;function isList($array) { if (!is_array($array) || count($array) == 0) {  return false; } return array_sum(array_keys($array)) &amp;gt; 0;}&lt;/code&gt;&lt;code&gt;?&amp;gt;&lt;/code&gt;Devuelve true si el array es numérico, y false si no es un array o es asociativo.En CakePHP puede ser interesante usarlo cuando queremos diferenciar entre los datos devueltos por un read y un find('all'). En el segundo caso, el array es numérico (una clave numérica por cada registro del modelo).Si los datos vienen de un read (o un find('first')) podemos convertirlos en array numérico con un simple $datos = array($datos), y luego procesar con un foreach. La ventaja es que el mismo código nos vale luego para ambos casos.Yo voy a usar esta idea para un uploadable behavior en el que estoy trabajando.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/eWOiF5SlpBM" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Mon, 21 Dec 2009 23:02:56 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">4025 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Querys CakePHP con operaciones bitwise</title>
 <link>http://cakephpilia.blogspot.com/2009/12/querys-cakephp-con-operaciones-bitwise.html</link>
 <description>&lt;p&gt;Otro de esos títulos "sólo para iniciados"...Hoy me he encontrado con un problemilla curioso. Necesitaba definir una condición para un find en la que hay una operación bitwise en la base de datos. En concreto, un &amp;amp; de una máscara binaria contra un campo del modelo.Bueno, pues la forma en que he conseguido que funcione es algo así:&lt;code&gt;...&lt;/code&gt;&lt;code&gt;array('conditions' =&amp;gt; array('12 &amp;amp; Rule.precedence'):&lt;/code&gt;&lt;code&gt;...&lt;/code&gt;Que debe dar un WHERE más o menos así:&lt;code&gt;WHERE 12 &amp;amp; `Rule`.`precedence`&lt;/code&gt;Es decir, primero pongo el valor de la máscara, luego la operación binaria y luego el campo.Si pongo primero el campo, CakePHP se empeña en hacer no sé qué y no aparece la comparación en el WHERE.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/V0Na1T0VKXY" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Fri, 11 Dec 2009 00:01:10 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">3925 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Crear un sitio CakePHP sin conexion a una base de datos</title>
 <link>http://infectogroovalistic.blogspot.com/2009/12/crear-un-sitio-cakephp-sin-conexion-una.html</link>
 <description>&lt;p&gt;CakePHP por default asume que usaremos una base de datos para nuestra aplicación, esto trae un problema cuando intentamos crear un sitio web sin base de datos.&lt;/p&gt;
&lt;p&gt;Encontré la solución, creando una conexión falsa que devuelva siempre TRUE. Esto lo hacemos de la siguiente manera:&lt;/p&gt;
&lt;p&gt;- Primero creamos un datasource en &lt;span style="font-weight: bold;"&gt;app/models/datasources/dbo/dbo_fake_dbo_source.php&lt;/span&gt; con el siguiente contenido:&lt;/p&gt;
&lt;pre&gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;class DboFakeDboSource extends DboSource&lt;br /&gt;{&lt;br /&gt; function connect()&lt;br /&gt; {&lt;br /&gt;  $this-&gt;connected = true;&lt;br /&gt;  return true;&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; function disconnect()&lt;br /&gt; {&lt;br /&gt;  $this-&gt;connected = false;&lt;br /&gt;  return true;&lt;br /&gt; }&lt;br /&gt;  &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;
Y ahora simplemente en la configuración de la base de datos ( &lt;span style="font-weight: bold;"&gt;app/config/database.php&lt;/span&gt; ) reemplazamos el valor del driver que por default es &lt;span style="font-weight: bold;"&gt;'&lt;span style="font-style: italic;"&gt;mysql&lt;/span&gt;'&lt;/span&gt; por &lt;span style="font-weight: bold;"&gt;'&lt;span style="font-style: italic;"&gt;fake_dbo_source&lt;/span&gt;'&lt;/span&gt; y listo!&lt;br /&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width="1" height="1" src="https://blogger.googleusercontent.com/tracker/33160121-7916525205599998313?l%3Dinfectogroovalistic.blogspot.com" alt="" /&gt;&lt;/div&gt;
&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/6MOkwrNYep8" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Wed, 09 Dec 2009 15:39:00 +0000</pubDate>
 <dc:creator>Leonel Quinteros</dc:creator>
 <guid isPermaLink="false">3906 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Obtener el prefijo de una acción (o dividir una cadena)</title>
 <link>http://cakephpilia.blogspot.com/2009/12/obtener-el-prefijo-de-una-accion-o.html</link>
 <description>&lt;p&gt;Esta es una de esas cosas que siempre se me olvidan, así que voy a anotarla.He aquí una operación típica para romper un string en dos partes, sabiendo que están separadas por un carácter concreto, en este caso el socorrido "underscore".&lt;code&gt;list($part1, $part2) = explode('_', $string);&lt;/code&gt;Aplicándolo a CakePHP, vamos a imaginar que tenemos el nombre de una acción y queremos saber si tiene prefijo y cuál es. Así que en algún sitio de nuestro Controller (probablemente en el beforeFilter) hacemos:&lt;code&gt;$action = $this-&amp;gt;params['action'];&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;list($prefix, $actionName) = explode('_', $action);&lt;/code&gt;Sencillo, ¿verdad?Pero, ¿qué pasa si $action no tiene prefijo y por tanto no hay separador en la cadena?Bueno, pues en ese caso, $prefix toma el valor de la cadena y $actionName queda vacío, por lo que necesitaríamos el siguiente código para controlar la situación:&lt;code&gt;$action = $this-&amp;gt;params['action'];&lt;/code&gt;&lt;code&gt;list($prefix, $actionName) = explode('_', $action);if(!$actionName) {    $actionName = $prefix;   $prefix = '';&lt;/code&gt;&lt;code&gt;}&lt;/code&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/kr8m8JEmw8Y" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Tue, 08 Dec 2009 14:15:05 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">3889 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Bootstrap para plugins</title>
 <link>http://cakephpilia.blogspot.com/2009/12/bootstrap-para-plugins.html</link>
 <description>&lt;p&gt;El archivo APP/config/bootstrap.php de una aplicación CakePHP nos sirve como lugar donde cargar funciones, cargar configuraciones, iniciar variables o definir constantes globales para nuestra aplicación.Estos días me encontré con la necesidad de disponer de una funcionalidad parecida para los plugins. La idea es interesante, se trata de poder disponer, entre otras cosas, de ajustes de configuración y constantes definidas en los plugins pero que sean accesibles desde otras partes de la aplicación.La cuestión es cómo hacer ese "bootstrapping" para plugins de forma "automágica".A mí se me ha ocurrido usar este fragmento de código en el bootstrap de la aplicación&lt;code&gt;&amp;lt;?php&lt;/code&gt;&lt;code&gt;App::import('Core', 'Folder');$folder =&amp;amp; new Folder();$folder-&amp;gt;cd(APP . 'plugins');$files = $folder-&amp;gt;findRecursive('bootstrap\.php');foreach ($files as $file) { include_once($file);}&lt;/code&gt;&lt;code&gt;?&amp;gt;&lt;/code&gt;La explicación es bastante sencilla. La clase Folder nos permite apuntar a un directorio, en este caso plugins, y obtener todos los archivos cuyo nombre se ajuste a un patrón grep, cuyos paths se obtienen en un array. Luego no tenemos más que hacer include de cada uno de los archivos y ya tenemos nuestros bootstrapping.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/THaqERKSBNY" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Fri, 04 Dec 2009 22:59:42 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">3869 at http://planetcakephp.org</guid>
</item>
<item>
 <title>Nota mental: Controller necesita $name</title>
 <link>http://cakephpilia.blogspot.com/2009/11/nota-mental-controller-necesita-name.html</link>
 <description>&lt;p&gt;Pues sí, los controladores necesitan tener la propiedad $name, al menos para funcionar en los test, de otro modo se rompe todo. Incluso con PHP 5.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/planet-cakephp-aggregator-spanish/~4/ntH9MpgJQ9g" height="1" width="1"/&gt;</description>
 <category domain="http://planetcakephp.org/aggregator/languages/spanish">Español</category>
 <pubDate>Sun, 22 Nov 2009 23:58:38 +0000</pubDate>
 <dc:creator>Fran Iglesias</dc:creator>
 <guid isPermaLink="false">3778 at http://planetcakephp.org</guid>
</item>
</channel>
</rss>
