Documente Academic
Documente Profesional
Documente Cultură
ISBN: 978-84-267-2160-0
DL: B-15822-2014
Impreso en
Printed in Spain
Dedico este libro con todo mi cariño a mis seres
queridos y a todas aquellas personas que me
han ayudado a materializar este proyecto.
Sobre mí
Nací en Valencia aunque de ascendencia castellana.
Empecé mis primeros pinitos en la informática con una
calculadora SHARP PC-1500A que consiguió mi padre
al adquirir un equipo informático a la empresa INGEL-
MO sobre los años ochenta, equipos que por aquella
época costaban alrededor de un millón de pesetas: una
CPU Z80 con 64 Kb de RAM; dos disqueteras de 5 1/4”
de 360 kB: una para el programa y otra para datos; y
una impresora matricial para confeccionar los docu-
mentos de reparto y facturas. El programa: un desarro-
llo a medida (según lo vendían) costaba aproximada-
mente lo mismo que el hardware (¡Qué tiempos aquellos!). Cuando exprimí la
calculadora al máximo me pasé directamente a programar «el bicho» que he
descrito. No he parado desde entonces, pasando por aplicaciones de escritorio
hechas en Turbo Pascal, Delphi, Access, Velneo hasta por fin aplicaciones web
desarrolladas en PHP y MySQL. De la mano de Symfony 1.4 entré por la puer-
ta grande en la programación orientada a objetos, aunque pronto se nubló mi
éxtasis al tener que desarrollar plugins y themes para Wordpress, que, aunque
teniendo mérito como desarrollo comunitario, es lo que en nuestro mundillo se
conoce como código espagueti.
Esta vasta experiencia y mis muchos deseos de aprender, unidos a las innu-
merables charlas a las que he asistido sobre Symfony y las incontables entre-
vistas de trabajo que he protagonizado, en las que me han preguntado sobre
patrones de diseño, programación en equipo, buenas prácticas, etc., me han
motivado a contar mi experiencia a nivel de desarrollo en PHP para que aque-
llos que tengan interés en aprender a programar para un mundo real encuen-
tren en este libro el apoyo técnico necesario para empezar esta andadura.
Índice de contenidos
1. Introducción .................................................................................................................... 1
Presentación ................................................................................................................................ 2
Composer ...................................................................................................................................... 2
Slim .................................................................................................................................................. 4
Entonces, ¿qué vamos a ver en este libro? ...................................................................... 5
Conclusión..................................................................................................................................... 6
Parte I: MVC
2. Patrón MVC ....................................................................................................................... 9
Introducción...............................................................................................................................10
¿Por qué esta elección?..........................................................................................................11
Conclusión...................................................................................................................................13
3. El controlador: Slim .................................................................................................... 15
Introducción...............................................................................................................................16
¿Cómo funciona? ......................................................................................................................17
¿Qué hace Slim? ........................................................................................................................19
Declaración de rutas ...............................................................................................................20
¿Atención duplicada? .............................................................................................................21
Conclusión...................................................................................................................................22
4. El modelo: IdiORM & Paris ....................................................................................... 23
¿Qué es un ORM? ......................................................................................................................24
¿Por qué un ORM?....................................................................................................................25
Métodos disponibles ..............................................................................................................30
Rizando el rizo ..........................................................................................................................32
Relaciones entre tablas .........................................................................................................33
Conclusión...................................................................................................................................35
5. La vista: Twig ................................................................................................................ 37
¡Ay, la vista, la vista! ................................................................................................................38
Vamos por partes .....................................................................................................................39
¿Qué es Twig? ............................................................................................................................41
¿Cómo funciona? ......................................................................................................................42
Tipos de datos ...........................................................................................................................45
Toma de decisiones .................................................................................................................46
Iteraciones ..................................................................................................................................46
Funciones de Twig ...................................................................................................................48
Extender Twig con funciones propias.............................................................................49
Macros ..........................................................................................................................................49
Resumen ......................................................................................................................................52
Conclusión...................................................................................................................................59
Índices
Convenciones
He seguido las siguientes convenciones a la hora de escribir este libro para fa-
cilitar el seguimiento del código fuente de los ejemplos y su lectura en general:
» Nombres de archivo en cursiva: index.php
» URL y enlaces en gris y subrayados: http://www.joseluislaso.es
» Contenido del los archivos en letra Andale Mono a 9: <?php echo
"hola"; ?>
» Salida del terminal en vídeo inverso con Courier a 11:
mkdir prueba
» Además utilizaré PSR0 a 2, por ello verás que no cierro nunca las eti-
quetas <?php, además de cuestiones de formateo de código1.
» En ocasiones omitiré parte del código que por motivos didácticos no
es importante; este será sustituido por tres puntos seguidos, en todo
caso el contenido completo está en github.
» Me he permitido las siguientes licencias al escribir:
» bbdd por base de datos para abreviar
» palabras en inglés de uso normal en programación en letra cursiva
» *nix indica cualquier tipo de sistema POSIX como Linux, Unix,
BSD, Darwin.
1
https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
Agradecimientos
» Al creador de SlimFramework, Josh Lockhart.
» Al creador de IdiORM&Paris, Jamie Matthews.
» Al creador de Twig, Fabien Potencier.
Presentación
Este no pretende ser un libro convencional en el que se expone mucha teoría
y se dan pocos ejemplos. De hecho te supongo unos conocimientos mínimos
que no voy a abordar. En los puntos en los que sea importante daré referencias
a sitios de donde puedes sacar información complementaria. De todas mane-
ras puedes plantearme las dudas que te surjan de la lectura de esta obra.
Como hilo conductor y soporte del código y ejemplos nos basaremos en una
aplicación real que he creado para este libro. Estoy trabajando en ella aún, así
que no está completa al 100% (¿qué aplicación lo está, verdad?). De esta manera
fijaremos los conceptos con código real. Esta aplicación se llama My-simple-web
y la tienes a tu disposición en github [http://github.com/jlaso/my-simple-web] y
puedes verla en funcionamiento en [http://mysimpleweb.com.es].
Composer
Composer es el gestor de dependencias de paquetes por excelencia de PHP.
A lo largo del libro vamos a utilizar composer muy a menudo, por lo que te
recomiendo que si aún no lo tienes instalado en tu equipo acudas a la URL
oficial1 y sigas los pasos dependiendo de la configuración de tu sistema.
1
http://getcomposer.org
2
1. Introducción
cd ~
mkdir prueba
cd prueba
nano composer.json
Este archivo le indica a composer los requerimientos que tiene nuestro proyecto.
Los paquetes o módulos que utilizará composer para descargar están en
http://packagist.org, más adelante veremos cómo utilizar una estructura más
compleja para indicar otro origen. En principio y para este primer acercamiento
nos sobra con lo que hemos escrito.
3
Programación PHP profesional con Slim, Paris y Twig
Slim
Te dije al principio de este capítulo que quería introducir dos conceptos al
instalar los ejemplos sencillos, el primero era composer, que ha quedado visto;
vamos a por el segundo.
2
PHP es más permisivo en esto.
4
1. Introducción
es el controlador, veremos enseguida que todas las peticiones que realizan los
usuarios de la aplicación entran por Slim y son resueltas por Slim, utilizando al
modelo para recuperar y/o persistir datos en la base de datos y al motor de la
vista para generar el resultado que se va a mostrar al usuario.
Brevemente, ya que vamos a ver con frecuencia este tema a lo largo del libro,
Slim hace lo siguiente:
» Recoge la petición (request) desde una interacción del usuario en el
navegador.
» Manipula los datos necesarios poniendo en juego al modelo.
» Utiliza los datos que sean precisos para pasárselos al motor de vista y
genera una respuesta HTML adecuada.
» Devuelve la respuesta al usuario (response).
5
Programación PHP profesional con Slim, Paris y Twig
Conclusión
He dividido el resto de capítulos del libro en cuatro partes, en la primera daremos
un vistazo muy somero a los componentes que forman parte del modelo MVC
que he elegido para este libro. La segunda parte trata de las herramientas
imprescindibles necesarias para programar en PHP de manera profesional. La
tercera es un repaso en profundidad al patrón MVC usando como material de
trabajo el código de la aplicación de ejemplo My-simple-web.
Por último y como cierre, la última parte trata de complementos que debes
conocer para pasar al siguiente nivel en la programación profesional en PHP.
No pretendo sentar cátedra con los complementos de esta última parte,
simplemente son pinceladas de cuestiones que alguno de nosotros tenemos
asumidas como obvias pero que a los compañeros que empiezan seguramente
les sonará a chino.
Para las próximas revisiones quiero agregar más código en lo referente
a ejemplos prácticos resueltos. Me dejo algunas cosas en el tintero que
probablemente añada en próximas revisiones, sobre todo guiado de los
comentarios que tengas a bien hacerme llegar a través de mi página web
http://www.joseluislaso.es.
6
Parte I: MVC
2 Patrón MVC
Contenido
» Introducción
» ¿Por qué esta elección?
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
Sin duda existe mucha literatura acerca del patrón modelo-vista-controlador (MVC)
y no se trata de repetir aquí todos esos conceptos técnicos. Como este libro pre-
tende ser algo eminentemente práctico vamos a enfocar el tema de esa manera.
Este libro trata de eso y por eso su título incluye cada una de las distintas partes
de ese patrón: Slim para la parte del controlador, Paris para la parte del modelo y
Twig para el motor de la vista.
10
2 Patrón MVC
11
Programación PHP profesional con Slim, Paris y Twig
// Prepare app
$app = new \Slim\Slim(...);
// Run app
$app->run();
Quedémonos con el require que carga las rutas y veamos por ejemplo la definición
de la ruta home que se hace en el archivo app/controller/frontend/home.php:
@1:
$app->get('/', function () use ($app) {
$app->render('frontend/home/index.html.twig');
})->name('home.index');
Ejemplo 2: app/controller/frontend/home.php
12
2 Patrón MVC
Conclusión
Aunque corto, he querido en este primer capítulo darte una idea de lo que es el
patrón que vamos a usar como base para nuestra aplicación de ejemplo y espero
que para todos tus desarrollos.
13
3 El controlador:
Slim
Contenido
» Introducción
» ¿Cómo funciona?
» ¿Qué hace Slim?
» Declaración de rutas
» ¿Atención duplicada?
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
He elegido Slim1 para mis proyectos porque representa la concentración de un
gran número de funciones en unos pocos Kb.
Está claro que hay otros frameworks, como pueden ser: silex, cakePhp, co-
deIgniter, etc. Por no hablar del todopoderoso Symfony o del omnipresente
Zend, aunque, es cierto que para proyectos no muy grandes ubicados en ser-
vidores comerciales económicos es implanteable usar frameworks altamente
consumidores de recursos. Aun así trabajar con ellos es muy enriquecedor a
nivel profesional.
Sin duda la elección del framework es muy personal, aunque si estás empe-
zando a desarrollar a nivel profesional estoy seguro de que quedarás muy sa-
tisfecho con este.
También puedes, como todos los proyectos que están en github, mandar un
pull request con tus mejoras para que el propietario las incluya en el proyecto,
si lo estima oportuno.
1
http://www.slimframework.com
16
3. El controlador: Slim
En los proyectos más serios y sobre todo a nivel profesional utilizo Symfony,
pero he de reconocer que a efectos de hosting y para proyectos más comunes
es más fácil manejar un proyecto que no sea Symfony por motivos obvios: en
un hosting compartido no puedes alterar la configuración de php ni agregar
nuevos componentes al sistema operativo, cosas que a buen seguro necesita-
rás si usas este framework.
¿Cómo funciona?
Como he dejado caer en el capítulo anterior, Slim se encarga de recoger la
petición, procesar los datos necesarios (de la bbdd o no) y encargarle al motor
de plantillas que genere una vista que luego devolverá en forma de respuesta.
En algunos casos (ajax, api, webservices) no interviene el motor de plantillas,
aunque sí se genera una respuesta, de todas formas por el momento nos va-
mos a centrar en peticiones convencionales en las que sí que hay respuesta
HTML al uso.
Es cierto que la primera posibilidad deja muy claro dónde se encuentra la aten-
ción de cada ruta, pero tiene dos inconvenientes importantes:
» Tenemos que repetir código en cada uno de los archivos, pues tendre-
mos mucho código en archivos separados que será necesario incluir.
En la mayoría de los casos, suele consistir en un archivo grueso con
todas las funciones comunes y tal vez un par de ellos más para mostrar
la cabecera y el pie.
17
Programación PHP profesional con Slim, Paris y Twig
Con este archivo le indicamos a Apache que cualquier ruta que le llegue (que
vaya a ser atendida dentro de esta carpeta, evidentemente), deberá ser
2
No quiero dejar de lado otros servidores web, pero este es el más extendido al nivel
en el que nos vamos a mover.
18
3. El controlador: Slim
Posteriormente veremos con detalle cómo es ese index.php por dentro. Pero
quédate en este momento con el hecho de que es el cargador de la aplicación
y el encargado de unir todos los servicios, entre ellos a Slim con el resto.
Pero ahora que no necesitamos los archivos físicos, podemos usar toda la ruta
para saber qué quiere hacer el usuario. Por convención se usa la misma estruc-
tura de carpetas, aunque no son necesarias, porque los buscadores indexan el
contenido como si de archivos físicos se tratase, organizados jerárquicamente
en carpetas.
Sea como sea, el hecho es que nuestro Slim, una vez tenga el control, va a
tomar la URL que le viene y va a hacer algo con ella que enseguida vamos a
descubrir.
19
Programación PHP profesional con Slim, Paris y Twig
Slim ha ido recogiendo durante el bootstrap3 todas aquellas rutas que hemos
ido declarando y cuando se lanza $app->run() lo que hace es matchear
la ruta que le viene en la petición con la expresión de una de esas definicio-
nes, por supuesto también tiene que hacer coincidir el método de la petición
(GET,POST, etc.).
Podemos definir más de una ruta que pueda atender la misma URL, pero clara-
mente se atenderá la primera definida. ¿Por qué querer definir rutas así? Luego
lo veremos.
Declaración de rutas
Ruta lógica versus ruta física
Básicamente lo que hace Slim es mapear las rutas de las URL (rutas físicas)
con una rutas internas que son atendidas por un segmento de código.
Para que los programadores nos entendamos mejor y sobre todo para poder
reutilizar las rutas, se implementa un mecanismo de ruta lógica que mapea
esa URL real con un nombre descriptivo. De esta manera como humanos re-
cordaremos mejor la secuencia articulo_prensa_introduccion que /blog/
articulos/index/search/introduccion-a-la-empresa, (está exagerado para que lo
veas más claro).
3
No lo volveré a mencionar, recuerda que bootstrap en nuestro caso es el index.php
o cargador de la aplicación.
20
3. El controlador: Slim
Para referirnos a las rutas lógicas necesitamos usar dos métodos que nos van
a permitir convertir las rutas lógicas a físicas: uno para el caso de la vista y otro
para el caso del controlador.
No creo que tenga que recordarte que en las etiquetas HTML o en las redirec-
ciones PHP tenemos que indicar una URL física y correcta.
Manos a la obra
Veremos a lo largo del libro cientos de veces la declaración de rutas, pero te
presento aquí el mecanismo para que te vaya sonando.
¿Atención duplicada?
Imagínate que quieres que la aplicación atienda rutas como: /es/quiero-que-
me-conozcas o /es/contacto, de tal manera que quiero-que-me-conozcas y
contacto son slugs que están guardados en la bbdd en la tabla de páginas
estáticas (argucia muy útil para delegar el mantenimiento de ese contenido en
4
No confundir con slug ya que slug es parte de la ruta real.
21
Programación PHP profesional con Slim, Paris y Twig
Conclusión
Me doy por satisfecho si en este punto del libro he sabido transmitirte, dentro
del concepto de patrón MVC, cuál es el cometido del controlador (Slim en nues-
tro caso), el cual vamos a usar a lo largo de todo el texto.
22
4 El modelo:
IdiORM & Paris
Contenido
» ¿Qué es un ORM?
» ¿Por qué un ORM?
» Métodos disponibles
» Rizando el rizo
» Relaciones entre tablas
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
¿Qué es un ORM?
Un ORM es una capa de abstracción sobre la de manejo de los datos que permite
manipularlos mediante una interfaz diferente a la que habitualmente conocemos,
que no es otra que el PDO que nos proporciona PHP. El uso de un ORM incluye
algunas ventajas como el mapeo de registros sobre clases de PHP.
Si tenemos que leer un registro de una tabla con las órdenes que nos provee
PHP haríamos primero un SELECT y, conociendo de antemano la estructura
de ese registro, accederíamos al campo nombre así:
Si creamos una clase que tenga los campos necesarios y creamos una interfaz
entre la bbdd y nuestra aplicación, que solo con pedirle que recupere el registro
x, lo haga y además despliegue los campos leídos sobre las propiedades de
nuestra clase, tendremos un ORM.
24
4. El modelo: IdiORM & Paris
En realidad nuestro ORM no nos va a exigir que creemos las propiedades que
va a tener nuestro registro, sino que mediante el uso de los métodos __get y
__set va a hacer un mapeo virtual.
Hay otras implementaciones más robustas en las que todo debe estar correc-
tamente declarado: los nombres de las propiedades, los tipos de las mismas,
incluso validaciones de blancos, etc., como pueden ser Doctrine y Propel.
Los ORM más serios incluyen mecanismos para hidratar los registros con las
respectivas relaciones. Como ya sabrás una bbdd relacional permite mante-
ner las tablas relacionadas entre ellas, Doctrine te permite cargar un cliente y
detrás todas las facturas que tenga vinculadas, direcciones de envío, etc. No-
sotros veremos cómo hacer eso más tarde con nuestro ORM empleando unas
cuantas instrucciones.
Para nuestro proyecto de ejemplo y estoy seguro de que para el 90% de los
trabajos que lleves a cabo a lo largo de tu vida profesional este es más que
suficiente, además de que aprendiendo las bases con este ORM el salto a otro
más complejo no supone tanto esfuerzo.
Para serte franco he visto y he hecho bastantes proyectos en los que así se ha
solucionado la parte del modelo. Pero creo que estarás de acuerdo conmigo
en que si estamos viendo cómo hacer proyectos siguiendo el patrón MVC no
queda muy apropiado utilizar sentencias directamente o no reutilizar código,
ofuscarlo, o cualquier otra mala práctica. De alguna manera, estamos sentando
las bases de la programación profesional.
Para que entiendas perfectamente lo que te digo voy a ponerte un ejemplo ima-
ginario de cómo acceder a una tabla de alumnos en la cual tenemos un ID y un
nombre. Voy a presentarte dos planteamientos, uno basado en las sentencias
25
Programación PHP profesional con Slim, Paris y Twig
convencionales que seguro que conoces, y el otro en la aplicación del ORM que
vamos a ver: Paris sobre IdiORM. El código fuente de este capítulo lo puedes
encontrar en http://github.com/jlaso/book-spt-code
Para poder llevar a cabo los ejemplos de este capítulo vas a necesitar una base
de datos mysql dentro de tu equipo con nombre “db_test”, con permisos com-
pletos para el usuario “user” con password “password”, donde veas estos litera-
les en los códigos de ejemplo sustitúyelos por los que hayas puesto en tu caso.
mysqli_query($hdl, <<<EOD
CREATE TABLE IF NOT EXISTS `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
EOD
);
Vamos a ver ahora el mismo ejemplo utilizando un poco de la magia del ORM ;<)
Voy a aprovechar y retomo el composer para que veas lo fácil que es integrar
una librería en nuestro proyecto, evidentemente si queremos trabajar con el
ORM necesitaremos las clases necesarias para esto.
26
4. El modelo: IdiORM & Paris
27
Programación PHP profesional con Slim, Paris y Twig
Primero voy a mostrarte la salida del mismo ejemplo usando el ORM para que
veas que el resultado es el mismo:
Lo que varía evidentemente es cómo hemos llegado hasta él. Veamos juntos el
código de sample_orm.php:
<?php
// @1
require __DIR__ . '/vendor/autoload.php';
// @2
/** esta clase mapea la tabla con una clase PHP (thanks ORM) */
// @3
ORM::configure('mysql:host=localhost;dbname=db_test');
ORM::configure('username', 'user');
ORM::configure('password', 'password');
// @4
$allStudents = Model::factory(‘Student’)->find_many();
// @5
foreach ($allStudents as $student){
print sprintf('%06d | %30s' . PHP_EOL, $student->id, $student->name);
}
28
4. El modelo: IdiORM & Paris
Repasando el código:
@1:
Antes que nada necesitamos incluir en nuestro código los vendors que nos ha
creado composer en base a nuestros requerimientos del archivo composer.
json. Nos ha facilitado la tarea y ha creado un autoload.php que con solo in-
cluirlo nos deja accesibles todas las clases (composer hace uso extensivo de
los namespaces de PHP 5.3). En todo caso a partir de ese require podemos
invocar los métodos y clases de Paris e IdiORM.
@2:
Ahora vamos a ver quizás lo más importante. Necesitamos crear una clase
que extienda de Model la clase que nos aporta Paris. Esta herencia nos va a
permitir emplear todos los métodos del ORM sobre nuestra clase y nos va a
asociar cada campo de la tabla a una propiedad «pública» del objeto (puedes
ver que en realidad no existe tal propiedad y se hace uso de los métodos má-
gicos __get y __set).
@3:
@4:
Y ya por fin llegamos el meollo del asunto: el acceso a los registros. Puedes ob-
servar que en realidad no hemos hecho uso directamente de la clase Student
que creamos, sino que mediante un patrón factoría1 invocamos su creación a
través de la superclase Model.
1
factory pattern
29
Programación PHP profesional con Slim, Paris y Twig
Está claro que internamente al final el ORM hace un SELECT como habíamos
hecho en el ejemplo plano anterior, pero creo que estarás de acuerdo conmigo
en que es mucho más clara la sentencia anterior que el SELECT en plano de
SQL. Este, en cuanto tenga un par de condiciones WHERE tienes que analizarlo
mentalmente cada vez para saber qué pretende el programador obtener en esa
línea, por no hablar del mantenimiento futuro.
@5:
Y comparando:
Sin palabras.
Métodos disponibles
A lo largo de los ejemplos del libro y el código de nuestra aplicación My-simple-
web verás instrucciones del ORM que seguramente no te costará seguir pues
el autor de IdiORM&Paris ha sido muy oportuno con los nombres de las mis-
mas, identificando plenamente la acción que realizan.
30
4. El modelo: IdiORM & Paris
Persite la entidad en la
base de datos, si el re-
save() gistro es nuevo utilizará $cliente->save();
un INSERT y si ya existía
hará un UPDATE.
31
Programación PHP profesional con Slim, Paris y Twig
Model::factory('Cliente')
Permite borrar varios re-
delete_many() ->where('borrado', 1)
gistros al mismo tiempo. ->delete_many();
Rizando el rizo
El ORM nos permite hacer prácticamente todas las consultas que podamos hacer
con sentencias SQL directas. No obstante, las mezclas imposibles de obtener en el
ORM debidas a su naturaleza, se pueden obtener mediante raw_query o where_
raw, este sería el caso de un where con condiciones OR, que veremos después.
Pero lo más importante: puedes crear relaciones entre las tablas (lo vemos
enseguida).
$student = Model::factory('Student')->find_one(1);
Y por qué no: hacer filigranas con la clase que deriva de Model. He dicho que
solo tiene que extender de Model, para un funcionamiento normal, es decir,
acceder a registros, persistir, etc. La implementación de los métodos mágicos
__get y __set ayudan al ORM a mapear los campos y hacer el trabajo sucio
por nosotros. Pero si necesitamos validar el objeto antes de persistirlo pode-
mos hacerlo, si queremos rellenar unos valores por defecto cuando creamos un
registro nuevo, podemos hacerlo.
32
4. El modelo: IdiORM & Paris
Plano ORM
mysqli_query(
$hdl, $student = Model::factory('Student')
"INSERT INTO `student` ->create();
Insertar (`name`) VALUES $student->name = 'JOSELUIS';
('JOSELUIS')" $student->save();
);
$name = "JOSELUIS";
$rows = mysqli_query($hdl,
sprintf("SELECT * FROM `student` $name = "JOSELUIS";
WHERE `name` = '%s'", $name)); $student = Model::factory('Student')
if ($student = $rows ->where('name', $name)
->fetch_assoc()){ ->find_one();
mysqli_query($hdl, if($student instanceof Student){
sprintf("UPDATE `student` $student->name = $name . '-' .
Update SET `name` = '%s-%d' date('U');
WHERE `id` = '%d'", $student->save();
$name, date('U'), }else{
$student['id'])); throw new \Exception('Alumno ' .
}else{ $name . ' no existe');
throw new \Exception( }
'Alumno ' . $name .
' no existe');}
// delete
$name = "JOSELUIS";
$rows = mysqli_query($hdl,
sprintf("SELECT * FROM `student` $name = "JOSELUIS";
WHERE `name` = '%s'", $name)); $student = Model::factory('Student')
if ($student = $rows ->where('name', $name)
->fetch_assoc()){ ->find_one();
mysqli_query($hdl, if($student instanceof Student){
Delete sprintf("DELETE $student->delete();
FROM `student` }else{
WHERE `id` = '%d'", throw new \Exception('Alumno ' .
$student['id'])); $name . ' no existe');
}else{ }
throw new \Exception(
'Alumno ' . $name .
' no existe');
}
33
Programación PHP profesional con Slim, Paris y Twig
cias SQL sencillas. Podemos por ejemplo seleccionar los alumnos que están
inscritos al curso con ID n.º 2, o podemos acceder al curso con ID n.º 2, e indi-
car al ORM que existe una relación entre cursos y alumnos, de tal manera que
con la instrucción $curso->alumnos() nos dé la lista de alumnos que están
inscritos al curso en cuestión.
Aunque ampliaré en la tercera parte este concepto quiero que te hagas una
idea de cómo sería una relación entre las tablas alumnos y cursos:
<?php
34
4. El modelo: IdiORM & Paris
// ...
$grade = Model::factory('Grade')->create();
$grade->name = '1st';
$grade->save();
// insert
$student = Model::factory('Student')->create();
$student->name = 'JOSELUIS';
$student->grade_id = $grade->id;
$student->save();
$student = Model::factory('Student')->create();
$student->name = 'ANGEL';
$student->grade_id = $grade->id;
$student->save();
Ejemplo 8: sample_relations.php
Como ves es fácil integrar las relaciones dentro del ORM, lo veremos con más
detalle cuando veamos el modelo en profundidad, en el capítulo 9.
Conclusión
Espero haberte transmitido por qué es más ventajoso el uso de un ORM que
las sentencias SQL incrustadas directamente en el código:
» Claridad
» Mantenimiento
» Menos acoplamiento con la bbdd, al no escribir directamente senten-
cias SQL podemos migrar a un sistema NOSQL, SQLITE, etc.
35
5 La vista: Twig
Contenido
» ¡Ay, la vista, la vista!
» Vamos por partes
» ¿Qué es Twig?
» ¿Cómo funciona?
» Tipos de datos
» Toma de decisiones
» Iteraciones
» Funciones de Twig
» Extender Twig con funciones propias
» Macros
» Resumen
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
De nuevo, como en otros capítulos, quiero introducir este con la famosa pregunta:
Espero que al final de este capítulo no solo las preguntas anteriores te parez-
can inoportunas sino que en algunos casos hasta las consideres aberraciones.
<?php
include 'header.php';
include 'body.php';
include 'footer.php';
Y con suerte… y con esfuerzo, podría llegar a tener un sistema más sofisticado,
parecido a este:
<?php
drawHeader($options);
drawBody($data, NO_SIDEBAR);
drawFooter($options);
38
5. La vista: Twig
De acuerdo, cada uno hemos utilizado nuestro truquillo para no tener que repetir
el código de un bloque a otro.
Lo que ocurre es que en el primer ejemplo la parte del header.php suele quedar
coja, pues termina en la etiqueta </head> o <body> que se cerrará normal-
mente en el archivo footer.php, si tenemos un IDE con realce de sintaxis (lo
cual recomiendo sobremanera) nos estará avisando todo el tiempo de que hay
etiquetas que no están cerradas o no han sido abiertas.
Para el segundo caso: utilizando métodos tendremos más éxito. Es fácil que po-
damos hasta crear temas (themes o skins) diferentes, pero a la hora de incrustar
css, javascript, etc., se nos va a hacer eterno, a no ser que incrustemos el HTML,
css y js dentro de las funciones, con la consiguiente pérdida de visibilidad.
<?php // view-blocks.php
function drawHeader($options)
{
?>
<html>
<head>
<title><?php echo $options['title']; ?></title>
</head>
<body>
<?php
De verdad te digo que si lo de arriba te parece normal el salto que te voy a pro-
poner a continuación te va a parecer de 100 metros.
39
Programación PHP profesional con Slim, Paris y Twig
<?php
return $template;
}
// Y la invocaríamos así:
renderView('demo.template', array(
'title' => 'mi web',
'content' => 'Aquí va el contenido de mi primer div',
'div_id' => 'main',
)
);
40
5. La vista: Twig
He perdido un poco más de tiempo en estos ejemplos porque quiero dejar claro
que Twig no hace magia, aunque te lo pueda parecer, es muy potente y robus-
to, pero al final son solo sustituciones y cálculos.
NOTA
No dejes de visitar la web oficial de Twig: http://twig.sensiolabs.org/
¿Qué es Twig?
Twig es un motor de plantillas creado por Fabien Potencier, en el que su mar-
cado es totalmente simétrico y que actualmente es muy conocido porque se
integra de serie en el conocido framework Symfony. Hay más motores de plan-
tillas: Smarty, Django, etc. ¿Por qué he elegido este? Bueno, en realidad me
lo presentó un compañero (gracias Fran ;<) y me enamoró desde el principio.
Una vez que trabajas con él, la verdad es que cuesta realizar proyectos sin
contar con su inestimable colaboración.
41
Programación PHP profesional con Slim, Paris y Twig
¿Cómo funciona?
He querido introducir el concepto en el ejemplo anterior, un archivo twig no
es más que código HTML con un marcado especial, para ello Twig se reserva
unas marcas basadas en llaves (como ya he dicho, simétricas) que le permiten
llevar a cabo toda su «magia».
Muy sencillo, ¿no? Las marcas más usuales son la pareja {{ }}, mientras que
{% %} nos permite aumentar la potencia de nuestras plantillas introduciendo
cálculos, condiciones, etcétera.
Herencia de plantillas
Imagínate que tenemos un layout general con lo básico: etiqueta html, head,
body, los css que se cargan en todas las páginas, los javascripts, etc. Un archi-
vo que por sí mismo es un HTML bien formado.
{# base.html.twig #}
<html>
<head>
<title>{{ title }}</title>
42
5. La vista: Twig
</head>
<body>
<div>
{{ content }}
</div>
</body>
</html>
Imagínate que cada vez que quieras crear una página nueva tengas que copiar
el código anterior e ir ampliando conforme lo necesitaras en esa nueva página.
Absurdo, ¿verdad?.
Pues es aquí donde entra la etiqueta {% extends %}. La vamos a ver con un
ejemplo.
{# base.html.twig #}
<html>
<head>
<title>{{ title }}</title>
{% block stylesheets %}
<style type="text/css">
body{
font-family: Arial;
}
</style>
{% endblock stylesheets %}
43
Programación PHP profesional con Slim, Paris y Twig
</head>
<body>
<div>
{% block content %}{% endblock content %}
</div>
</body>
{% block javascripts %}
<script type="text/javascript">
alert('hola');
</script>
{% endblock javascripts %}
</html>
{% extends 'base.html.twig' %}
{% block content %}
<div id="main">
{% block main_block %}
{% endblock main_block %}
</div>
<div id="footer">
{% block footer %}
<p>Este es el footer de mi web</p>
{% endblock footer %}
</div>
{% endblock content %}
Como te decía, esta capa intermedia utiliza base.html.twig de base (lo extiende)
y respetando sus bloques javascript y stylesheets lo que hace es fraccio-
nar el bloque content en dos, ya que quien ha diseñado el frontend ha pensado
que el footer es tan común que lo saca fuera, pero por otro lado lo deja como
bloque porque hay una página en el frontal que no necesita footer (por ejemplo
la del vídeo promocional).
44
5. La vista: Twig
{% extends 'frontend.html.twig' %}
{% block main_block %}
<div class="cointainer">
bla bla bla ...
</div>
{% endblock main_block %}
Desde luego un ejemplo muy simplificado, recuerda que lo que quiero que lle-
gues a comprender es la dinámica.
Tipos de datos
Los tipos de datos que puede manejar Twig son todos aquellos que pueda
manejar PHP, hasta tal punto que cualquier clase que tenga getters sobre atri-
butos protected o private son accesibles directamente desde Twig, bien con el
nombre del método getValor() o solo mediante valor1. Veamos un ejemplo:
{# ejemplo getters #}
<pre>
class SampleClass
{
private $valor;
Además, todos los tipos de datos escalares, los arrays convencionales y los
arrays asociativos, son accesibles directamente por su clave (numérica o aso-
ciativa) o iterando sobre ellos.
1
Evidentemente las propiedades públicas también son accesibles directamente.
45
Programación PHP profesional con Slim, Paris y Twig
Toma de decisiones
Para completar la guinda del pastel, podemos utilizar condicionales.
Por ejemplo, imagina que quiero mostrar el nombre del usuario si este ha inicia-
do sesión y un enlace al login en caso contrario.
{% if user is defined %}
Bienvenido, {{ user }}
{% else %}
<a href="login">Login</a>
{% endif %}
Iteraciones
Para iterar sobre una lista de elementos podemos utilizar el bucle for.
46
5. La vista: Twig
{% set lista = [] %}
<ul>
{% for item in lista %}
<li>{{ item }}</li>
{% else %}
<li>No hay elementos</li>
{% endfor %}
</ul>
47
Programación PHP profesional con Slim, Paris y Twig
Funciones de Twig
Como ya te he mencionado antes, la página oficial de Twig es la mejor fuente
de ejemplos de las distintas funciones que ofrece. Funciones y filtros, que aun-
que parecido, no son lo mismo.
NOTA
Hasta ahora no he abordado el tema del estilo.
Se aconseja dejar un espacio entre las llaves y el interior, en cambio se
sugiere que el signo | que hace de tuberia (como en *nix) entre lo que se
va a mostrar y el filtro a aplicar esté pegado a ambos.
Así:
{% set content = 'Esta <strong>letra</strong> está enfatizada' %}
Este es el contenido de la variable content:
{{ content|raw }}
{{ content|default('<strong>Null</strong>')|raw }}
Ahora verás que las funciones son más sencillas porque son como las funcio-
nes de PHP:
{% set foo = {'foo':1, 'bar':'2'} %}
48
5. La vista: Twig
{{ attribute(foo,'bar') }}
mostraría un 2
Sencillo, ¿verdad? Solo tenemos que tener una función global o un método
estático de cualquiera de nuestras clases y mediante addFunction vinculamos
la función Twig con el método global o estático que la va a resolver. Fíjate que
hay que indicar la ruta(namespace) correctamente.
Macros
Hemos visto cómo extender Twig con funciones y filtros propios, pero en oca-
siones no es necesario llegar hasta ese extremo, o el resultado que se preten-
de obtener de la función tiene tanto HTML que realizar una función para ese
menester choca un poco con la idea de la separación de capas. En todo caso,
49
Programación PHP profesional con Slim, Paris y Twig
sea por el motivo que sea, debes conocer este elemento que otorga a twig una
potencia adicional (más si cabe).
Como ejemplo para ilustrar el uso de las macros vamos a partir de la creación de
un formulario en el cual tenemos un montón de labels seguidos de su correspon-
diente input, una tarea repetitiva y que además da poco juego a la hora de alterar
el comportamiento general del conjunto, como por ejemplo podría ser el hecho de
añadir una clase nueva a todos los input, o solo a aquellos que sean requeridos.
<ul>
<li>
<label for="nombre">Nombre</label>
<input type="text" name="nombre" value="{{ data.nombre }}"/>
</li>
<li>
<label for="apellido">Apellido</label>
<input type="text" name="apellido" value="{{ data.apellido }}"/>
</li>
<li>
<label for="aficiones">Aficiones</label>
<input type="text" name="aficiones" value="{{ data.aficiones }}"/>
</li>
<li>
<label for="libros">Libros</label>
50
5. La vista: Twig
Y por otro lado la plantilla twig que va a hacer uso de esa macro, fíjate sobre
todo en cómo se importan y que luego su uso es como el de una función con-
vencional.
{% set data = {
'nombre': 'Joseluis',
'apellido': 'Laso',
'aficiones': 'Informática',
'libro': 'Programación PHP profesional con Slim,Paris y Twig'
}
%}
<ul>
{% for name,value in data %}
<li>
{{ macros.drawField(name,'text',value) }}
</li>
{% endfor %}
</ul>
51
Programación PHP profesional con Slim, Paris y Twig
Resumen
Como corolario te muestro una lista de funciones y filtros que tiene Twig en
la actualidad2. No pretende ser un compendio exhaustivo, sino más bien una
referencia. En todo caso te aconsejo siempre que acudas a la documentación
oficial para ejemplos e información actualizada.
Filtro Comentario
Independientemente de si está activado el autoescape a nivel
de Twig podemos marcar un bloque para que escape (todo,
html, js, o falso).
autoescape-
endautoescape {% autoescape [|'html'|'js'|false] %}
Everything will be automatically
escaped in this block
{% endautoescape %}
2
La actualidad se refiere a la versión que actualmente uso para My-simple-web, Twig
es un proyecto en expansión y continuamente se añaden nuevas funcionalidades.
52
5. La vista: Twig
Filtro Comentario
Igual que el bucle foreach de PHP pero con las variables inver-
tidas, {% for item in collection %}, además permite
declarar una sección para cuando no existen elementos sobre
los que iterar (else).
<select name="tallaje">
for-else-endfor
{% for talla in tallas %}
<option value="talla.id">{{ talla.name }}</option>
{% else %}
<option>No hay tallas para seleccionar</option>
{% endfor %}
</select>
Condicional.
{% if user is not null %}
if-else-endif {{ user.name }}
{% else %}
<a href="login">Inicia sesión</a>
{% endif %}
Quita los espacios y las nuevas líneas entre el texto literal y las lla-
ves reservadas de Twig, para producir por ejemplo textos exactos.
{% spaceless %}
spaceless- Además de lo evidente, esta utilidad me ha resultado
endspaceless útil para formatear bien el código en el twig y no ge-
nerar espacios o saltos de línea en el HTML que pueden
resultar en un comportamiento inesperado.
{% endspaceless %}
53
Programación PHP profesional con Slim, Paris y Twig
Filtro Comentario
Extensión de la herencia que permite utilizar otra plantilla de un
use modo similiar a extends. Véase la documentación original en:
http://twig.sensiolabs.org/doc/tags/use.html
Muestra el contenido interior tal cual, sin interpretar.
{% verbatim %}
verbatim- {{ 'hola y adios' }}
endverbatim {% endverbatim %}
muestra:
{{ 'hola y adios' }}
Filtro Comentario
Valor abosoluto.
{{ -234|abs }}
abs
muestra:
234
iterable|batch(n,[char])
Completa un array con elementos char para que el número de
elementos sea múltiplo de n:
{% set items = ['a', 'b', 'c'] %}
<table>
{% for row in items|batch(2, 'x') %}
<tr>
{% for column in row %}
batch <td>{{ column }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
produce:
<table>
<tr><td>a</td><td>b</td></tr>
<tr><td>c</td><td>x</td></tr>
</table>
54
5. La vista: Twig
Filtro Comentario
cadena|convert_encoding(destino,origen)
Este filtro convierte una cadena desde un charset (el segundo
convert_encoding
parámetro) a otro charset (el primer parámetro):
{{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
muestra:
I like my-foo and bar
55
Programación PHP profesional con Slim, Paris y Twig
Filtro Comentario
Devuelve el último elemento de un array o de una cadena.
last {{ [1,2]|last }} muestra 2
{{ '1234'|last }} muesta 4
number_format muestra:
1,123.40
56
5. La vista: Twig
Filtro Comentario
El filtro slice extrae una secuencia de un string o un array.
{{ '12345'|slice(1, 2) }} muestra 23
slice {% for i in [1, 2, 3, 5]|slice(1, 2) %}
{# itera entre 2 y 3 #}
{% endfor %}
Función Comentario
Acceso a un array asociativo u objeto de manera programática.
{% set foo = {'foo':1, 'bar':'2'} %}
attribute {{ attribute(foo,'bar') }}
muestra 2
57
Programación PHP profesional con Slim, Paris y Twig
Función Comentario
Muestra el contenido del bloque indicado.
block
{{ block('body') }}
Comprobación Comentario
Devuelve true si la variable está definida.
defined
{{ valor is defined ? valor : 'undefined' }}
58
5. La vista: Twig
Comprobación Comentario
Devuelve true si el valor es par.
even
{% if valor is even %} ... {% endif %}
Operador Comentario
Utilizado sobre todo en los bucles for y en comprobaciones de
si un elemento está contenido en una lista:
{% if 1 not in [1, 2, 3] %}
in ...
{% endif %}
{% for item in items %}
...
{% endfor %}
Conclusión
Este capítulo ha sido extenso pero productivo, espero que hayas asimilado
gran parte de lo expuesto y, si no, que hayas marcado estas páginas para vol-
ver a ellas cuando tengas que consultar algo sobre Twig.
59
Programación PHP profesional con Slim, Paris y Twig
60
Parte II: Herramientas
6 Composer
Contenido
» Introducción
» Repositorios privados
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
Composer proporciona una manera sencilla de tener todos los requerimien-
tos de tu aplicación controlados mediante un sencillo archivo de texto .json.
Mediante su ejecutable se puede desplegar una completa aplicación en un
instante1. Como dice la documentación oficial de composer, es un gestor
de dependencias, no un gestor de paquetes, ya que no instala nada a nivel
global.
Al principio de esta obra te describí someramente los pasos para crear una apli-
cación php utilizando composer. Dando por sentado que al menos composer lo
tienes instalado (http://getcomposer.org), vamos a indagar un poco más en los
entresijos de esta estupenda herramienta.
1
Un instante es una manera de hablar, la verdad es que no es una tarea inmediata.
64
6 Composer
65
Programación PHP profesional con Slim, Paris y Twig
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-0": { "Slim": "." }
}
}
En realidad este archivo es el del proyecto slim, luego veremos cómo crear
nuestros propios composer.json. De nuevo quiero que le pierdas el miedo a
este tipo de documentos.
Por tanto no ha creado un proyecto como los que vamos a utilizar en este libro,
ya que en lugar de una aplicación con varios módulos nos ha creado una apli-
cación con un único módulo, no es que no le podamos añadir más pero va a re-
sultar más rápido empezar con un archivo composer.json propio e ir incluyendo
los módulos que vayamos precisando a medida que nuestro proyecto crezca.
Fíjate además que la carpeta Slim está en la raíz y no dentro de la carpeta ven-
dor, de alguna manera el proyecto es solo Slim.
Con “2.*” le estamos indicando que nos da igual qué versión siempre que su
número mayor sea 2, en todo caso se descargará la más reciente de las 2.x.
NOTA
Buenas prácticas: se recomienda incluir la carpeta vendor dentro de
.gitignore, ya que se puede recrear con el programa composer.
66
6 Composer
con las versiones reales instaladas. Install instala los paquetes que haya en el
archivo composer.json o a las versiones que indique el archivo composer.lock,
en caso de que exista (véase siguiente punto).
Composer.lock
Cuando composer termina de instalar escribe en el archivo composer.lock las
versiones exactas que ha descargado/instalado. Si el archivo existe, cuando se
ejecuta composer install se instalan exactamente las versiones indicadas en
él, por eso es conveniente seguir este archivo con git.
Repositorios privados
¿Qué ocurre si los paquetes que queremos utilizar no están en packagist? ¿Y
si además ni siquiera son públicos?
Este caso también está contemplado por composer, no necesitamos hacer nada
raro, podemos disfrutar de la potencia de composer aunque los paquetes no sean
públicos. Únicamente hace falta en el composer.json indicarle cuáles son los orí-
genes de los paquetes, y composer mirará ahí antes de ir a buscar a packagist.
{
"name": "jlaso/demo-composer1",
"description": "Para utilizar paquetes privados",
"repositories": [
{
"type": "vcs",
"url": "git@gitlab.com:jlaso/test-module1.git"
}
],
"require": {
"php": ">=5.3.3",
bla, bla, bla
"jlaso/test-module1": "*"
}
}
Una cosa a tener en cuenta: el usuario que utiliza composer con esta configu-
ración ha de poder tener acceso al repositorio indicado, ya sea con clave SSH
o con usuario y contraseña, si no composer fallará.
67
Programación PHP profesional con Slim, Paris y Twig
puerto convencional (22), la declaración sería así (para un puerto 1234, una IP
192.168.1.1, y un usuario user-ssh con permisos suficientes):
"repositories": [
{
"type": "vcs",
"url": "ssh://user-ssh@192.168.1.1:1234/var/git/test.git"
}
],
Estas son las cláusulas que debemos utilizar para cada uno de los casos:
Clave Uso
68
6 Composer
{
// ...
"scripts": {
"post-install-cmd": [
"rm -rf app/cache/*"
],
"post-update-cmd": [
"rm -rf app/cache/*"
]
},
// ....
}
NOTA
Esta carpeta (app/cache) no se debe seguir por el git, quizás
te preguntes entonces por qué es necesario borrarla. La expli-
cación es bien sencilla: la primera vez que nos bajemos el pro-
yecto con git clone la carpeta estará vacía pero en las diferen-
tes ejecuciones de la aplicación se van generando diferentes
archivos en app/cache que permiten su ejecución optimizada.
Por tanto durante una actualización o instalación de paquetes
querremos que la aplicación esté como cuando la clonamos
la primera vez.
Esto nos permite que al desplegar una nueva versión en el servidor que requie-
re de una actualización o instalación de paquetes borre directamente la caché
de la aplicación.
Pero no hablemos de desplegar con git pues los más puristas no lo consideran
un método de despliegue; esto seguramente daría para otro libro.
Conclusión
Espero haberte transmitido la importancia de esta aplicación en lo que a de-
sarrollo web con PHP se refiere. Encontrarás en él un valioso aliado y te
permitirá sobre todo mantener al día tus proyectos y unificar los criterios a la
hora de versiones instaladas de entre las que hayas probado. Si un paquete
que utilizas al actualizarlo deja inestable tu web siempre puedes volver a la
versión anterior y bloquear tu composer.json en esa versión estable conocida
para tu proyecto.
69
Programación PHP profesional con Slim, Paris y Twig
70
7 Git como SCM
Contenido
» Introducción teórica
» Explicación práctica
» Instalación
» Comandos básicos: init y clone
» Funcionamiento en el día a día
» Ejemplos prácticos de uso
» Buenas prácticas
» Comandos más usados de git
» Rizando el rizo
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción teórica
SCM es un sistema de control de versiones para los archivos fuentes (source
control management).
Hay diferentes SCM, entre los que destacan git y subversion. Aunque el fun-
cionamiento interno es diferente entre uno y otro, comparten la misma idea: un
repositorio es un lugar local o remoto donde se almacenan los archivos fuentes,
de tal manera que podemos hacer un seguimiento de la versión de cada archi-
vo, volver a puntos anteriores, aplicar parches, y un largo etcétera.
En este libro vamos a ver solo el caso de git, así que a partir de aquí nos cen-
traremos en este SCM. Las mayores diferencias están, por supuesto, en que
los comandos son diferentes, pero sobre todo en el tratamiento que se hace de
las ramas y de la centralización o distribución de repositorios.
Git es un SCM distribuido, se dice esto porque cada uno de los distintos repo-
sitorios que luego veremos cómo crear comparten en principio la misma infor-
mación, pudiendo hacer las veces de repositorio central cualquiera de ellos.
Para que un SCM sea efectivo debe proveer los mecanismos oportunos para
sincronizar la distinta información entre los diferentes repositorios.
72
7. Git como SCM
Los ejemplos que pondré en este capítulo van a suponer la siguiente configu-
ración:
- Un repositorio central.
- Diferentes repositorios para cada uno de los desarrolladores en cada
una de sus máquinas.
- Una copia del repositorio en el servidor de desarrollo y otra en el de
producción.
Explicación práctica
Cuando un grupo de programadores colabora de manera conjunta en un pro-
yecto, existen a priori varias maneras de trabajar:
» directamente sobre una carpeta compartida en una red local o en una
carpeta en el cloud (como Dropbox o similares);
» subir y compartir directamente en el sitio FTP o soluciones parecidas;
» compartiendo los fuentes mediante un disco duro externo o dispositi-
vo removible;
» obligándose cada desarrollador a tocar solo archivos de un área de-
terminada y al final ponerlas todas en común;
» utilizar un SCM.
Después de haber trabajado con soluciones como las primeras (sí, las he pro-
bado todas a lo largo de mi vida profesional), uno se da cuenta enseguida de
las deficiencias de las mismas, entre las que podemos contar:
» concurrencia de accesos que provocan inconsistencia en los archivos;
» no se puede controlar el versionado;
» tampoco se puede volver atrás en caso de detectar un error en alguna
implementación;
» y, por supuesto, el número de colisiones y dolores de cabeza cuando
el número de programadores concurrentes aumenta es significativo.
73
Programación PHP profesional con Slim, Paris y Twig
Git
Aunque no falto de complejidad, un sistema de control de versiones, una vez
aprendido y aplicado de forma sistemática ofrece algo de lógica al sinfín de idas
y venidas de archivos, para los casos enumerados antes.
En primer lugar tenemos desglosadas las partes en las que git organiza inter-
namente la información dentro del sistema de archivos. Es necesario aclarar
que cuando hagamos un listado de las carpetas y archivos del repositorio no
veremos nada de esto. Cada una de las partes identificadas de alguna manera
son los estados por los que pasan los archivos hasta llegar a estar consolida-
dos e integrados en el repositorio.
Vamos por partes: en primer lugar veamos el área de trabajo (working directory),
que no es otra cosa que el área en la que se agregan los archivos, se modifican
o se borran. El contenido de esta área puede visualizarse haciendo un git sta-
tus, que nos listará todos los elementos con cambios, nuevos o borrados1, de
1
Los archivos borrados aparecen al hacer git status si los archivos estaban integrados
en el repositorio, ya que si son nuevos y los borramos antes de hacer un git add para
git no han existido nunca.
74
7. Git como SCM
tal manera que no están integrados dentro del repositorio hasta que no se hace
un git add, que pasa los archivos al staging area, y por fín un git commit que
nos validará los cambios y los integrará en nuestro repositorio local.
La órden que introduce los cambios es git stash y la que los recupera es
git stash pop si queremos que esos cambios desaparezcan del stash o git
stash apply si queremos recuperarlos y conservarlos en el stash.
Veamos como git sigue o no los cambios en los archivos en el working direc-
tory.
NOTA
Puede que encuentres útil la orden:
git rm --cached filename
Si quieres obligar a git a que ignore el archivo filename que hasta ahora
había sido seguido, ya que con sólo la mera introducción de su patrón en
.gitignore no conseguiras “deshacerte” de él. La orden que te comento bo-
rra ese archivo de la cache del working directory para que realmente borre
el rastro de ese archivo y se pueda ignorar de manera efectiva.
75
Programación PHP profesional con Slim, Paris y Twig
Instalación
La instalación de git está muy bien documentada tanto en la página del pro-
yecto como en la red. Por regla general es suficiente con utilizar el gestor de
paquetes del sistema operativo *nix, solo en casos muy aislados me ha tocado
instalar desde las fuentes, como en alguna versión 5.x de centos. Para los ca-
sos de sistemas operativos comerciales existen instaladores gráficos.
Si no tienes la suerte de trabajar en un sistema operativo basado en POSIX,
con una terminal completa y funcional, te tocará instalar extensiones de tipo
bash. Git incorpora en su instalador para Windows esta opción por defecto, si
tienes instalado algún terminal como putty lo puedes seguir utilizando, aunque
particularmente considero mejor la implementación bash-git porque tiene un
tratamiento más natural de las carpetas y archivos y te hace sentir casi como si
estuvieras delante de una terminal *nix.
Para el caso de MacOs, puedes decidirte por el instalador gráfico o bien por las
distribuciones basadas en macports o brew, en ambos casos la instalación es
muy sencilla y se resuelve mediante un brew install git o port install
git. En este caso el terminal de este sistema operativo viene por defecto con
todo el sabor *nix.
Para distribuciones basadas en Debian (Ubuntu incluida) nos será suficiente
con hacer sudo apt-get install git-core.
En el caso de las distribuciones basadas en Fedora (Centos incluido) podre-
mos intentar hacer sudo yum install git-core. En ocasiones aisladas me
he visto obligado a instalar desde las fuentes en algunas versiones de Centos,
como he comentado más arriba.
No tengo experiencia en otras distribuciones de Linux ni otros sistemas opera-
tivos, pero la documentación por la red sobre la instalación de git es abundante
y no tendrás problemas en conseguirlo.
Las cuestiones relacionadas con Linux te aprovecharán tanto si tienes la suerte de
trabajar directamente en este sistema operativo como si tienes la suerte de tener
76
7. Git como SCM
un servidor propio, que de seguro estará basado en alguna distribución de las men-
cionadas. Como recomendación personal, si aún no te has decidido por ninguna
en concreto, es Centos para el servidor y Ubuntu para el ordenador de trabajo.
Ten en cuenta en todo caso que por regla general las comunicaciones del re-
positorio local al remoto y viceversa se hacen por ssh, con lo cual tendrás que
tomar las medidas oportunas para crear las claves en tu máquina (si no lo has
hecho ya) y autorizar esa clave2 en el servidor para evitar estar continuamente
introduciendo contraseñas.
Para clarificar esto un poco imáginate que el usuario local que utilizo es jlaso,
entonces cuando me refiero a este archivo en mi máquina: ~/.ssh/id_rsa.pub
lo estoy haciendo a /home/jlaso/.ssh/id_rsa.pub, mientras que si el usuario que
utilizo para conectarme con el servidor se llama gituser el archivo ~/.ssh/autho-
rized_keys en el servidor lo encontraré en /home/gituser/.ssh/authorized_keys.
cd ~/mis_proyectos_php
git clone ruta_del_repo_remoto:/nombre_del_repo carpeta_local_opcional
cd carpeta_local_opcional
2
Me refiero a registrarla en el archivo ~/.ssh/authorized_keys.
77
Programación PHP profesional con Slim, Paris y Twig
cd ~/mis_proyectos_php/proyecto_1
git init
Lo normal es utilizar un repositorio remoto que permita compartir los fuentes con
el grupo de programación. Solo tendremos entonces que declarar un origin en
nuestro repositorio recién creado, mediante el comando git remote add origin
ruta_del_servidor_remoto:/nombre_repo.
Cuando vamos a empezar una nueva funcionalidad crearemos una rama me-
diante la instrucción git branch -b feature/nueva-funcionalidad.
Para que nuestra rama y/o cambios estén accesibles a los demás programa-
dores o solo para que esté a buen recaudo (como copia) subiremos nuestros
cambios al repositorio central. Para ello actualizaremos primero nuestra rama
haciendo git pull origin feature/nueva-funcionalidad, y acto segui-
do actualizaremos el repositorio central con nuestros cambios mediante git
push origin feature/nueva-funcionalidad, a menos que se hayan produ-
cido confl ictos que tengamos que subsanar. Vamos a ver un diagrama de fl ujo
para todo lo que hemos enumerado.
78
7. Git como SCM
remoto (git clone) o bien hemos inicializado desde cero (git init)3 . El resul-
tado de ambos es un repositorio funcional que nos permite ver el ciclo completo
de los cambios que se han de actualizar entre nuestro repositorio remoto y local.
- Para ver en qué estado están los archivos (modificados, borrados, re-
nombrados o cambiados de sitio):
» git status
Hasta aquí para mantener un repositorio local. Para sincronizar los cambios re-
motos y los nuestros haremos git pull remote rama y git push remote
rama, siendo rama el nombre de la rama con la que estemos trabajando y
remote el nombre que tenga el repositorio remoto, habitualmente origin.
Hay muchos servidores en Internet para poder alojar nuestro repositorio: bit-
bucket.org (gratuito para grupos de hasta 5 personas )4, github.com (gratuito
para proyectos open-source), gitlab.com (gratuito) o un servidor propio.
Uso de ramas
Una vez se empieza a trabajar con git se ve necesario el uso de algún tipo de
instrumento que permita ir más allá de la simple incorporación de archivos al
repositorio.
3
En el caso de inicializar un repositorio desde cero con git init si se quieren subir cam-
bios a un repositorio remoto hay que añadir la ruta de este repositorio mediante git
remote add remote-name url
4
Estos datos son a la fecha de publicación de este libro, en ningún caso el autor ga-
rantiza la gratuidad de este servicio ya que es ajeno al mismo.
79
Programación PHP profesional con Slim, Paris y Twig
Ten en cuenta que pull actualiza nuestro repositorio local con los últimos cambios
del remoto y push actualiza el repositorio remoto con los cambios que tengamos
80
7. Git como SCM
nuevos en nuestro local. Es obligatorio siempre hacerlo en este orden. ¿La lógi-
ca de esto? Si se producen confl ictos estos quedan en nuestro repositorio local
hasta que los subsanemos (CONFLICT).
El caso es que ahora van a modificar los dos el mismo archivo en la misma lí-
nea, y con contenidos diferentes. El primer usuario en subir el cambio no tendrá
ningun inconveniente. El segundo cuando haga un pull de la rama en cuestión
será informado por git de que una modificación realizada por él mismo choca con
una modificación realizada por otro usuario en el mismo archivo, por lo que será
requerido por git para modificar él el archivo y validar los cambios. Para que esto
no sea una tarea ardua y penosa, git informa tanto de los archivos en confl icto al
hacer el pull, como indicando con marcas dentro del archivo lo que contiene la
rama remota y lo que tiene nuestra rama local, mediante un marcado especial.
Abrimos una terminal del sistema y nos situamos en una carpeta temporal para
luego poder desechar el contenido de la misma:
> cd ~
> mkdir temp
> cd temp
> git init --bare confl ict-test
> git clone confl ict-test confl ict-test-1
> git clone confl ict-test confl ict-test-2
Hasta aquí hemos creado un repositorio maestro vacio y hemos clonado ese
mismo repositorio dos veces para simular la interacción de dos usuarios dife-
rentes.
81
Programación PHP profesional con Slim, Paris y Twig
$a = 1;
$b = 2;
(No te preocupes por este error, indica solamente que la rama no existe en el
repositorio, recuerda que está completamente vacío).
82
7. Git como SCM
Y subimos ese cambio procediendo igual que antes (simplifico un poco la salida
del terminal):
cd ../confl ict-test-1
Pero esta vez no hacemos el pull, recuerda que estamos simulando dos usua-
rios que trabajan a la vez sobre el mismo archivo.
83
Programación PHP profesional con Slim, Paris y Twig
Ahora se nos ha debido de abrir el editor del sistema con un contenido parecido
a este:
Merge branch 'master' of ~/confl ict-test
Si te fijas, git nos ha informado de que ha hecho la fusión utilizando una estra-
tegia recursiva y que no se ha hecho necesaria la intervención humana.
84
7. Git como SCM
Y como antes add, commit, pull y push, no te presento el terminal pues creo
que estos pasos los tendrás ya claros.
> cd ../conclict-test-1
> nano prueba.php
85
Programación PHP profesional con Slim, Paris y Twig
print "(CAMBIO 4 del OTRO USUARIO) la suma de a y b es " . ($a + $b) . PHP_EOL;
Como puedes apreciar, git no ha sido capaz de mezclar los cambios pues se
refieren a la misma línea. Por tanto nos lo indica en esta salida, diciéndonos
cuál o cuáles son los archivos que tenemos que revisar. En nuestro caso, por
ser sencillo, solo uno pero podrían ser varios. Es importante no perder de vista
esta salida de pantalla para poder corregir todos los confl ictos.
$a = 1;
$b = 2;
<<<<<<< HEAD
print "(CAMBIO 4 del OTRO USUARIO) la suma de a y b es " . ($a + $b) . PHP_EOL;
=======
print "(CAMBIO 3 del USUARIO 1) la suma de a y b es " . ($a + $b) . PHP_EOL;
>>>>>>> 0b47af8013943e524ed669d6b1fdbb0f85c3487c
86
7. Git como SCM
Lo que te decía, git nos ha marcado (lo he subrayado para guiarte) dónde ha en-
contrado los confl ictos para que nosotros mismos decidamos qué se queda en
el archivo definitivo, si nuestro cambio o el que subió el compañero. En muchas
ocasiones, estas decisiones son evidentes y en otras no tan obvias. A menudo nos
tocará hablar con el otro usuario y ver cuáles son los motivos para esa diferencia
y saber así con qué quedarnos. Hasta que no se resuelvan los confl ictos no po-
dremos hacer un push e integrar todos nuestros cambios en el repositorio central.
¿Ves ahora por qué es necesario primero el pull y luego el push? Espero que sí.
Una vez hechos esos cambios grabamos y tendremos que hacer de nuevo todo
el proceso:
Confl icts:
prueba.php
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
# .git/MERGE_HEAD
# and try again.
#
# Please enter the commit message for your changes.
Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch and 'origin/master' have diverged,
# and have 1 and 1 different commit each, respectively.
#
87
Programación PHP profesional con Slim, Paris y Twig
Espero que con esta extensa demostración haya quedado explicado bien la
resolución de confl ictos de git.
Buenas prácticas
Hay una serie de convenciones que es recomendable seguir.
88
7. Git como SCM
Para cada una de las nuevas funcionalidades que tengamos que implementar
crearemos una rama que partirá de develop y terminará en develop, estas ramas
vivirán únicamente durante el proceso de desarrollo de dicha funcionalidad, bo-
rrándose inmediatamente una vez se haya integrado la rama con la de develop.
Las ramas release sirven para unir la rama develop con la master en aquellos
momentos en los que se decida integrar una serie de funcionalidades en pro-
ducción; estas releases se etiquetarán correspondientemente (ver git tag).
89
Programación PHP profesional con Slim, Paris y Twig
Funcionalidad Comando
git clone url [carpeta]
Clonar un repositorio en una
carpeta local si no se indica carpeta se creará con un
nombre igual al del proyecto a clonar.
Listar las ramas que hay en el reposi- git branch
torio local y saber cuál
es la activa. aparece marcada con asterisco (*) la activa.
90
7. Git como SCM
Funcionalidad Comando
Juntar los contenidos de la rama x con
la rama activa haciendo un merge no git merge --no-ff rama-x
fast-forward (recomendado)
Actualizar la rama activa del reposi-
torio local con los datos de la rama
«rama-remota» del repositorio remo- git pull origin rama-remota
to. Internamente se comporta como
un merge.
Rizando el rizo
A continuación te voy a exponer un caso práctico para que veas cómo puede
ser útil alguna de las funciones avanzadas que hemos visto en la tabla anterior.
91
Programación PHP profesional con Slim, Paris y Twig
Conclusión
Como en los demás capítulos de este modesto libro, no pretendo en este abar-
car todo el complejo sistema de git.
6
http://git-scm.com
92
Parte III: MVC a fondo
8 Controller: Slim
Contenido
» Introducción
» Cómo implementar una ruta simple
» Personalizar errores en Slim
» Ganchos (Hooks)
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
Hasta ahora hemos visto teoría y ejemplos, y aunque estoy seguro de que lo
has entendido todo muy bien, en el fondo aún te preguntas qué es eso de mo-
delo, vista, controlador, etc.
Pues bien, aunque como te digo hemos ido viendo muchos ejemplos de código
no hemos visto nada realmente aprovechable directamente para la aplicación
que pretendemos desarrollar. En este momento, después de haber tenido una
idea somera de cada uno de los componentes vamos a empezar por fin a ver el
código de nuestra aplicación de ejemplo. Recuerda que lo que pretendemos ha-
cer es, usando el patrón MVC, una aplicación simple que presente unas cuantas
páginas al usuario, una para la home, una de contacto, en fin, lo básico.
De acuerdo, entonces, ¿qué es lo que pasa en realidad cuando llega una peti-
ción desde el navegador del usuario a nuestra aplicación web?
Para que todo funcione correctamente necesitamos que nuestro bootstrap ini-
cie todos los servicios necesarios, establezca cuáles son los patrones de las
URL y ceda el contol a Slim, que verá qué función en concreto es capaz de
atender la URL que está solicitando el usuario en esa petición, le cederá el
control a esa función y, si todo ha ido bien, a la vuelta (cuando termine el con-
trolador encargado) devolverá la respuesta al usuario.
96
8. Controller: Slim
date_default_timezone_set('Europe/Madrid');1
Twig_Autoloader::register();
// DB access
require_once ROOT_DIR . '/app/config/dbconfig.php';
ORM::configure('mysql:host='.DBHOST.';dbname='.DBNAME);
ORM::configure('username', DBUSER);
ORM::configure('password', DBPASS);
// Prepare view
\lib\TwigViewSlim::$twigOptions = array(
'charset' => 'utf-8',
'cache' => ROOT_DIR . '/app/cache',
'auto_reload' => true,
'strict_variables' => false,
'autoescape' => true
);
1
Es una buena práctica poner esta configuración en el PHP.INI si tienes acceso a él.
Desde la versión 5.3 de PHP es obligatorio indicar la zona horaria donde se ejecutan
los scripts de PHP, so pena de recibir una advertencia.
97
Programación PHP profesional con Slim, Paris y Twig
// Prepare app
$app = new \Router\SlimExt(array(
'templates.path' => ROOT_DIR . '/app/templates',
'log.level' => 4,
'log.enabled' => true,
'log.writer' => new \Slim\Extras\Log\DateTimeFileWriter(array(
'path' => ROOT_DIR . '/app/logs',
'name_format' => 'y-m-d'
)),
'view' => new \lib\TwigViewSlim(),
)
);
$languages = app\config\Config::getInstance()->getLanguageCodes();
\Slim\Route::setDefaultConditions(array(
'lang' => implode('|', $languages)
));
//@1:
// Define routes for controller
require_once ROOT_DIR . '/app/controller/autoload.php';
// Run app
$app->run();
Todas las peticiones que haga el usuario a nuestra aplicación2 van a pasar por
aquí, este inicializador se encarga de recoger la petición y proceder en conse-
cuencia.
NOTA
Este comportamiento ocurre gracias al archivo .htaccess que te recomien-
do revisar. Todas las aplicaciones web que hago exponen solo la carpeta
web al público y tienen un .htaccess que hace que todas las peticiones
pasen a través de un solo archivo. También se puede configurar en el vir-
tualHost del servidor, pero requiere acceso a la máquina y conocimientos
más avanzados.
#web/.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
2
Excepción hecha de archivos concretos, como imágenes, css, js, etc.
98
8. Controller: Slim
@1:
Justo donde tenemos esta línea es donde se produce la lectura de las rutas:
// Define routes for controller
require_once __DIR__ . '/../app/controller/autoload.php';
// frontend routes
require_once __DIR__."/frontend/home.php";
require_once __DIR__."/frontend/articles.php";
require_once __DIR__."/frontend/contact.php";
require_once __DIR__.'/frontend/login.php';
require_once __DIR__.'/frontend/entity.php';
// backend routes
require_once __DIR__."/backend/admin.php";
require_once __DIR__."/backend/CRUD/list-entity.php";
require_once __DIR__."/backend/CRUD/edit-entity.php";
require_once __DIR__."/backend/CRUD/new-entity.php";
require_once __DIR__."/backend/crud.php";
<?php
/**
* ruta de la home (index)
*/
$app->get('/(:lang(/))', function ($lang = 'es') use ($app) {
$app->render('frontend/home/index.html.twig');
})->name('home.index');
99
Programación PHP profesional con Slim, Paris y Twig
NOTA
Recuerda que la disquisición entre ruta lógica y ruta física ya la hicimos
en la sección “Ruta lógica versus ruta física” en la página 20.
A primera vista estamos definiendo la ruta de la home. Por ser esta la primera
vamos a detenernos en cada parte de la definición.
$app es una variable (global no sería la palabra correcta, pues recuerda que
seguimos estando en el contexto de ejecución principal, es decir, no estamos
dentro de ninguna clase ni dentro de ninguna función) que representa la instan-
cia de Slim que se creó en el bootstrap.
Como instancia de Slim tiene unos determinados métodos, algunos de los cua-
les sirven para la definición de las rutas: get, post, delete, put, map,
via y name.
Todos ellos (menos name y via) aceptan un parámetro de cadena que repre-
senta el patrón de la ruta que pretendemos atender y la función (normalmente
una closure) que va a atender la petición, si la ruta concuerda.
El objeto que devuelve (una ruta) permite asignarle un nombre lógico mediante
name. Vamos a plantearlo así, reescribiendo el código, porque te veo un poco
perdido ;<)
<?php
/**
* ruta de la home (index)
*/
function home_route_controller($lang = 'es')
{
$app = \Router\SlimExt::getInstance();
$app->render('frontend/home/index.html.twig');
}
$home_route = $app->get('/(:lang(/))', 'home_route_controller');
$home_route->name('home.index');
Mejor, ¿verdad?
Bueno, pero estarás de acuerdo conmigo en que la otra escritura es más com-
pacta, además de que hace uso de las funciones anónimas (closures) y per-
mite la definición en línea. En todo caso, me he detenido en esta porque es la
primera, pero a lo largo de todo el texto veremos las rutas en forma compacta.
100
8. Controller: Slim
NOTA
Hay un capítulo que aborda el tema de i18n (internacionalización) con detalle.
101
Programación PHP profesional con Slim, Paris y Twig
Por simplicidad, y sobre todo para que cada vez que se tenga que añadir un
nuevo idioma no haya que rehacer la lista de rutas, se puede confeccionar el
patrón indicado antes, pero quiero que tengas claro que son equivalentes.
Con esto queda visto el modelo de ruta simple que introduje al principio de este
punto. Recuerda que más adelante retomaremos los idiomas.
// Prepare app
$app = new \Slim\Slim();
/**
* Not found 404 handler method
*/
$app->notFound(function(Exception $e = NULL ) use($app){
print 'página no encontrada'; exit;
});
$app->get('/', function() use ($app){
print('hola'); exit;
}
);
// Run app
$app->run();
3
https://github.com/jlaso/my-simple-web
102
8. Controller: Slim
103
Programación PHP profesional con Slim, Paris y Twig
error_reporting(E_ALL); // @1
// Prepare app
$app = new \Slim\Slim();
$app->config('debug', false); // @2
/**
* error 500 handler method
*/
$app->error(
function (\Exception $e) use ($app)
{
$html = '<html>';
$html .= '<body>';
$html .= '<ul><li>Error <strong>' . $e->getMessage() . '</strong></li>';
$html .= '<li>en la linea ' . $e->getLine() . '</li>';
$html .= '<li>del archivo ' . $e->getFile() . '</li>';
$html .= '</ul></body>';
$html .= '</html>';
print $html;
exit;
}
);
104
8. Controller: Slim
// Run app
$app->run();
@1:
Si queremos filtrar los códigos de error que queremos mostrar al usuario utili-
zaremos la función error_reporting de PHP, ya que Slim respeta su contenido a
la hora de lanzar la excepción personalizada.
@2:
Es obligatorio poner a Slim en modo no debug para que el tratamiento persona-
lizado de los errores funcione, en otro caso Slim siempre mostrará su pantalla
completa de errores, que es esta:
105
Programación PHP profesional con Slim, Paris y Twig
Ganchos (Hooks)
Vamos a abundar un poco en los entresijos de Slim y veremos cómo podemos
hacer cosas más complejas.
Vamos por partes: los ganchos que define Slim son estos y son llamados en
este orden:
» slim.before
» slim.before.router
» slim.before.dispatch
» slim.after.dispatch
» slim.after.router
» slim.after
Slim los invoca de forma automática en cada fase del ciclo. Si tienes curiosidad
echa un vistazo al archivo vendor/slim/slim/Slim/Slim.php. La función call es
la que se encarga de llamar a los ganchos.
106
8. Controller: Slim
Hemos previsto que cada uno de los banners tenga un código que nos permita
identificarlo, así la URL de aterrizaje para cada uno de ellos tendrá este aspecto:
http://mysimpleweb.com.es/registro?code=1
Pero al mismo tiempo queremos permitir que la variable code pueda ir en todas
las URL, pero que se materialice en la de registro, es decir, podríamos tener esto:
<?php
// ...
// Run app
$app->run();
Esta simple línea nos controla el valor de la variable code entremos por donde
entremos. Pero convendrás conmigo en que, aunque es una solución óptima y
muy concreta, está tan acoplada al código que difícilmente podremos variarla
sin modificar la línea que la contiene. Para este caso tan ingenuo quizás te
parezca absurdo hacerlo de otro modo, pero cuando adquieras el hábito de
hacerlo bien no entenderás por qué se podía hacer de la manera anterior.
Vamos a crear una función que nos realizará lo anterior. Ten en cuenta que si
por ejemplo la primera vez llamamos a la URL con la variable code establecida
pero las siguientes veces no, la variable de sesión se nos borrará y no hará lo
que pretendíamos en un principio. Si por el contrario queremos filtrar los regis-
tros desde determinada IP, para que no se tenga en cuenta este parámetro si
se viene desde la IP fija de nuestra oficina, tendremos que alterar y agregar
más código a lo que en principio era una línea clara y simple.
107
Programación PHP profesional con Slim, Paris y Twig
Cualquier alteración por nimia que nos parezca enturbiará el bootstrap, y ade-
más nos planteará un momento de ejecución que a lo mejor no es el correcto.
De la manera en que lo hemos hecho en el ejemplo siempre va a suceder antes
de que Slim tome el control ( $app->run(); ).
<?php
// ...
$app->hook('slim.before.dispatch', function() use($app){
if(isset($_GET['code'])){
$_SESSION['code'] = $_GET['code'];
}
}
);
// Run app
$app->run();
En todo caso conviene poner los hooks en un archivo separado para más cla-
ridad y mantener el bootstrap lo más limpio posible.
Middlewares
Hablando de control de acceso, hay otro mecanismo más general que suele
ser utilizado para el control de acceso a zonas restringidas de la aplicación. Las
middleware o funciones intermedias se ejecutan justo antes de la función que
atiende la ruta. El valor de devolución de la middleware motiva la ejecución o
no de la que atiende la ruta.
<?php
// ...
function redirectIfNotLogged()
{
$app = Slim::getInstance();
if (!isLogged()) {
$app->redirect($app->urlFor('.login'));
}
}
108
8. Controller: Slim
Lo normal es que las middleware se reutilicen en varias rutas, por ello no sue-
len ser closures, sino funciones globales, y se usan mediante la cadena de
texto que representa su nombre.
$app->get('/ruta/', 'middleware1', 'middleware2', ... ,'funcion_de_la_ruta');
Rizando el rizo
Te animo a escoger cualquier tarea que tu aplicación web necesite hacer de
manera repetitiva y reiterada y, además, en los mismos puntos para que estas
tareas sean realizadas por ganchos. A modo de ejemplo te dejo la siguiente lista:
» Envío de correo electrónico de confirmación a un usuario al registrarse.
» Subir un comentario a Facebook cuando el usuario publique en nuestro
sitio.
» Actualizar la puntuación del usuario cuando este haga alguna acción
premiada.
» Hacer procesamiento de imágenes en el servidor cuando el usuario
suba una imagen (hacerla más pequeña, normalizarla, generar una mi-
niatura, etc.).
Algunos de estos ejemplos, conforme vaya recibiendo feedback por vuestra
parte y en la medida en que mi tiempo me lo permita, irán siendo publicados
en mi blog4.
Conclusión
Sin duda, la pieza más importante de nuestro invento, el controlador permite
poner todas las piezas en juego y hacer que una petición de un usuario pueda
recorrer todo el ciclo de manera oportuna, recabar los datos necesarios de la
bbdd, persistirlos si fuera preciso y devolver una respuesta adecuada al usuario.
4
Te recuerdo que publico con cierta frecuencia artículos de divulgación sobre progra-
mación en http://www.jaitec.net
109
Programación PHP profesional con Slim, Paris y Twig
110
9 Vamos con
el modelo
Contenido
» Introducción
» Configurar la instancia
» Recordando un poco
» Una consulta sencilla
» Una consulta completa
» Usando tablas de bases de datos diferentes
» Extender IdiOrm&Paris
» Diagrama de tablas de My-simple-web
» SluggableInterface
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
Ya vimos en la primera parte una ligera aproximación al modelo, si hay algo
que necesites recordar es un buen momento para darle un repaso a ese capí-
tulo. En todo caso, en este vamos a acometer el tema de manera muy práctica,
por lo que vamos a entrar en materia enseguida.
Pretendo que veas en primer lugar cómo está organizada nuestra aplicación de
ejemplo y, sobre todo, que la puedas utilizar como modelo en los desarrollos
que hagas.
Configurar la instancia
ORM::configure('mysql:host=localhost;dbname=my-simple-web');
ORM::configure('username', 'user');
ORM::configure('password', 'password');
Estas tres instrucciones son suficientes para configurar el ORM, a partir de aquí
se puede utilizar mediante las órdenes correspondientes.
Con el primero obtenemos un array con las consultas efectuadas y con el segun-
do solo la última consulta. Esta la puedes utilizar en el caso de querer sustituir al
gestor de excepciones por defecto de PHP, y de esta manera mostrar la última
consulta efectuada, posiblemente la que motivó el fallo que lanza la excepción.
Recordando un poco
A modo de breve recordatorio has de crear una clase que extienda de Model, la
cual en principio no es necesario que tenga propiedades ni métodos. Esta sim-
ple herencia permite al ORM instanciar un objeto de esa clase en las diferentes
llamadas que efectúes.
112
9. Vamos con el modelo
Verás más adelante que vamos a extender la clase Model que nos proporciona el
ORM con una serie de mejoras, aprovechando la herencia que proporciona PHP.
y solo uno concreto por medio de su ID (en el ejemplo el cliente 12) de esta
manera:
$cliente = Model::factory('Cliente')->find_one(12);
Un where normal
$usuario = Model::factory('User')
->where('email', 'jlaso@joseluislaso.es')
->find_one();
Con una condición más compleja, sumando condiciones (and):
$usuario = Model::factory('User')
->where('email','jlaso@joseluislaso.es')
->where_gte('role_id', 500)
->find_one();
Cuando queremos hacer un where con OR no podemos hacerlo directamente
mediante el método where pues las condiciones se agregan (AND) y tenemos
que utilizar un método alternativo que nos proporciona el ORM: where_raw.
$students = Model::factory(‘Student’)
->where_raw(‘name = ? OR name = ?’, array(‘JOSELUIS’, ‘ANGEL’))
->find_many();
113
Programación PHP profesional con Slim, Paris y Twig
Un join
Volvamos al ejemplo sobre alumnos y cursos, vamos a obtener un listado de
los alumnos que están inscritos en el curso 1:
$allStudents = Model::factory('Grade')
->table_alias('g')
->join('Student', array('s.grade_id', '=', 'g.id'), 's')
->where('g.id', 1)
->find_many();
// ...
1
Si el nombre del método no se puede asociar con lo que hace en realidad la interpre-
tación de la consulta donde está implicado ese filtro se hace farragosa.
114
9. Vamos con el modelo
$allStudentsOfFirstGrade = Model::factory('Student')
->filter('firstgrade')
->find_many();
Rizando el rizo
Cuando veamos el apartado de extender el modelo veremos cómo hacer con-
sultas más complejas, utilizando las ventajas del ORM.
TRUCO
Se pueden indicar expresiones de MySQL directamente como valor de
los campos, mediante set_expr:
$usuario->set_expr('last_logged_in', 'NOW()');
115
Programación PHP profesional con Slim, Paris y Twig
Extender IdiOrm&Paris
Lo cierto y verdad es que se me hace muy pesado escribir cada vez la ins-
trucción Model::factory('Entidad') ya que el IDE que utilizo (PhpStorm
para los interesados) me ayuda con el autocompletado y ‘Entidad’ es un pará-
metro literal que no se autocompleta. Me resultaría más fácil escribir algo así:
Entidad::factory(). Esta traducción es muy sencilla.
Además, el hecho de tener nuestra propia clase nos permitirá agregar al objeto
Model métodos extra, como pueden ser los relacionados con la validación e
hidratación que luego veremos en acción.
Por ello vamos a extender Model con nuestra propia clase y la vamos a dotar
de algunas mejoras.
<?php
namespace app\models\core;
use \Model;
// ...
abstract class BaseModel extends Model implements ...
{
public static function factory(...)
{
// ...
$class = get_called_class();
return parent::factory($class);
}
// ...
}
116
9. Vamos con el modelo
<?php
namespace app\models\core;
/**
* Permite que una entidad pueda tomar directamente los valores de un
* array asociativo, por ejemplo el request e hidratarse con esos valores,
* para hacer un bind de un formulario
*/
interface BindableInterface
{
/**
* Efectúa el bind de los parámetros pasados
* @param array $array
* @return mixed
*/
namespace app\models\core;
use \Model;
use app\models\core\BindableInterface;
117
Programación PHP profesional con Slim, Paris y Twig
Esto plantea algunos problemas en caso de tener campos que no estén den-
tro del modelo, como el campo de comprobación de «soy humano» en el
formulario de contacto.
La mayor parte de la complejidad que ahora obviamos la vamos a ver en este
mismo capítulo, aunque más adelante he preparado uno exclusivamente con
cuestiones relacionadas con los formularios y las validaciones.
En el capítulo dedicado a los formularios precisamente pongo el ejemplo del
formulario de contacto.
118
9. Vamos con el modelo
Vamos a ir viendo una por una esas clases. Las puedes encontrar dentro del
código en la carpeta app/models.
BaseModel
Ya hemos estado viendo alguna de las mejoras que le hemos hecho a la clase
Model que nos aporta el ORM, y que las hemos puesto dentro de un descen-
diente directo: BaseModel, vamos a ver cómo queda la clase de manera inte-
gral para que te hagas una idea completa.
119
Programación PHP profesional con Slim, Paris y Twig
<?php
namespace app\models\core;
use \Model;
use app\models\core\BindableInterface;
use app\models\core\Form\FormListTypeInterface;
/**
* Extends Model from ORM
*
* The ORM uses magic __get and __set to map properties to table fields, for that
* I have named my other methods with underscore to permit use that names for fields
*
*/
abstract class BaseModel
extends Model
implements BindableInterface
{
/**
* Factory from extended class
*
* that permits this
* Entity::factory()->...
* or
* BaseModel::factory('Entity')->...
*
* @param string $class
*
* @return \ORMWrapper
*
*/
public static function factory($class="")
{
if (!$class) {
$class = get_called_class();
}
return parent::factory($class);
}
/**
* @param string $class
*
* @return BaseModel
*/
public static function create($class="")
{
if (!$class) {
$class = get_called_class();
}
120
9. Vamos con el modelo
return parent::factory($class)->create();
}
/**
* Bind entity data fields, post form for example
*
* @param array $array
*
* @internal param $assoc -array $array
* @return mixed|void
*/
public function bind(array $array)
{
$ent = get_called_class();
$frmLstClass = "\\{$ent}FormType";
if (class_exists($frmLstClass)) {
/** @var FormListTypeInterface $formList */
$formList = new $frmLstClass;
foreach ($formList->getForm() as $formItem) {
$field = $formItem['field'];
$type = strtolower($formItem['type']);
$ro = isset($formItem['widget']['readonly'])
&& $formItem['widget']['readonly'];
// only bind not readonly fields
if (!$ro && in_array($type,array(
'text',
'textarea',
'number',
'hidden',
))) {
$value = isset($array[$field]) ? $array[$field] : null;
if ( null !== $value ) {
$this->set($field,$value);
}
}
}
} else {
foreach ($array as $key=>$value) {
$this->set($key,$value);
}
}
}
/**
* get default create options
*
* @return array
*/
121
Programación PHP profesional con Slim, Paris y Twig
/**
* Get the table name that corresponds to class name
* in the actual namespace system
*
* @param string $class
* @return string
*/
public static function _tableNameForClass($class)
{
// CamelCase to undescore_case
$class = strtolower(preg_replace('/([a-zA-Z])(?=[A-Z])/', '$1_', $class));
// table name equals to PSR0 of class name
return str_replace("\\","_",strtolower($class));
}
/**
* Get the pretty name of the model
*/
public static function _entityName()
{
$class = \lib\MyFunctions::camelCaseToUnderscored(get_called_class());
$array = explode("\\",$class);
array_shift($array);
return implode("",$array);
}
/**
* Get relations
*
* @return array
*/
public function _relations()
{
return array();
}
/**
* Get name of entity in singular
*
* @return string
*/
122
9. Vamos con el modelo
/**
* Get name of entity in plural
*
* @return string
*/
public static function _namePlural()
{
return _('BaseModels');
}
/**
* Get name of entity
*
* @return string
*/
public static function _nameEntity()
{
$str = \lib\MyFunctions::camelCaseToUnderscored(get_called_class());
return str_replace('\\','_',$str);
}
/**
* Return message "$field can’t leave blank", translated
*
* @param string $field
*
* @return string
*/
public function _cantLeaveBlank($field)
{
return sprintf(_('%s can\'t leave blank'),$field);
}
Como puedes ver, hemos hecho algo más que extender la clase básica Model
que nos proporciona nuestro ORM. Lo más destacable es que la obligamos a
cumplir con la interfaz bindable, de tal manera que cualquier descendiente po-
drá hacer directamente un $entity->bind($request).
Lo explico al principio del código fuente (en inglés2): hago uso del subrayado en
todos los métodos para dejar el espacio de nombre intacto para los nombres de
2
Es una buena práctica declarar variables y métodos en inglés ya que es un idioma más
compacto que el nuestro y además nos hacemos más universales de cara a extender
nuestro código. Normalmente también suelo escribir los comentarios en inglés.
123
Programación PHP profesional con Slim, Paris y Twig
SluggableInterface
En ocasiones tendremos que validar un campo que represente un slug (parte
de la url que define el item en concreto).
Estos valores deben ser únicos dentro de la tabla concreta y además no pue-
den contener espacios ni símbolos diferentes de letras, números y el guion.
Por ese motivo he creado una interfaz que integre ese comportamiento:
124
9. Vamos con el modelo
<?php
namespace app\models\core;
interface SluggableInterface
{
Es una interfaz muy sencilla pues solo obliga a implementar un método con-
creto, que deberá devolver un valor positivo en el caso de que el slug pasado
exista en la bbdd para la tabla concreta. Si el ID pasado como argumento es
diferente de cero es para no tener en cuenta el registro representado por ese
ID, de esa manera podremos validar ese slug sin considerar su propio registro.
De lo contrario, siempre daría positivo para un slug preexistente.
Ahora vamos a ver todas las clases que descienden de BaseModel, que son
las que implementan de alguna manera nuestra estructura de la base de datos
(salvo alguna excepción, que veremos).
Entity_entity
Vamos a empezar viendo la entidad más sencilla, su función dentro de la apli-
cación no es otra que permitir automatizar tareas de gestión en la parte trasera
(backend). Su implementación es la más sencilla de todas las que vamos a ver ya
que incluye solo el método que permite su creación dentro de la base de datos.
Fíjate que otros ORM, como Doctrine, pueden extraer los metadatos de las
propiedades declaradas en la clase que representa la entidad, pero en nuestro
125
Programación PHP profesional con Slim, Paris y Twig
<?php
namespace Entity;
use app\models\core\BaseModel;
/**
* Get the SQL creation sentece of this table
*
* @param array $options
* @return string
*/
public static function _creationSchema(Array $options = array())
{
$class = self::_tableNameForClass (get_called_class());
// default options
$options = array_merge(self::_defaultCreateOptions(),$options);
return
<<<EOD
EOD;
126
9. Vamos con el modelo
Entity_staticpage
<?php
namespace Entity;
use app\models\core\BaseModel;
use app\models\core\SluggableInterface;
use app\models\core\ValidableInterface;
use lib\SlimFunctions;
class Staticpage
extends BaseModel
implements SluggableInterface,ValidableInterface
{
public static function checkSlug($slug, $id = 0)
{
$count = self::factory()
->where('slug',$slug)
->where_not_equal('id',$id)
127
Programación PHP profesional con Slim, Paris y Twig
->count();
$sql = \ORM::get_last_query();
return $result;
}
/**
* Get the SQL creation sentece of this table
*
* @param array $options
* @return string
*/
public static function _creationSchema(Array $options = array())
{
$class = self::_tableNameForClass(get_called_class());
// default options
$options = array_merge(self::_defaultCreateOptions(),$options);
return
<<<EOD
CREATE TABLE IF NOT EXISTS `{$class}` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`slug` varchar(100) NOT NULL,
`title` varchar(100) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE={$options['engine']} DEFAULT CHARSET=ons['charset']} AUTO_INCREMENT=1;
EOD;
}
}
128
9. Vamos con el modelo
En esta ocasión hemos introducido dos métodos nuevos, uno viene dado por
el cumplimiento de la interfaz ValidableInterface: validate, y el otro por
SuggableInterface: checkSlug.
...
if ($request->isPost()) {
$item->bind($request->post());
if ($item instanceof ValidableInterface) {
$errors = $item->validate();
}
if (!count($errors)) {
$item->save();
$app->redirect($app->urlFor('admin.list-entity',array(
'entity'=>$entity
)));
}
}
...
Entity_article
Una de las entidades que permite mostrar un uso más extensivo del ORM,
dentro de My-simple-web es la de artículos.
129
Programación PHP profesional con Slim, Paris y Twig
namespace Entity;
use app\models\core\BaseModel;
use app\models\core\SluggableInterface;
use app\models\core\ValidableInterface;
use lib\MyFunctions;
/**
* Class that stores articles of this web
*/
class Article
extends BaseModel
implements SluggableInterface, ValidableInterface
130
9. Vamos con el modelo
/**
* Checks that slug not exists
*
* @param string $slug
* @param int $id
* @return bool
*/
public static function checkSlug($slug, $id = 0)
{
$count = self::factory()
->where('slug',$slug)
->where_not_equal('id',$id)
->count();
return $result;
}
/**
* Get the SQL creation sentece of this table
*
* @param array $options
131
Programación PHP profesional con Slim, Paris y Twig
* @return string
*/
public static function _creationSchema(Array $options = array())
{
$class = self::_tableNameForClass(get_called_class());
// default options
$options = array_merge(self::_defaultCreateOptions(),$options);
return
<<<EOD
EOD;
/**
* Get descriptions throught intermediary table
*
* @return \ORM
*/
public function getDescriptions() {
$sql = '`id` IN (
SELECT `description_id`
FROM `entity_article_description`
WHERE `article_id` = ?
)';
$descriptions = Description::factory()
->where_raw($sql,$this->id)
->find_many();
return $descriptions;
}
/**
* Get relations with other entities
*
* @return array
*/
132
9. Vamos con el modelo
namespace Entity;
use app\models\core\BaseModel;
use app\models\core\ValidableInterface;
use lib\MyFunctions;
/**
* Class that stores articles of this web
*/
class ArticleDescription extends BaseModel
{
/**
* Get the SQL creation sentece of this table
*
* @param array $options
* @return string
*/
public static function _creationSchema(Array $options = array())
{
$class = self::_tableNameForClass(get_called_class());
// default options
$options = array_merge(self::_defaultCreateOptions(),$options);
return
<<<EOD
133
Programación PHP profesional con Slim, Paris y Twig
EOD;
Entity_contact
<?php
namespace Entity;
use app\models\core\BaseModel;
use app\models\core\ValidableInterface;
use lib\MyFunctions;
class Contact
extends BaseModel
implements ValidableInterface
{
134
9. Vamos con el modelo
if (empty($this->email)) {
$result['email'] = $this->_cantLeaveBlank(_('Email'));
}
if (empty($this->phone)) {
$result['phone'] = $this->_cantLeaveBlank(_('Phone'));
}
if (empty($this->message)) {
$result['message'] = $this->_cantLeaveBlank(_('Message'));
}
return $result;
}
Security_user
Como no podía ser de otra manera, tenemos también implementada toda la
parte del usuario que va a acceder a nuestra aplicación, ya sea en la parte
frontal, intranet o backend. Para no interferir con el espacio de nombres más
genérico de entity que estábamos usando hasta ahora, y sobre todo para que
veas que podemos separar en carpetas/conceptos nuestras entidades relacio-
nadas con el modelo de datos.
135
Programación PHP profesional con Slim, Paris y Twig
Todo lo que tiene que ver con el usuario lo he puesto en una carpeta llamada
Security. Es importante destacar que el nombre de la carpeta debe empezar
por mayúscula, ya que cuando regeneramos la base de datos, se tiene en
cuenta este punto para «meterse» en esa carpeta y buscar las clases sus-
ceptibles de generar tablas (que son aquellas que tienen declarado el método
_creationSchema).
Otra vez más la cuestión de los nombres es al mismo tiempo un mero forma-
lismo y un compromiso por expresar en una sencilla palabra todo aquello que
conlleva la clase o clases implicadas en ello.
Veamos su código:
<?php
namespace Security;
use app\models\core\BaseModel;
/**
* Get the SQL creation sentece of this table
*
* @param array $options
* @return string
*/
public static function _creationSchema(Array $options = array())
{
136
9. Vamos con el modelo
$class = self::_tableNameForClass(get_called_class());
// default options
$options = array_merge(self::_defaultCreateOptions(),$options);
return
<<<EOD
EOD;
/**
* Get the roles have the user
*
* @return array
*/
$sql = sprintf("SELECT *
FROM `security_role`
WHERE `id` IN
(SELECT `id`
FROM `security_role_user`
WHERE `user_id` = '%d' )"
,$this->id);
$roles = RoleUser::factory()->raw_query($sql);
$this->roles = array();
foreach ($roles as $role) {
$this->roles[] = $role->name;
}
return $this->roles;
}
137
Programación PHP profesional con Slim, Paris y Twig
/**
* Test if user can ... $rol
*
* @param string $rol
*
* @return bool
*/
public function can($rol)
{
// first, get user's roles
$roles = $this->getRoles();
// and test if can ... $rol
return Role::can($roles,$rol);
}
/**
* Goody for find_one
*
* @param int $id
*
* @return \ORM
*/
public static function getUser($id)
{
return User::factory()->find_one($id);
}
<?php
namespace app\models\core;
use app\models\core\Registry;
3
Para mí un helper es un método que permite sistematizar una tarea repetitiva.
138
9. Vamos con el modelo
interface FixturableInterface
{
/**
* Generate fixtures
*
* @param Registry $fixturesRegistry
*
* @return void
*/
public function generateFixtures(Registry $fixturesRegistry);
/**
* Get the order of fixture generation
*
* @return int
*/
public static function getOrder();
<?php
namespace Entity;
use \app\models\core\FixturableInterface;
use Entity\Staticpage;
use \app\models\core\Registry;
139
Programación PHP profesional con Slim, Paris y Twig
return $this;
}
/**
* Generate fixtures
*
* @param \app\models\core\Registry $fixturesRegistry
*
* @return void
*/
public function generateFixtures(Registry $fixturesRegistry)
{
$this->addNewItem(
array(
'slug' => 'about.en',
'content' => 'About us !',
'title' => 'About us',
)
)
->addNewItem(
array(
'slug' => 'about.es',
'content' => '¡ Acerca de nosotros !',
'title' => 'Esto es lo que contamos sobre nosotros.',
)
)
->addNewItem(
array(
'slug' => 'privacy-policy.es',
'content' => 'Esta es nuestra política de privacidad',
'title' => 'Política de privacidad'
)
)
->addNewItem(
array(
'slug' => 'privacy-policy.en',
'content' => 'This is our privacy policy',
'title' => 'Privacy policy'
)
)
;
}
140
9. Vamos con el modelo
/**
* Get the order of fixture generation
*
* @return int
*/
public static function getOrder()
{
return 10;
}
}
Creo que el código se entiende perfectamente: tenemos dos métodos obliga-
torios para cumplir con la interfaz. Uno es el orden en el que hay que ejecutar
esta fixture, ya que puede que haya entidades que dependan de otras y nece-
sitaremos haber creado primero las independientes. El otro es el que se va a
invocar para crear los registros de prueba.
Para regenerar tanto la base de datos como los registros de prueba he creado
un pequeño script que tienes en la carpeta principal del proyecto.
Ten en cuenta que su utilización borra la base de datos sin preguntar, por lo
que en una instalación real de producción deberías borrarlo una vez reiniciada
la bbdd.
<?php
date_default_timezone_set('Europe/Madrid');
error_reporting(E_ALL);
print PHP_EOL.PHP_EOL.__DIR__.PHP_EOL;
$loader = require 'vendor/autoload.php';
Twig_Autoloader::register();
$em = new \app\models\EntityManager(true);
$em->dropDatabase();
$em->createDatabase();
$em->createTables();
$em->generateFixtures();
die('ok'.PHP_EOL.PHP_EOL);
Conclusión
Este capítulo ha sido denso en código, no podía dejar la oportunidad de en-
señarte de cerca lo que puedes implementar con el ORM. En la mayoría de
los casos has visto que nos vamos a encontrar con una clase que extiende de
141
Programación PHP profesional con Slim, Paris y Twig
4
{Entity} se substituye por el nombre de la entidad en concreto, para Article quedaría
ArticleFormType.
142
10 Añadiendo
Twig
Contenido
» Introducción
» Parte de administración de datos
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
Después del capítulo anterior tan prolijo en texto y código vamos a continuar
integrando Twig en nuestra aplicación. Ya hemos hecho un uso muy básico en
algunas rutas que hemos visto en los capítulos anteriores, pero ahora vamos a
centrarnos sobre todo en la inclusión de datos procedentes de la bbdd que el
controlador ha conseguido en la parte que a él le corresponde.
`content` text,
$static = Entity\Staticpage::factory()
->where('slug',$slug.'.'.$lang)
->find_one();
144
10. Añadiendo Twig
})->name('home.staticpage');
Este controlador que acabamos de ver atiende las peticiones de rutas estáti-
cas, como /es/about, /en/who-we-are, etc. Sería fácil declarar las rutas en el
idioma correspondiente, solo habría que insertar los registros correspondientes
a sobre-nosotros.es para /es/sobre-nosotros, no lo he hecho así porque las
demás rutas que no son estáticas no están traducidas y puede resultar un poco
chocante que las rutas cambien su estructura. En principio no te has de preo-
cupar por eso porque webs muy conocidas utilizan la técnica de anteponer el
idioma, una barra (/) y luego el slug de la ruta en inglés. En todo caso se pue-
den traducir todas las URL si es tu deseo. Te lo dejo como ejercicio.
@1:
Esta argucia permite, tras comprobar que no existe ese slug en la bbdd, pasarle
de nuevo el control a Slim para que siga procesando la ruta actual con otras
funciones que sean capaces de interpretarla (recuerda el tema de atención
duplicada al principio del libro).
Esta ruta nos viene muy bien como ejemplo porque es muy sencilla, suficiente
para ilustrar cómo le llegan al motor de plantillas las variables.
@2:
Fijémonos en la línea:
$app->render('frontend/home/staticpage.html.twig',array(
'static' => $static,
)
);
{% block subcontent %}
145
Programación PHP profesional con Slim, Paris y Twig
<div class="clear"></div>
<div class="grid_2"><p> </p></div>
<div class="grid_8" id=”subcontent”>
</div>
<div class="grid_2"><p> </p></div>
<div class="h2 grid_12"> </div>
{% endblock %}
Pero por lo general es mejor tener las variables con el mismo nombre en el con-
troller y en la plantilla, a la larga te evitarás quebraderos de cabeza y el código
es más fácil de seguir.
146
10. Añadiendo Twig
más compleja que mande más datos a la plantilla y que esta tenga condiciona-
les y más complicaciones que lo que hemos simplificado arriba.
Vamos a ver cómo podemos listar todos los registros de una sola entidad, te-
niendo en cuenta, eso sí, que podemos paginar y buscar.
use ...
$entity = \app\models\core\Sanitize::string(trim(strtolower($entity)));
$ucEntity = \lib\MyFunctions::underscoredToCamelCaseEntityName($entity);
$frmLstClass = $ucEntity."FormType";
if (class_exists($frmLstClass)) {
147
Programación PHP profesional con Slim, Paris y Twig
}else{
$search = null;
$query = null;
$params = null;
}
// @2:
if ($formList instanceof FormSearchTypeInterface) {
$search = array(
'form' => $formList->getSearchForm(),
'data' => $search,
'errors' => array(),
);
}else{
$search = array(
'form' => array(),
'data' => array(),
'errors' => array(),
);
}
// @3:
$paginator = new Paginable($ucEntity,array(
'query' => $query,
'params' => $params,
'recPerPage' => 10
));
$paginator->setBaseRouteAndParams('admin.list-entity',
array('entity'=>$entity));
if (($page > 1) && ($page > $paginator->getPages())) {
$app->notFound();
}
$paginator->setCurrentPage($page);
// @4:
$items = $paginator->getResults();
// @5:
$app->render('backend/entity/list.html.twig', array(
'form' => $formList->getFormList(),
'search' => $search,
'items' => $items,
'entity' => $entity,
'paginator' => $paginator,
'searchable'=> ($formList instanceof FormSearchTypeInterface),
'entityName'=> $ucEntity::_entityName(),
));
} else {
$app->notFound();
}
})->via('GET','POST')->name('admin.list-entity');
148
10. Añadiendo Twig
Vamos a empezar por el final, que está más cerca. Esta ruta atiende GET y
POST porque la paginación y búsqueda se van a hacer por POST. Aclarado
este punto vamos a ver qué le vamos a mandar a la plantilla. Como ves la línea
que envía los datos a la plantilla es algo más compleja que la que hemos visto
en el punto anterior.
@1:
Fíjate que lo primero que hacemos es comprobar si la petición es POST y si
tenemos algún valor pasado en la variable search, ya que esto indica que el
usuario ha introducido un valor en la caja de texto correspondiente.
{% block stylesheets %} {# @7 #}
{{ parent() }}
<style type="text/css">
.center { text-align: center; }
</style>
{% endblock stylesheets %}
{% block subcontent %} {# @8 #}
<div class="row-fl uid">
<div class="span12">
<div>
<legend class="span4">{{ entityName ~ ' list'}}</legend>
{% if paginator.needPagination %} {# @9 #}
{{ paginator_backend_render(paginator) }}
{% endif %}
</div>
149
Programación PHP profesional con Slim, Paris y Twig
150
10. Añadiendo Twig
Quiero detenerme un instante en esta plantilla pues aunque no es muy extensa con-
densa muchas de las cosas que hemos visto o veremos. Quiero que te fijes y que
intentes hacer un esfuerzo mental intentando comprender cada una de las líneas.
Voy a hacer este esfuerzo contigo, para que no te sientas perdido.
En la primera línea ves que estamos extendiendo de una plantilla, que es la que
hace de base para todas las del backend. En principio no la abras aún, créeme,
tiene unos cuantos bloques que vamos a ver, y mucho HTML. Repasemos esta
en la que estamos y cuando te encuentres cómodo con ella, podrás mirar la
base y entender lo demás.
A continuación, en @6 estamos definiendo una variable. Esta nos va a servir
para que en el menú del sidebar se nos marque como activa la opción en la que
estamos, que si te fijas no es otra que admin.list-staticpage para este caso,
ya que entity se establece en el controlador que pasa los datos a la plantilla.
¡Muy bien! Veo que vas casando los conceptos. Este uso de variables y clases
activas para indicar el lugar del menú en donde se encuentra el usuario es algo
muy común y te acostumbrarás a verlo y utilizarlo. Ahora viene el bloque de las
hojas de estilo (@7). Como no queremos reescribirlo todo usamos la función
parent() que nos copia todo el contenido anterior del bloque, recuerda que sin
este ardid el bloque quedaría vacio a expensas de lo que incluyamos en la
plantilla hija (la que extiende).
Ahora viene el bloque principal (@8), el que contiene la información real. Me-
diante el condicional de @9 estamos agregando un paginador al listado en el
151
Programación PHP profesional con Slim, Paris y Twig
caso de que sea necesario, o sea, que haya más registros de los que caben en
una página.
Repasando te diré que paginator se le pasa a la plantilla y que es de clase Pagi-
nable, mira la creación en @3, de momento solo te diré que un objeto de clase
Paginable tiene un método needPagination que es el que hemos usado en la
plantilla. Fíjate que el paginador en realidad se pinta utilizando una función de
twig creada por nosotros.
Puedes ver su implementación en la clase PaginatorViewExtension dentro de
\app\models\core\Pagination. No te preocupes ahora por la paginación pues
hay una sección dedicada a esto en la sección “Paginación y búsqueda” en la
página 205.
El siguiente bloque (@10) pinta los campos oportunos para permitir la búsque-
da, para el caso de que la entidad proporcionada cumpla con la condición de
implementar la interfaz searchable.
Mira cómo le pasamos el dato a la plantilla en @5: 'searchable'=> ($formList
instanceof FormSearchTypeInterface). He tratado de que el método en ge-
neral sea muy compacto, por eso me he permitido esa licencia.
Ya que estamos vamos a ver uno por uno los datos que le pasamos a la plan-
tilla en @5:
$app->render('backend/entity/list.html.twig', array(
// formulario propiamente
'form' => $formList->getFormList(),
// objeto búsqueda si lo permite la entidad, sino será null
'search' => $search,
// los registros propiamente a listar
'items' => $items,
// entidad que estamos concretamente listando
'entity' => $entity,
// objeto paginador que nos va a permitir pintarlo
'paginator' => $paginator,
// booleano que indica si la entidad permite búsquedas
'searchable'=> ($formList instanceof FormSearchTypeInterface),
// el nombre de la entidad a efectos estéticos
'entityName'=> $ucEntity::_entityName(),
));
152
10. Añadiendo Twig
registros de una determinada entidad. En el caso que nos ocupa (páginas es-
táticas) no tenemos implementado la interfaz FormSearchTypeInterface, por
eso no nos ha pintado la caja de búsqueda en la captura de la imagen.
Por el momento, vamos a obviar este punto, luego iremos a una entidad que per-
mita búsquedas y lo veremos. ¡Sigamos entonces! Lo que viene a continuación
es la tabla que lista los registros, podemos ver que la cabecera (<thead>) se
pinta con otra función propia {{ form_table_head(form) }}, recuerda que las
funciones de Twig propias se declaran en el archivo lib\TwigViewSlim.php, si lo
revisas verás que la función form_table_head de twig en realidad la implementa
el método estático form_table_head de la clase FormWidget que encontrarás
en la carpeta app\models\core\Form, que vamos a revisar brevemente.
No tienen por qué coincidir los nombres de la función twig con el método de la
clase o función global que la implementa, pero creo que coincidirás conmigo en
que así el código es más fácil de seguir.
/**
* renders the table head of a list of records
*
* @param array $form
* @return html formatted string
*/
public static function form_table_head(array $form)
{
$result = ‘’;
foreach ($form as $item) {
$label = isset($item[‘widget’][‘label’])
? $item[‘widget’][‘label’]
: $item[‘field’];
$type = $item[‘type’];
if ($type==’boolean’) {
$label = ‘<i class=”icon-check”></i> ’.$label;
}
$result .= sprintf(‘<th><span>%s</span></th>’,$label);
}
return $result;
}
153
Programación PHP profesional con Slim, Paris y Twig
pintaremos un pequeño checkbox para facilitar la lectura. Date cuenta que a esta
función al final le estamos pasando el objeto formulario (que no deja de ser un
array).
Seguimos con el resto de la tabla. Vamos ahora a por los registros en sí. De
nuevo presta atención a la línea @11, verás que hay un bucle for que recorre
los elementos que tenemos que pintar, recuerda que items contiene los regis-
tros que cumplen con la paginación y la búsqueda (caso de haberla). Hemos
llegado a {{ form_table_row(form,item) }} que pinta la fila, dejando úni-
camente el tema de los botones de acción a expensas del resto del twig. Fíjate
que le estamos pasando el objeto formulario como a la función que pintaba la
cabecera y el item concreto que vamos a pintar.
154
10. Añadiendo Twig
if($value==='') $value=" ";
$result .= sprintf('<td><span class="%s">%s</span></td>',
$class,$value);
}
return $result;
}
155
Programación PHP profesional con Slim, Paris y Twig
Abordemos ahora la parte que nos habíamos dejado, el condicional que te-
nemos en @10. Recuerda que lo habíamos aparcado momentáneamente y
que dijimos que la entidad correspondiente tenía que implementar la interfaz
FormSearchTypeInterface para poder pintar esa zona, que si ves en la ima-
gen superior, no es más que un botón que desplega un input de texto para la
cadena de búsqueda y un botón para buscar.
156
10. Añadiendo Twig
cómo está hecho, aunque este tema se trata en profundidad en el capítulo 14,
apartado «Paginación y búsqueda». A destacar el hecho de que el cuadro de
búsqueda es un formulario que se envía por POST, así no se enturbia la URL y
el arrastre de parámetros cuando paginamos es transparente. Fíjate que se ha
pintado un solo input porque la clase que define cómo ha de ser la búsqueda de
esa entidad solo tiene un campo, pero podrían haber sido varios inputs:
public function getSearchForm()
{
Conclusión
Con esto hemos visto que las posibilidades de envío de datos desde el contro-
lador a la plantilla son prácticamente ilimitadas y nos dan mucho juego a la hora
de decidir qué es lo que se tiene que mostrar o no.
157
11 Formularios
Contenido
» Introducción
» Formulario de contacto
» Formularios en el backend
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
Creo que estamos de acuerdo en que la confección de los formularios es con
diferencia lo más tedioso en el desarrollo de una aplicación en general y web
en particular. Si esto lo unimos al siguiente punto en el temario: la validación,
tenemos el lote completo del tedio.
Ahora vamos a valernos de las interfaces y clases anexas que empezamos a ver
cuando hablábamos del modelo en el punto “Entity_contact” en la página 134.
Como ves algunas de las Entidades que ya vimos implementan también esta
interfaz.
<?php
namespace app\models\core\Form;
interface FormListTypeInterface
{
Esta interfaz obliga a implementar dos métodos que son los que permitirán al
generador de formularios hacer su trabajo. Por un lado, tenemos el método que
160
11. Formularios
Formulario de contacto
Uno de los formularios que tiene la aplicación es el de contacto. Aquí se trabaja
con una entidad que se persiste en la bbdd. Necesitaremos controlar cuestio-
nes como CSRF y captchas, cosas que no nos dan de base ni Slim ni Twig.
Veamos cómo implementarlos en este caso.
/**
* contact form
*/
$app->map('/contact/', function () use ($app) {
$errors = array();
$contact = \Entity\Contact::factory()->create();
// @1:
if ($app->request()->isPost()) {
$sum = $_SESSION[‘sum’];
$contact->bind($app->request()->post());
161
Programación PHP profesional con Slim, Paris y Twig
$errors = $contact->validate();
if (count($errors)==0) {
if ($sum['one']+$sum['two']==$contact->sum) {
$contact->save();
$app->redirect($app->urlFor('.contact.thanks'));
} else {
$errors['sum'] = 'Sum error';
}
}
} else {
$sum = array(
'one' => rand(1,30),
'two' => rand(1,30),
);
$sum['result'] = $sum['one'] + $sum['two'];
$_SESSION['sum'] = $sum;
}
// @2:
$app->render('frontend/contact/index.html.twig',array(
'contact' => $contact,
'sum' => $sum,
'errors' => $errors,
));
})->via('GET','POST')->name('contact.index');
{{ macro.form_styles() }}
162
11. Formularios
163
Programación PHP profesional con Slim, Paris y Twig
</fieldset>
</form>
</div>
<div class="grid_2"><p> </p></div>
<div class="h2 grid_12"> </div>
{% endblock %}
Espero que veas en el código cosas que ya hemos ido tratando a lo largo del
libro. Hay otras en cambio que trataremos en breve, como la internacionalización.
Pero no te preocupes ahora por eso, vamos a centrarnos en lo que sucede con el
formulario. La primera vez (en la petición GET) se pinta el formulario con los datos
que le llegan mediante la instrucción render (ver @2): contact, sum y errors.
Esta estructura es la que debes seguir para la mayor parte de los formularios
de introducción de información. En casos más concretos, antes de guardar el
registro en la bbdd sanearemos la información que nos llega por parte del usua-
rio, filtrándola de alguna manera.
164
11. Formularios
Estos dos métodos nos van a permitir indicar en una entidad qué campos se
van a mostrar en el listado y cuáles en el formulario de edición.
namespace Entity;
use app\models\core\Form\FormListTypeInterface;
// ...
class ContactFormType implements FormListTypeInterface ...
{
public function getFormList()
{
$formBuilder = new FormListBase();
return $formBuilder
->add('id','text', array(
'label'=>'#',
'attr' =>array(
'class'=>'badge',
),
)
)
->add('created_at','date', array('label'=>'Created At'))
->add('email','text',array('label'=>'Email'))
->add('phone','text',array('label'=>'Phone'))
->add('pending','boolean',array(
'label'=>' ?',
'attr'=>array(
'class' => 'span1',
),
)
)
->end();
}
return $formBuilder
->add('id','p', array(
'readonly'=>true,
'attr'=>array(
'class'=>'text-success',
)
)
)
165
Programación PHP profesional con Slim, Paris y Twig
->add('message','textarea')
->end();
}
// ...
}
Formularios en el backend
Vamos a ver ahora el caso de un listado de registros y un formulario para la
zona de backend. Esta zona está totalmente automatizada, por lo que encon-
trarás las rutas un tanto abstractas. Como ejemplo vamos a ver el caso de la
entidad Article.
166
11. Formularios
<?php
use lib\MyFunctions;
use app\models\core\BaseModel;
use app\models\core\ValidableInterface;
use app\models\core\Pagination\Paginable;
use app\models\core\Form\FormSearchTypeInterface;
use app\models\core\Search\SearchQueryBuilder;
/**
* crud - generic list for entities that have defined
* Entity\EntityFormType
*/
$app->map('/admin/list/:entity/(:page)', function($entity,$page=1) use ($app) {
$entity = \app\models\core\Sanitize::string(trim(strtolower($entity)));
$ucEntity = \lib\MyFunctions::underscoredToCamelCaseEntityName($entity);
$frmLstClass = $ucEntity."FormType";
if (class_exists($frmLstClass)) {
if ($app->request()->isPost()) {
$search = $app->request()->post('search');
$qb = new SearchQueryBuilder($formList->getSearchForm(),$search);
$qb->buildQuery();
$query = $qb->getQuery();
$params = $qb->getParams();
}else{
$search = null;
$query = null;
$params = null;
}
167
Programación PHP profesional con Slim, Paris y Twig
$paginator->setBaseRouteAndParams(
'admin.list-entity',
array(‘entity’=>$entity)
);
$paginator->setCurrentPage($page);
$items = $paginator->getResults();
$app->render('backend/entity/list.html.twig',array(
'form' => $formList->getFormList(),
'search' => $search,
'items' => $items,
'entity' => $entity,
'paginator' => $paginator,
'searchable'=> ($formList instanceof FormSearchTypeInterface),
'entityName'=> $ucEntity::_entityName(),
));
} else {
$app->notFound();
}
})->via('GET','POST')->name('admin.list-entity');
168
11. Formularios
namespace Entity;
use app\models\core\Form\FormNewInterface;
use app\models\core\Form\FormListTypeInterface;
use app\models\core\Form\FormListBase;
use app\models\core\Form\FormBase;
use app\models\core\Form\FormSearchTypeInterface;
class ArticleFormType
implements FormListTypeInterface,
FormSearchTypeInterface,
FormNewInterface
{
/**
* tipo de formulario de lista
*
* @return array
*/
public function getFormList()
{
return $formBuilder
->add('id', 'text', array('label'=>'#',
'attr' =>array(
'class'=>'badge'),
))
169
Programación PHP profesional con Slim, Paris y Twig
->add('slug', 'text')
->add('title', 'text', array('label'=>'Title'))
->end();
/**
* tipo de formulario de edición de campos
*
* @return array
*/
public function getForm()
{
return $formBuilder
->add('id', 'p', array('readonly'=>true,
'attr' =>array(
'class' =>'text-success'
))
)
->add('slug', 'text', array('readonly'=>true))
->add('title', 'text', array('label'=>'Title'))
/*
->addSubForm(
$subForm->add('id', 'text')
->add('lang', 'text')
->add('content','text')
->end()
)
*/
->end();
/**
* tipo de formulario de búsqueda para el backend
*
* @return array
*/
public function getSearchForm()
{
return $formBuilder
170
11. Formularios
->add(array('title','description'),
'text', array(
'label' => 'Text',
'op' => 'like',
)
)
->end();
Conclusión
No es que los formularios de una aplicación web no se puedan hacer de la
manera tradicional, lo que ocurre es que si uno no es muy metódico en este
sentido se suele dejar cosas como el CSRF, validación de algún campo, pre-
sentación de algún otro, etc.
De todas maneras, como siempre, de lo que se trata es de que veas una ma-
nera de hacerlo y sobre todo de aprender.
171
12 Validación
Contenido
» Introducción
» Extender BaseModel
» ¿Cómo usar la validación?
» SluggableInterface
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
Cualquier aplicación que vaya a aceptar datos de un usuario, ya sea mediante
un formulario o no (API, callbacks, etc.) debe ser capaz de soportar algún sis-
tema de validación.
Cuando vimos el capítulo del modelo en detalle vimos una por una cada una de
las entidades que integran My-simple-web, pero ahora vamos a centrarnos solo
en la parte de validación de esas clases.
Extender BaseModel
Para permitir la validación vamos a preparar una interfaz y haremos que la
cumplan aquellas entidades que se van a poder introducir por parte del usuario
o admin.
<?php
namespace app\models\core;
interface ValidableInterface
{
public function validate();
}
174
12. Validación
erróneo por varios conceptos porque solo podremos indicar un motivo. Por tan-
to, seremos precisos con las comprobaciones que hagamos.
return $result;
}
// ...
}
<?php
class Validate
{
public static function email($email)
{
return preg_match("|^([\w\.-]{1,64}@[\w\.-]{1,252}\.\w{2,4})$|i", $email);
}
175
Programación PHP profesional con Slim, Paris y Twig
/**
* Return message “$field can’t leave blank”, translated
*
* @param string $field
*
* @return string
*/
public static function cantLeaveBlank($field)
{
return sprintf(_(‘%s can\’t leave blank’),$field);
}
Es muy sencillo: solo hay que pasarle el nombre del campo. Nos devolverá un
mensaje traducido indicando que no se puede dejar el campo x en blanco.
Se podría crear un método diferente para cada tipo de validación, por ejemplo, para
comprobar un correo electrónico tienes el método Validate::mail($field).
return $result;
}
176
12. Validación
if ($app->request()->isPost()) {
$sum = $_SESSION['sum'];
$contact->bind($app->request()->post());
$errors = $contact->validate();
if (count($errors)==0) {
if ($sum['one']+$sum['two']==$contact->sum) {
$contact->save();
$app->redirect($app->urlFor('.contact.thanks'));
} else {
$errors['sum'] = 'Sum error';
}
}
}
177
Programación PHP profesional con Slim, Paris y Twig
SluggableInterface
Aunque no propiamente una validación tal y como hemos visto en los casos
anteriores, lo cierto y verdad es que permite establecer si se va a aceptar un
slug como válido, normalmente debido al hecho de que ya exista en la bbdd en
otro registro que no es el actual.
namespace app\models\core;
interface SluggableInterface
{
La idea como ves es tener una función homogénea que nos permita dar por
bueno ese slug, como te comentaba.
178
12. Validación
que solo exista un slug igual a ese, que corresponda con el ID 1235 y que ade-
más sea URL compliant. Esto último es que no tenga caracteres no aceptados
en las rutas, como son: espacios, símbolos extraños como ?, *, #, &, y aunque
se aceptan por los buscadores, cuando tratamos con páginas con un juego de
caracteres latino, ni acentos, tildes ni cedillas.
namespace lib;
// ...
class MyFunctions
{
// ...
/**
* slugify string passed
*
* @param string $text
*
* @return string
*/
public static function slug($text)
{
$str = trim($text);
$str = str_replace(array(
'ñ','Ñ','ç','Ç',
'á','é','í','ó','ú',
'à','è','ì','ò','ù',
'ä','ë','ï','ö','ü',
'Á','É','Í','Ó','Ú',
'À','È','Ì','Ò','Ù',
'Ä','Ë','Ï','Ö','Ü',
),array(
'n','N','c','C',
'a','e','i','o','u',
179
Programación PHP profesional con Slim, Paris y Twig
'a','e','i','o','u',
'A','E','I','O','U',
'A','E','I','O','U',
'A','E','I','O','U',
),$str);
return $str;
}
// ...
Como te decía utilizaremos este método cuando el formulario de vuelta nos lle-
gue con el slug vacío, utilizando el título del artículo, por ejemplo, para generarlo.
Como en otras ocasiones, vamos a tomar la clase Article para ver cómo está
resuelto este método.
<?php
namespace Entity;
// ..
class Article
extends BaseModel
implements SluggableInterface, ValidableInterface
{
/**
* Checks that slug not exists
*
180
12. Validación
// ...
}
Conclusión
Creo que ha quedado explicado de manera bastante clara cómo validar los
contenidos de una entidad, delegando esa comprobación en ella misma, que
es quien debe conocer cómo deben ser sus campos.
181
13 i18n:
Internacionalización
Contenido
» Introducción
» Sistema de trabajo
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
Internacionalización es la preparación de una aplicación para que presente los
textos en el idioma elegido por el usuario de entre los soportados. Para ello
cada vez que mostremos en una plantilla o directamente en PHP un texto, uti-
lizaremos una función que convertirá ese texto según el idioma elegido. Hasta
aquí la teoría es sencilla.
No hay muchos sistemas que permitan obtener de una manera eficiente textos
en varios idiomas. Por su sencillez de uso y su facilidad de implementación ya
que tiene soporte nativo en PHP he elegido gettext.
Sistema de trabajo
Para que te hagas una idea, si no conoces esta herramienta, yo utilizo este
sistema:
└── i18n
└── TRANSLATE.md
└── ca_ES
│ └── LC_MESSAGES
│ └── default.mo
│ └── default.po
└── en_GB
│ └── LC_MESSAGES
│ └── default.mo
│ └── default.po
184
13 i18n. Internacionalización
└── es_ES
│ └── LC_MESSAGES
│ └── default.mo
│ └── default.po
└── i18n.php
Consejo
Cuando actualices el catálogo es posible que no veas los cambios
en la web hasta que no reinicies apache.
185
Programación PHP profesional con Slim, Paris y Twig
<?php
$langs = array (
'es' => 'ES',
'en' => 'GB',
'ca' => 'ES',
);
$code = isset($_REQUEST['lang'])?$_REQUEST['lang']:'es';
if (isset($langs[$code]))
$iso_code = $code.'_'.$langs[$code];
else{
$code = "es";
$iso_code = 'es_ES';
}
if (isset($_SESSION['lang'])) $_SESSION['lang']=$code;
186
13 i18n. Internacionalización
Para poder utilizar gettext dentro de las plantillas debemos crear una exten-
sión, esto es así para todas las funciones y fi ltros que no vienen de serie en
Twig. Este punto lo vimos en su momento (página 49). Vamos a repasar aquí
brevemente cómo hacer que gettext esté disponible en las plantillas:
<?php
187
Programación PHP profesional con Slim, Paris y Twig
Conclusión
Aunque no es la única manera de enfocar el tema de la internacionalización, no
he querido liarte con sistemas más complejos y menos generalistas.
Ten en cuenta que gettext puede ser usado de manera nativa por todas las
aplicaciones que se ejecutan en los servidores, de manera que, si utilizas un
sistema así poco te costará aprender variantes.
188
14 Agregando
nuevos componentes
Contenido
» Introducción
» Anotaciones para rutas
» EntityManager
» Paginación y búsqueda
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Introducción
En este capítulo vamos a abordar la parte más complicada del libro: los com-
portamientos deseados que hay que programar de cero ya que no vienen con
los componentes que he elegido para desarrollar My-simple-web, por lo que te
pido que estés muy atento al contenido de este capítulo.
/**
* @Route("/", name="_demo")
* @Template()
*/
public function indexAction()
{
return array();
}
// ...
}
Como ves la definición de la ruta viene dada por un comentario «especial» con
el símbolo arroba delante y la palabra Route. Dentro se indica como primer pa-
rámetro la ruta que va a atender ese controlador y cómo la vamos a llamar a ni-
vel lógico para dirigirnos a ella en otras partes de la aplicación, mediante name.
190
14. Agregando nuevos componentes
Vemos que hay un método que atiende la ruta, una declaración de ruta y un
nombre lógico. Se parece bastante, ¿no?
1
Hasta ahora hemos visto la declaración compacta de la ruta mediante una clo-
sure y quedando todo en una invocación encadenada de $app->metodo(ruta,
closure)->name(nombre)
191
Programación PHP profesional con Slim, Paris y Twig
La diferencia con Symfony2 es que hemos dejado Route con solo el contenido
de la URL que tiene que atender, hemos creado una nueva anotación llamada
Name, y tenemos otra anotación Method que no habíamos visto en el otro caso
porque tiene un valor por defecto, que es GET, tenemos que reproducir ese
comportamiento, o sea, si no se indica Method será GET.
¿Cómo empezamos?
La verdad es que necesitamos leer el archivo php de manera paralela a lo que
hace el propio intérprete de PHP para analizar las anotaciones y en función de
ellas crear las correspondientes sentencias $app->get, o $app->post, etc.
Como este sistema no parece muy eficiente de nuevo volveremos la cara hacia
Symfony2 para ver cómo lo han resuelto. Utilizaremos un archivo a modo de
caché, que solo regeneraremos cuando las fechas del archivo PHP de origen
y el cacheado difieran.
Además necesitaremos verificar el sistema con el que Slim invoca las funciones
globales, para que nos permita hacer lo propio con los métodos estáticos de una
clase, y por último crearemos una clase Controller de la cual extenderán las
clases que van a dar soporte a rutas de Slim, una de las ventajas de la que va-
mos a dotar a la clase Controller es la instancia de Slim como una propiedad.
function __construct()
{
$this->slimInstance = Slim::getInstance();
}
192
14. Agregando nuevos componentes
El analizador de anotaciones
A continuación vamos a ver cómo leer el archivo PHP, extraer las cadenas median-
te coincidencia de expresiones regulares y por fin generar las reglas para las rutas.
Al principio pensé en crear un archivo yml al más puro estilo symfony, pero
luego me di cuenta de que no era tan eficiente como crear un archivo PHP
ejecutable directamente y además no es necesario alterar el archivo de caché
generado de manera manual, por lo que da igual el formato.
La misma rutina que hace el análisis crea los archivos de caché y los ejecuta, un
todo en uno que espero que no te produzca demasiado quebradero de cabeza.
Al final se han debido de cargar ambos archivos en el núcleo para que se pue-
dan invocar en el caso de que coincida alguna ruta.
193
Programación PHP profesional con Slim, Paris y Twig
namespace Router;
use \Slim\Slim;
class RoutingCacheManager
{
protected $cacheDir;
function __construct()
{
$this->cacheDir = dirname(dirname(dirname(dirname(__FILE__)))) .
'/cache/routing';
@mkdir($this->cacheDir, 0777);
}
/**
* This method writes the cache content into cache file
194
14. Agregando nuevos componentes
*
* @param $class
* @param $content
*
* @return string
*/
protected function writeCache($class, $content)
{
$content = '<?php' . PHP_EOL . PHP_EOL .
'// Generated with RoutingCacheManager' . PHP_EOL . PHP_EOL .
$content . PHP_EOL;
$fileName = $this->cacheFile($class);
file_put_contents($fileName, $content);
return $fileName;
}
/**
* Sets the modify time of cache file according to classfile
*
* @param $classFile
*/
protected function setDateFromClassFile($classFile)
{
$className = $this->className($classFile);
$cacheFile = $this->cacheFile($className);
$fileDate = filemtime($classFile);
touch($cacheFile, $fileDate);
}
/**
* gets the full path and the name of cache file
*
* @param $class
*
* @return string
*/
protected function cacheFile($class)
{
return $this->cacheDir . '/' . $class . '.php';
}
/**
* Extracts the className through the classfile name
*
* @param $classFile
*
* @return mixed
* @throws \Exception
*/
195
Programación PHP profesional con Slim, Paris y Twig
/**
* Indicates if the classfile has a diferent modify time that cache file
*
* @param $classFile
*
* @return bool
*/
protected function hasChanged($classFile)
{
$className = $this->className($classFile);
$cacheFile = $this->cacheFile($className);
$cacheDate = file_exists($cacheFile) ? filemtime($cacheFile) : 0;
$fileDate = filemtime($classFile);
/**
* @param $classFile
*
* @return string
* @throws \Exception
*/
protected function processClass($classFile)
{
$className = '';
$content = file_get_contents($classFile);
$result = '';
preg_match_all('/class\s+(\w*)\s*(extends\s+)?([^{])*/s',
$content, $mclass, PREG_SET_ORDER);
$className = $mclass[0][1];
if (!$className){
throw new \Exception(sprintf('class not found in %s', $classFile));
}
196
14. Agregando nuevos componentes
197
Programación PHP profesional con Slim, Paris y Twig
return $this->cacheFile($className);
}
/**
* Return cachefile contents
*
* @param $classFile
*
* @return string
*/
protected function getCache($classFile)
{
return file_get_contents($this->updateAndGetCacheFileName($classFile));
}
/**
* Process the cachefile, in PHP require is enough
*
* @param $classFile
*
* @throws \Exception
*/
protected function processCache($classFile)
{
require_once($this->updateAndGetCacheFileName($classFile));
}
/**
* Main method to invoke the routing system
*
* @param $phpFile
*/
public function loadRoute($phpFile)
{
require_once $phpFile;
$this->processCache($phpFile);
}
Verás que no he utilizado el método de creación de rutas que hemos visto con
más regularidad sino aquel que se debe usar para los formularios, que veremos
más adelante. Te hago un pequeño adelanto:
$app->map('nombre/ruta/fisica', closure)->via(methods)->name('nombre_logico');
Lo que hacemos es mapear una determina ruta via unos determinados méto-
dos. De esta manera, la caché de ruteo sin saber de antemano qué métodos
198
14. Agregando nuevos componentes
Vamos a ver el resultado de la caché al procesar las rutas que ya están prepa-
radas con las anotaciones:
<?php
EntityManager
Aunque la idea está importada de otros frameworks quizás el nombre no sea el
más adecuado, pues parte de la funcionalidad del EntityManager ya la realiza
el ORM.
Para centrar las ideas lo que vamos a implementar es un gestor que nos permi-
ta regenerar la base de datos, pretendendiendo llegar a este modelo de código
compacto:
<?php
//..
$em = new \app\models\EntityManager(true);
$em->dropDatabase();
$em->createDatabase();
$em->createTables();
$em->generateFixtures();
199
Programación PHP profesional con Slim, Paris y Twig
namespace app\models;
use \ORM;
use \app\models\core\Registry;
use \app\models\core\FixturableInterface;
class EntityManager
{
private $conn;
private $dbname;
private $dump;
/**
* Execs the sql statement passed
*
* @param $sql
*
* @return resource
*/
public function execute($sql)
{
if ($this->dump) {
print $sql.PHP_EOL;
}
$result = mysql_query($sql,$this->conn);
if (mysql_errno($this->conn)) {
die($sql.PHP_EOL.mysql_error($this->conn).PHP_EOL);
}
return $result;
}
200
14. Agregando nuevos componentes
/**
* Reads the contents of this dir and returns only dirs
* that have first letter capitalized
*
* @return array
*/
protected static function readdir()
{
$entries = array();
foreach (scandir(__DIR__) as $entry){
if ($entry!='.' && $entry!='..' && is_dir(__DIR__.'/'.$entry)) {
if ($entry == ucfirst($entry)) {
$entries[] = $entry;
}
}
}
return $entries;
}
/**
* Forces the load of classes contained in this dir
*
* @return void
*/
public static function forceRequireEntityClasses()
{
$dirs = self::readdir();
foreach ($dirs as $dir) {
$subdir = __DIR__.'/'.$dir;
$files = scandir($subdir);
foreach ($files as $file) {
// only the php files that has the first letter capitalized
if ($file!='.' && $file!='..' && preg_match('/\.php$/i',$file)) {
if ($file == ucfirst($file)) {
require_once $subdir.'/'.$file;
}
}
}
}
}
/**
* Drops database
*/
public function dropDatabase()
{
$sql = sprintf('DROP DATABASE IF EXISTS `%s`;',$this->dbname);
$this->execute($sql);
}
201
Programación PHP profesional con Slim, Paris y Twig
/**
* Create database
*/
public function createDatabase()
{
$sql = sprintf('CREATE DATABASE IF NOT EXISTS `%s`;', $this->dbname);
$this->execute($sql);
}
/**
* Select Database
*/
public function selectDb()
{
mysql_select_db($this->dbname);
}
/**
* Create tables
*/
public function createTables()
{
self::forceRequireEntityClasses();
$this->selectDb();
// get entity classes that extends from BaseModel
$classes = get_declared_classes();
foreach($classes as $class){
if (is_subclass_of($class,'app\\models\\core\\BaseModel')) {
if (method_exists($class,'_creationSchema')) {
if ($this->dump) {
print $class.PHP_EOL;
}
//$sql = $class.'::_creationSchema';
$sql = $class::_creationSchema();
$this->execute($sql);
}
}
}
}
/**
* Generate fixtures for all entities
*/
public function generateFixtures()
{
self::forceRequireEntityClasses();
$ordered = array();
// get entity classes that extends from FixturableInterface
$classes = get_declared_classes();
foreach($classes as $class){
//print $class.PHP_EOL;
202
14. Agregando nuevos componentes
if (is_subclass_of($class,'app\\models\\core\\FixturableInterface')) {
//print 'order '.$class::getOrder().’ ‘;
$ordered[sprintf("%05d-%s",$class:getOrder(),$class)] = $class;
}
}
$this->selectDb();
ksort($ordered);
print PHP_EOL;
$fixtureRegistry = new Registry();
foreach($ordered as $order=>$class){
if ($this->dump) {
print $order.PHP_EOL;
}
/** @var FixturableInterface $fixtureClass */
$fixtureClass = new $class;
$fixtureClass->generateFixtures($fixtureRegistry);
}
}
}
namespace Entity;
use ...;
/**
* Class that stores articles of this web
*/
class Article extends BaseModel implements ...
{
// ...
/**
* Get the SQL creation sentece of this table
*
* @param array $options
* @return string
*/
public static function _creationSchema(Array $options = array())
{
203
Programación PHP profesional con Slim, Paris y Twig
$class = self::_tableNameForClass(get_called_class());
// default options
$options = array_merge(self::_defaultCreateOptions(),$options);
return
<<<EOD
EOD;
// ...
}
204
14. Agregando nuevos componentes
Paginación y búsqueda
No es que sean lo mismo, pero te darás cuenta, por la manera en que hay que
resolver la cuestión de que van intimamente ligadas. Pues podemos paginar
pero además hay que mantener el filtrado de la búsqueda.
Veamos en primer lugar cómo empezar a obtener los registros paginados des-
de la bbdd. Evidentemente hay que hacer uso de las opciones LIMIT y OFFSET
que nos brinda SQL. Pero necesitamos algo más, lo normal es saber cuántos
registros hay en total, cuántos registros queremos por página y de ahí calcular
cuantas páginas hay en total.
Presentaremos un paginador con este estilo:
¡Bien! Vamos a ver los cálculos, cómo traducir esto a instrucciones para el
ORM y cómo presentar los datos mediante la plantilla. ¡Cógete fuerte a la silla!
<?php
define('REG_POR_PAG', 10);
$app->get('/lista-clientes', 'lista_clientes');
function lista_clientes()
{
$app = \Slim\Slim::getInstance();
$request = $app->request();
$currentPage = $request->get(‘pa’) ?: 1;
$clientes = Model::factory(‘Cliente’)
->offset($firstRecord)
->limit(REG_POR_PAG)
->find_many();
$app->render('clientes.html.twig', array(
'clientes' => $clientes,
'pa' => $currentPage,
'numPaginas' => $numPaginas,
));
}
205
Programación PHP profesional con Slim, Paris y Twig
Lo que tiene de malo esta primera aproximación es que para cada uno de los
listados que queramos tener paginados (en teoría todos), debemos copiar este
modelo, y créeme, no va a ser tan sencillo cuando añadamos la búsqueda.
Solo quiero hacerte ver cuál es la dinámica, ya que la idea es extrapolar este
modelo y llegar a conseguir que llamar a un paginador sea tan sencillo como
hacer esto:
$paginator = new Paginable("Cliente",array('recPerPage' => 10));
$paginator->setBaseRouteAndParams('lista-clientes', array(‘entity’=>cliente));
if (($page > 1) && ($page > $paginator->getPages())) {
$app->notFound();
}
define('REG_POR_PAG', 10);
$app->get('/lista-clientes', 'lista_clientes');
function lista_clientes()
{
$app = \Slim\Slim::getInstance();
$request = $app->request();
// conviene sanear la entrada
$search = $request->get('search');
$currentPage = $request->get('pa') ?: 1;
if(!substr_count($search, '%')){
$search = '%' . $search . '%';
}
$clientes = Model::factory('Cliente')
->offset($firstRecord)
206
14. Agregando nuevos componentes
->limit(REG_POR_PAG)
->where_like('nombre', $search)
->find_many();
$app->render('clientes.html.twig', array(
'clientes' => $clientes,
'pa' => $currentPage,
'numPaginas' => $numPaginas,
));
Por tanto, hemos visto que el algoritmo se complica, tenemos que conservar
el where tanto en la obtención de datos, como en la cuenta de registros para
calcular el número de páginas totales.
Esto para el caso de una sola condición, podemos querer buscar por nombre,
teléfono, email, etc.
Por este motivo, vamos a crear unas interfaces que nos permitan dotar a las
clases de comportamientos especiales en los casos de paginación y búsqueda.
namespace app\models\core\Pagination;
interface PaginableInterface
{
/**
* Generates a paginator from the ORMWrapper specified
* with ten records per page as default
*
* @param string $entity
* @param array $_options
*/
public function __construct($entity, $_options = array());
/**
* Returns the items for the page selected
207
Programación PHP profesional con Slim, Paris y Twig
*
* @return ORM ArrayCollection
*/
public function getResults();
/**
* Set the records per page desired
*
* @param int $num
*/
public function setNumRecPerPage($num);
/**
* sets the current page
*
* @param $page
*/
public function setCurrentPage($page);
/**
* obtains total page number
*
* @return mixed
*/
public function getPages();
/**
* obtains the current page
* @return mixed
*
*/
public function getCurrentPage();
/**
* Set the base route and params to generate route
* for each page
*
* @param $route
* @param $params
*/
public function setBaseRouteAndParams($route, $params);
/**
* returns the route for specified page
*
* @param int $num
* @return string
*/
public function getRouteForPage($num);
208
14. Agregando nuevos componentes
/**
* returns true if number of records is greater
* than recPerPage
*
* @return bool
*/
public function needPagination();
/**
* returns true if currentPage is not the first
*
* @return bool
*/
public function hasPreviousPage();
/**
* returns the number of previous page
*
* @return int
*/
public function getPreviousPage();
/**
* returns true if currentPage are not the last
*
* @return int
*/
public function hasNextPage();
/**
* returns the number of the next page
*
* @return int
*/
public function getNextPage();
$options = array_merge(array(
209
Programación PHP profesional con Slim, Paris y Twig
Tenemos una configuración por defecto: no hay query (búsqueda), y una can-
tidad de 10 registros por página, es importante partir de unos datos conocidos,
por eso array_merge es una instrucción muy útil para juntar las opciones junto
con parámetros básicos.
Ahora le ha tocado el turno al método que nos devuelve los registros de la se-
lección, tanto a nivel de página actual como de búsqueda si la hay.
public function getResults()
{
return $result;
210
14. Agregando nuevos componentes
}else{
return BaseModel::factory($this->entity)
->offset($start)
->limit($this->recPerPage)
->find_many();
}
}
}
Un método que nos va a ser de utilidad cuando utilicemos esta clase va a ser
el de saber cual es la URL que nos conduce a la página anterior o a la página
siguiente, para cuando tengamos que pintar un paginador en pantalla y facilitar
al usuario la navegación.
Por eso los métodos que vienen a continuación los tienes que ver en conjun-
to teniendo en mente este fin. Primero establecemos una ruta base, que va a
admitir un parámetro página obligatoriamente además de los que los paráme-
tros que admita de base. Imagínate que tenemos este tipo de ruta: /articulos/
index/:year/:month/:day/:page, como puedes deducir la función que atienda esta
ruta va a tener cuatro parámetros así: function loquesea($year,$month,
$day,$page), nos interesa tener claros los parámetros fijos y poder variar la
página en función de si estamos avanzando o retrocediendo en la navegación.
Veámos el código:
public function setBaseRouteAndParams($route, $params)
{
$this->route = $route;
$this->routeParams = $params;
}
2
Como ya vimos en el capítulo dedicado al modelo.
211
Programación PHP profesional con Slim, Paris y Twig
$params = array_merge(array(‘page’=>intval($num)),$this->routeParams);
$app->render('frontend/articles/index.html.twig',array(
'articles' => $articles,
'paginator' => $paginator,
));
})->name('articles.index');
Como puedes apreciar en el código existe una extensión Twig para pintar el
paginador, veámosla:
class TwigViewSlim extends Twig
{
private function addFunctions(\Twig_Environment $twigEnvironment)
{
212
14. Agregando nuevos componentes
// ...
$twigEnvironment->addFunction('paginator_backend_render',
new Twig_Function_Function(
'\app\models\core\Pagination\PaginatorViewExtension::render',
array(
'is_safe' => array('html')
)
));
// ...
}
// ...
}
namespace app\models\core\Pagination;
use app\models\core\Pagination\PaginableInterface;
use app\models\core\Pagination\PaginationRender;
class PaginatorViewExtension
{
/**
* Shows the paginator
*
* @param \app\models\core\PaginableInterface $paginator
* @return mixed
*/
public static function render(PaginableInterface $paginator)
{
$pagination = new PaginationRender($paginator);
return $pagination->render();
}
213
Programación PHP profesional con Slim, Paris y Twig
<?php
namespace app\models\core\Pagination;
use app\models\core\Pagination\PaginationRenderInterface;
use app\models\core\Pagination\Paginable;
$page = $this->paginator->getCurrentPage();
$numpages = $this->paginator->getPages();
if ($startPage < 1) {
$endPage = min($endPage + (1 - $startPage), $numpages);
214
14. Agregando nuevos componentes
$startPage = 1;
}
if ($endPage > $numpages) {
$startPage = max($startPage - ($endPage - $numpages), 1);
$endPage = $numpages;
}
$pages = array();
// previous
$class = $this->options['css_prev_class'];
$url = $this->options['prev_disabled_href'];
if (!$this->paginator->hasPreviousPage()) {
$class .= ' '.$this->options['css_disabled_class'];
} else {
$url = $this->paginator->getRouteForPage(
$this->paginator->getPreviousPage()
);
}
// first
if ($startPage > 1) {
$pages[] = sprintf('<li><a href="%s">%s</a></li>',
$this->paginator->getRouteForPage(1), 1);
if (3 == $startPage) {
$pages[] = sprintf('<li><a href="%s">%s</a></li>',
$this->paginator->getRouteForPage(2), 2);
} elseif (2 != $startPage) {
$pages[] = sprintf('<li class="%s"><a href="%s">%s</a></li>',
$this->options['css_dots_class'],
$this->options['dots_href'],
$this->options['dots_message']);
}
}
// pages
for ($i = $startPage; $i <= $endPage; $i++) {
$class = '';
if ($i == $page) {
$class = sprintf(' class="%s"', $this->options['css_active_class']);
}
$pages[] = sprintf('<li%s><a href="%s">%s</a></li>',
$class, $this->paginator->getRouteForPage($i), $i);
}
// last
if ($numpages > $endPage) {
215
Programación PHP profesional con Slim, Paris y Twig
// next
$class = $this->options['css_next_class'];
$url = $this->options['next_disabled_href'];
if (!$this->paginator->hasNextPage()) {
$class .= ' '.$this->options['css_disabled_class'];
} else {
$url = $this->paginator->getRouteForPage(
$this->paginator->getNextPage());
}
Repasa bien el código, he puesto pequeños comentarios para que se vea qué
sección estamos tratando.
El código HTML correspondiente que se pintará para cada uno de los elemen-
tos queda parametrizado por las opciones que podemos inyectar a la clase.
No es muy bueno que el código HTML esté integrado dentro de las clases PHP,
en este caso se dan dos eximentes: estamos declarando un extensión de Twig,
216
14. Agregando nuevos componentes
Eso sí, el tipo de paginador que mostraría sería una lista desordenada (ul) con
elementos (li) para cada página.
Esto nos es útil si queremos utilizar los css de twitter bootstrap, pero si que-
remos implementar nuestro propio paginador con elementos div por ejemplo
tendremos que cambiar parte del código.
Conclusión
Hasta aquí en lo concerniente a la incorporación de nuevos componentes a la
aplicación My-simple-web. Es posible que te hayas sentido un tanto confuso
con la cantidad de código que te he mostrado.
217
15 Aplicación
de ejemplo
Contenido
» Presentación
» ¿Cómo empezar?
» ¿Qué es un fork?
» Pull request
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Presentación
Como hemos ido viendo a lo largo de esta obra, la aplicación de ejemplo que
ilustra los conceptos de este libro la tienes a tu disposición en http://github.com/
jlaso/my-symple-web. Eres libre de usarla, modificarla y mejorarla cuanto con-
sideres oportuno, solo espero que te sea de utilidad lo expuesto, y que si tienes
alguna mejora que aportar y deseas colaborar lo hagas abiertamente, a través
de los mecanismos que proporciona github, mediante los pull-request. Por su-
puesto no he pretendido en ningún momento sentar cátedra, ten en cuenta que
solo soy un modesto programador con muchas ganas de aprender. Por lo tanto
tampoco te fies al al ciento por ciento de lo que aquí te he expuesto. Pruébalo
todo por tu cuenta, aprende del uso y de la práctica y, desde luego, si encuen-
tras algún error estaré encantado de recibirlo y corregirlo.
En todo caso, ten siempre en cuenta que My-simple-web solo es una estructura
que te permitirá crear tus propias aplicaciones, por tanto, no es una web termi-
nada. De hecho te recomiendo que me sigas en github para estar al corriente
de los cambios que en ella voy realizando regularmente.
¿Cómo empezar?
Si de verdad tienes interés en utilizar la estructura de My-simple-web para tus pro-
pios proyectos lo primero que tienes que hacer es un fork del proyecto en tu propia
cuenta de github. Cualquier mejora que creas susceptible de ser incorporada al
proyecto principal y que desees compartir tienes que solicitar un pull-request.
220
15. Aplicación de ejemplo
Una vez hayas entrado con tu cuenta en github, acude a la dirección http://jlaso.
github.io/my-simple-web/
Esta es una pequeña pantalla resumen con unas pequeñas instrucciones para
clonar el proyecto, enlaces para descargarlo sin git y enlace al repositorio ori-
ginal en github.
Si seguimos el link:
221
Programación PHP profesional con Slim, Paris y Twig
¿Qué es un fork?
Es como fotocopiar un proyecto. Como te he mencionado antes, la creación
de proyectos abiertos (libre u open-source) en github es gratuita a la fecha de
publicación de este libro. Lo que hace el fork es crear un proyecto en tu cuen-
ta con el mismo nombre que el que estás «forkeando» y luego se hace un git
clone en tu cuenta.
A partir de ese instante para github es como si el proyecto fuera tuyo, por lo que
podrás hacer commits sin tener que pedir permiso a nadie.
La idea, claro, es aprovechar todo el código y por supuesto añadir cosas que ne-
cesitarás para tu propio proyecto. Por ejemplo, si queremos cambiar el aspecto
de la página de inicio o añadir una nueva funcionalidad como una tabla de au-
tores para los artículos solo es cuestión de hacerlo. Tampoco es obligatorio que
hagas commits si no quieres, ten en cuenta que el proyecto, aunque hayas hecho
un fork y esté en tu cuenta, será público, con lo cual todo el mundo verá lo que
añadas; no sé si esto puede ser un problema en tu caso porque a lo mejor estás
desarrollando para un cliente y no vas a ceder el código al público en general. Si
fuera así no hagas commits del proyecto en esa cuenta de github, a no ser que
tengas una cuenta profesional y puedas establecer como privado el proyecto.
Bueno, sea como sea, el caso es que la facilidad que nos proporciona github
para ver proyectos de otros compañeros, seguirlos (watch), y hacer forks nos
proporciona una increíble ayuda para poder estudiar código de otros programa-
dores, y sobre todo utilizar lo que otros tan amablemente han puesto a dispo-
sición del mundo.
Pull request
Si al final decides subirte al carro de la programación libre, quien sabe, quizás
solo quieras aprender y de paso compartir. Una de las cosas más maravillosas
222
15. Aplicación de ejemplo
es cuando tu fork avanza por encima del proyecto principal y le pides al propie-
tario del proyecto original que incluya parte de tu código. Esta acción tiene un
nombre y se llama pull request.
Valida la petición comprobando lo que vas a pedir (los cambios del código apa-
recen en otro color) y pulsando sobre el botón Create pull request:
223
Programación PHP profesional con Slim, Paris y Twig
Como confirmación de lo que has hecho, github nos presentará esta pantalla:
Lo normal es que el propietario del proyecto al que intentas hacer el pull re-
quest entable una conversación contigo sobre las mejoras que pretendes inco-
porar o por qué son necesarias. Evidentemente no voy a reproducir aquí toda
una conversación real, pero te puedes hacer una idea de por dónde van los
tiros viendo esta captura:
224
15. Aplicación de ejemplo
Una vez el propietario acepta el pull request esto es lo que veríamos por fin:
225
Programación PHP profesional con Slim, Paris y Twig
Y con esto queda visto de manera pormenorizada cómo realizar una petición a
un proyecto principal de modificaciones que hemos llevado a cabo en nuestra
copia (fork) del proyecto.
Conclusión
Aunque es fácil que no uses esta opción nunca, es bueno conocer los mecanis-
mos que se nos proporcionan a nivel colaborativo.
Espero haberte transmitido bien la filosofía de trabajo para los proyectos que
decidas compartir con el resto del mundo mediante github.
En todo caso, si necesitas ampliar más información cuenta con los foros y con
la propia documentación de github.
226
Parte IV: Para subir nota
16 TDD
Contenido
» Presentación
» ¿En qué consiste el TDD?
» ¿Para qué sirven los test?
» Ejemplo práctico
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Presentación
Este capítulo pretende ser solamente una introducción y un punto de partida
hacia la filosofía TDD (Test Development Driven), de tal manera que pretendo
sembrar en ti la duda razonable de si hay otra manera de hacer las cosas que
la simple programación, con los acostumbrados ciclos de prueba y error.
1
Test driven development.
2
Es una lástima que el libro sea en blanco y negro, porque precisamente TDD muestra
los errores en rojo y los test válidos en verdes. Usaremos no obstante esta analogía
durante todo el capítulo porque tú si verás los resultados en tu pantalla de esa manera.
230
16. TDD
En realidad no hay que esperar a ejecutar los test al llegar al final del libro, pero
al no tratar este de TDD en exclusiva no lo he abordado hasta ahora para no
hacer que el lector huya despavorido. Nuevos conceptos y todos a la vez pue-
den ocasionar este resultado.
Bien, como te decía, TDD es una filosofía de trabajo que consiste básicamen-
te en desarrollar primero el test que va a hacer que una funcionalidad sea
válida o no, este test ha de contemplar todos los casos que puede presentar
la entrada y validar la misma. Una vez se tienen todos los casos contempla-
dos, se realiza el método mínimo que hace que su salida pase el test, sin
muchas florituras. Es más, es recomedable hacerlo más bien a lo bestia, sin
patrones de diseño, sin cuidar mucho los detalles: si ponemos muchos if o
si cabe un case, el caso es que el método implementado pase todos los test
en verde. Una vez conseguido esto vamos refactorizando el código, simple-
mente empleando métodos de programación que permitan reducir el uso de
variables, el uso de if, no repitiendo código, implementando el código repe-
tido en métodos separados, etc. Pero en cada iteración, en cada cambio,
volvemos a correr los test, tienen que salir siempre en verde, si de un paso al
otro se nos cambia a rojo, tenemos que revisar lo que hemos hecho en ese
paso y arreglarlo para volver a conseguir el verde. Y esto básicamente es el
TDD, por supuesto de una manera muy somera, como te decía solo quiero
rascar y motivarte a interesarte e indagar más sobre este tema, existen libros
muy buenos acerca de este tema. El que te pongo a continuación no es que
lo recomiende explícitamente pero además de ser gratuito está escrito en-
teramente en castellano, no habla específicamente de PHP pero puede ser
un buen punto de partida. El libro se llama Diseño Ágil por TDD y lo puedes
descargar de aquí http://www.dirigidoportests.com/el-libro.
231
Programación PHP profesional con Slim, Paris y Twig
ocasiones necesita ayuda sobre ciertas cuestiones, como puede ser un boots-
trap que cargue las clases, configuración, etc., en el proyecto my-simple-web
encontrarás en la raíz un pequeño archivo bootstrap.php3 y un phpunit.xml que
te reproduzco a continuación:
phpunit.xml
<phpunit backupGlobals="true"
backupStaticAttributes="false"
bootstrap="bootstrap.php"
cacheTokens="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
forceCoversAnnotation="false"
mapTestClassNameToCoveredClassName="false"
printerClass="PHPUnit_TextUI_ResultPrinter"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader"
strict="false"
verbose="false">
</phpunit>
Y bootstrap.php
<?php
Veámos ahora un ejemplo: en la carpeta lib existen unas cuantas funciones de uti-
lidad que permiten realizar tareas como comprobar un email, generar un slug, etc.
Vamos a ver cómo sería la clase que verificaría que estas funciones producen
el resultado esperado:
<?php
namespace lib;
3
No confundas con el bootstrap de nuestra aplicación, este de ahora es un cargador
que permite inicializar lo necesario para poder correr los test. El concepto es el mis-
mo pero el archivo es diferente.
232
16. TDD
use lib\MyFunctions;
use \PHPUnit_Framework_TestCase;
/**
* Check_email tests
*/
public function testCheckEmail()
{
// check_mail assertions
$this->assertTrue( MyFunctions::check_email('email@email.com') );
// email that is only word
$this->assertFalse( MyFunctions::check_email('emailinvalid') );
// email that hasn’t domain termination
$this->assertFalse( MyFunctions::check_email('email@invalid') );
}
233
Programación PHP profesional con Slim, Paris y Twig
$test = "entity_table_name";
$this->assertTrue("Entity\\TableName" ==
MyFunctions::underscoredToCamelCaseEntityName($test)
);
}
Ejemplo práctico
Ahora vamos a hacer un ejemplo práctico desde cero, vamos a pensar en una
funcionalidad que queramos implementar y haremos el test que la cubra. E
iremos poco a poco aumentando requerimientos.
234
16. TDD
Para que puedas seguir el código y las diferentes iteraciones voy a numerar
tanto la función como su test, lo que vamos a hacer es empezar a cumplir es-
pecificaciones una a una.
Empecemos:
<?php
class TddSample
{
<?php
Corremos el test:
235
Programación PHP profesional con Slim, Paris y Twig
1) TddSampleTest::testPruebaTdd1
Failed asserting that null is not equal to null.
~/mysimpleweb.com.dev/tddSample/tests/TddSampleTest.php:12
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
Ahora vamos a variar el método para devolver un valor y veremos que si pasa,
para mantener todos los métodos juntos en la misma clase, los voy a numerar,
como te he dicho antes.
<?php
class TddSample
{
// ..
El resultado es:
OK (1 test, 1 assertion)
236
16. TDD
Ahora se trata de iterar hasta conseguir el resultado final, puede que en algún
momento te parezca que hacemos trampa, pero recuerda la premisa de que
no es necesario implementar nada más que lo que se nos pide, no hay que
inventar ni hacer filigranas.
Vamos a hacer primero el test que queremos cumplir, que va a ser que el valor
devuelto sea una cadena de tamaño 6 y que esté compuesta por 6 dígitos.
<?php
//..
class TddSampleTest extends PHPUnit_Framework_TestCase
{
//..
public function testPruebaTdd3()
{
$object = new TddSample();
$result = “” . $object->pruebaTdd3();
$this->assertNotEquals(null, $result);
$this->assertEquals(6, strlen($result));
$this->assertTrue((bool)preg_match(‘/\d{6}/’,$result));
}
}
Sin duda alguna te llamarán la atención algunos typecast que he tenido que añadir,
para estar seguro de que el resultado se evalúa como una cadena no hay nada
como concatenar la cadena vacía con el resultado: "" . $object->pruebaTdd3(),
y luego el resultado de preg_match no es un booleano estricto ya que devuelve
literalmente 0 o 1, y por eso (bool) lo transforma, assertTrue hace una compro-
bación estricta y no nos la pasa sin ese «truco».
OK (1 test, 3 assertions)
Como ves nuestro pequeño truco de devolver una cadena con seis unos ha fun-
cionado, ten en cuenta que para pasar este test no es necesario hacer nada más.
237
Programación PHP profesional con Slim, Paris y Twig
return false;
}
1) TddSampleTest::testPruebaTdd4
238
16. TDD
~/mysimpleweb.com.dev/tddSample/tests/TddSampleTest.php:51
FAILURES!
Tests: 1, Assertions: 4, Failures: 1.
<?php
class TddSample
{
//..
public function pruebaTdd4()
{
$digits = array(0,1,2,3,4,5,6,7,8);
shuffl e($digits);
return implode("",array_slice($digits,0,6));
}
OK (1 test, 4 assertions)
239
Programación PHP profesional con Slim, Paris y Twig
Una única cuestión antes de terminar este capítulo, y es que en el código verás
todas las funciones que hemos ido viendo en la misma clase y numeradas, así
como los métodos de test, están así para poder ilustrar este capítulo, evidente-
mente esto en un entorno real se hace sobre el mismo método. Ten en cuenta
que para poder ilustrar esa evolución y que la tengas a tu disposición en el
código de My-simple-web he mantenido ese imaginario hilo de tiempo.
Conclusión
No te asustes porque haya incluido este capítulo en la parte que dramáticamen-
te me he atrevido a llamar PARA SUBIR NOTA. No se trata de que aquí solo
van a llegar los de intelecto superior, ni nada parecido. La idea es que asimiles
todo lo expuesto anteriormente, y como remate o guinda de este maravilloso
pastel que hemos montado, tenemos el tema del TDD. Ten en cuenta que en la
práctica no es una cosa que se hace al final del desarrollo, sino de manera to-
talmente paralela al mismo. Es más, los tests están vivos junto con el desarrollo
del software. Permiten entre otras cosas asegurar la calidad del software que
subimos a producción. Si al final te decides a usarlos en tus desarrollos estarás
ya en un nivel Ninja ;<)
240
17 Caso práctico
Contenido
» Presentación
» E-commerce básico
» Manos a la obra
» El reto
» Conclusión
Programación PHP profesional con Slim, Paris y Twig
Presentación
Para que veas de que manera la aplicación My-simple-web te puede ser útil
en tus desarrollos vamos a extenderla para crear un e-commerce muy básico.
Bien, te voy a sacar de dudas, vamos a vender dominios, sí, nombres de do-
minios.
Además, para hacerlo más interesante, y sobre todo para saber si el libro te ha
resultado útil, te voy a proponer un reto: al final de este capítulo encontrarás
más información.
1
http://es.wikipedia.org/wiki/Drop_shipment
242
17. Caso práctico
E-commerce básico
Para documentar este capítulo y no montar un sistema completo de tienda
electrónica ajeno por completo al ámbito de este libro vamos a pensar en la
venta de un solo producto. Debe ser algo que podamos conseguir mediante
una API, por ejemplo algún libro de Amazon, algo de dropshipping o registro de
dominios a través de un registrador de dominios autorizado.
1) Montar una pequeña API que nos permita saber si un artículo está dispo-
nible,
2) Comprarlo.
Para centrar las ideas en este punto vamos a tomar el ejemplo de registro de
dominios.
Una de las API con las que he trabajado, he de decir que con resultado muy
satisfactorio, debido sobre todo a la madurez de la documentación y a que
admite un modo sandbox (no se realiza la compra de manera real), es la de
la multinacional OVH. No sé si en tu vida profesional vas a realizar este tipo
de ventas o integraciones en los carritos de la compra que montes, pero
además, este, por tratarse de un producto que no es físico, no necesita ser
enviado de manera material, con lo cual la última parte (y en su caso enviarlo
si corresponde) verás que consiste básicamente en enviar un correo electró-
nico al cliente.
Hay otra cuestión que en nuestro caso nos puede ayudar, de alguna manera,
a valorar este como un servicio real aplicable a otros sectores, y se trata de
que la compra efectiva no se materializa de manera inmediata. El proveedor
encola la petición, procesándola en segundo plano, y no tiene listo el dominio
hasta que no han pasado algunos minutos. Esta demora, perfectamente puede
243
Programación PHP profesional con Slim, Paris y Twig
Sea como sea y teniendo claro que es imposible abarcar todos y cada uno de
los productos que son susceptibles de ser vendidos de manera electrónica, si
te parece oportuno tomaremos este ejemplo como base.
Otra cuestión bien diferente es que al final decidas montar una tienda online
para un cliente o para ti mismo y los productos que estés ofreciendo sean con-
trolados directamente por ti sin necesidad de una API para ello. En este caso,
bastante más complejo, hay que tener en cuenta una serie de cuestiones que
complican en exceso este ejemplo, pero te voy a enumerar algunas para que
las tengas en mente:
Si sumas todas estas características habrás desarrollado desde cero una apli-
cación web al estilo de las más grandes, como puedan ser Prestashop o Ma-
gento, lo que no digo que no se pueda hacer, pero en la mayoría de los casos
no es conveniente reinventar la rueda.
2
Los cronjobs son tareas que se le indican al sistema operativo para que realice de
manera repetitiva a intervalos de tiempo establecidos.
3
El dropshipping permite conocer la disponibilidad (existencias diarías de los artícu-
los) pero no garantiza que a lo largo del día las existencias no mengüen.
244
17. Caso práctico
al final, con las distintas evoluciones, terminas gestionando una tienda com-
pleta.
Centremos conceptos
Volviendo a nuestro ejemplo quizás lo encuentres triste y pobre al lado de todos
los puntos que te he enumerado, ya que en nuestro caso:
3) Haremos un cron que verifique que el dominio está disponible una vez
registrada la compra para de esa manera hacer dos cosas: a) informar
al usuario de que ya tiene disponible el dominio; y b) hacer los ajustes
oportunos en las DNS.
4) Por eso en la zona de usuario además de listar los dominios que tiene
comprados el usuario y cuándo caducan por si los quiere renovar, centra-
lizaremos la gestión de los DNS. Ya ves que algo que parecía en principio
tan sencillo se va complicando poco a poco. Imagínate si habláramos de
más productos y con la posibilidad de descripciones, tallas, colores, dife-
rentes imágenes y/o vídeos.
Manos a la obra
Vamos a empezar
Lo primero que necesitamos, además de la API es establecer las entidades que
vamos a necesitar para realizar todo con éxito.
245
Programación PHP profesional con Slim, Paris y Twig
246
17. Caso práctico
Internamente se dará de alta un registro con los datos del usuario, se creará
una contraseña aleatoria, se enviará un correo dándole la bienvenida e indican-
do esa contraseña para que pueda acceder al área de usuario y se creará un
registro en la tabla de dominios, que se asociará con ese usuario.
Habrá que implementar un cron cada 30 minutos4 para que verifique la validez
del registro mediante una llamada al registrador de dominios. Necesitaremos
también un cron diario para por un lado verificar la validez de los registros DNS
y por otro comprobar la caducidad de los dominios para avisar con tiempo al
usuario de que su dominio necesita ser renovado.
Area de usuario
En esta zona el usuario podrá:
» Acceder a la zona de registro de nuevos dominios.
» Pagar por adelantado un dominio que está a punto de caducar o no.
» Cambiar los DNS de su dominio de una manera muy básica (registros
A y CNAME).
Vamos a crear ahora una clase que nos encapsule los métodos del proveedor
de registro que vamos a utilizar, de tal manera que podamos tener una interfaz
4
30 minutos es un tiempo adecuado según mi experiencia, pero puede ser cualquier
otro que consideres.
247
Programación PHP profesional con Slim, Paris y Twig
uniforme para ello. Dejaremos todos los intríngulis de la conexión con la API
del tercero a esa clase.
Siguiendo los principios que hemos ido enumerando a lo largo del texto vamos
a hacer que este bloque de código sea totalmente autónomo de My-simple-
web, para lo cual crearemos un módulo separado. Creo que esto te puede ser
de ayuda así que te voy a enumerar los pasos que he seguido. No son para que
los repitas tú en este caso, sino para que los tengas de guía en el caso de que
necesites hacer algo parecido.
{
"name": "jlaso/ovh-domain-api",
"version": "1.0.0",
"description": "Client API for ovh.com domain functions",
"keywords": ["api", "ovh", "domains"],
"homepage": "https://github.com/jlaso/ovh-domain-api.git",
"type": "module",
"license": "mit",
"authors": [
{
"name": "Joseluis Laso",
"email": "jlaso@joseluislaso.es",
"homepage": "http://www.joseluislaso.es"
}
],
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-0": { "JLaso\\OvhDomainApi": "" }
},
"target-dir": "JLaso/OvhDomainApi"
}
248
17. Caso práctico
Los puntos más importantes, sin duda, son los que indican cómo se va a llamar
el paquete (name), dónde está ubicado (homepage), y dónde se va a ubicar al
desplegarlo (target-dir).
Para crear ese composer.json necesitamos saber qué poner en homepage, por lo
que de manera paralela habremos creado el repositorio correspondiente en github.
Por tanto desde un terminal y estando situados en la carpeta más interna (re-
cuerda que hemos comentado que solo vamos a trabajar en esa), ejecutare-
mos las siguientes instrucciones:
git init
git remote add origin jlaso@github.com/ovh-domain-api.git
A partir de aquí todos los avances que se produzcan en el código local se pue-
den ir subiendo al repositorio central.
Nos queda ahora crear ese paquete en packagist.org para que la vinculación
sea total, y de esa manera, en el proyecto principal, sea suficiente con incluir la
siguiente línea en el composer.json
"require": {
...
"jlaso/ovh-domain-api": "*",
...
},
Quiero recordarte aquí, tal y como vimos en el capítulo de Composer que este
último paso no es obligatorio y que podemos utilizar los repositorios VCS. Pero
como te he comentado al principio de esta sección quiero que veas cómo ha-
cerlo todo de arriba abajo y luego, en función de tus necesidades reales, pue-
das discernir qué camino es el mejor para tu caso concreto.
249
Programación PHP profesional con Slim, Paris y Twig
Nos queda ahora escribir el código de la API que nos va a permitir crear esa
capa de abstracción y utilizar instrucciones sencillas para realizar las tareas
básicas que vamos a llevar a cabo:
» Comprobar si un dominio está libre.
» Registrar un dominio.
Así, el contenido de esa API será el siguiente:
<?php
namespace JLaso\OvhDomainApi\Service;
class OvhApi
{
const UNKNOWN = 0; // Can not retrieve availability of domain
const AVAILABLE = 1; // Domain is available
const NOT_AVAILABLE = - 1; // Domain is not available
/**
* @param $user
* @param $password
* @param bool $sandBoxMode
* @param string $language
*/
function __construct($user, $password, $sandBoxMode = true, $language = 'es')
{
$this->sandBoxMode = $sandBoxMode;
$this->language = $language;
$this->password = $password;
$this->username = $user;
250
17. Caso práctico
$this->accessData = array(
'hosting' => 'none',
'offer' => 'gold',
'profile' => 'whiteLabel',
'owo' => 'no',
'owner' => $user,
'admin' => $user,
'tech' => $user,
'billing' => $user,
/**
* @param string $domain
*
* @return int
*/
public function isAvailable($domain)
{
$domain = strtolower(trim($domain));
$this->login();
$results = $this->request(‘domainCheck’, array($domain));
foreach($results as $result){
if($result->predicate == 'is_available'){
if($result->value){
return self::AVAILABLE;
}else{
return self::NOT_AVAILABLE;
}
}
}
251
Programación PHP profesional con Slim, Paris y Twig
return self::UNKNOWN;
}
/**
* @param OwnerDomain $ownerData
*/
public function createOwnerId(OwnerDomain $ownerData)
{
$result = $this->request('nicCreate', $ownerData->asArray());
var_dump($result); die;
}
/**
* @param string $domain
* @param string $ownerId
*
* @return bool
*/
public function registerDomain($domain, $ownerId)
{
if ($this->isAvailable($domain)) {
$this->request(
'resellerDomainCreate',
array($domain, 'owner' => $ownerId)
);
return true;
}
return false;
}
/**
* PROTECTED METHODS
*/
/**
* logout in ovh
*/
protected function login()
{
$this->soapClient = new \SoapClient(self::WSDL_URL);
$this->session = $this->soapClient->login(
$this->username,
$this->password,
$this->language,
false
);
}
252
17. Caso práctico
/**
* logout in ovh
*/
protected function logout()
{
if($this->soapClient && $this->session){
$this->soapClient->logout($this->session);
$this->session = null;
}
}
/**
* @param $method
* @param array $param
* @param bool $catchException
*
* @return bool|mixed
* @throws \Exception
* @throws \SoapFault
*/
protected function request($method, $param = array(), $catchException = true)
{
if(!$this->session){
$this->login();
}
$this->lastException = null;
try{
if($catchException){
$this->lastException = $e;
return false;
}else{
throw $e;
}
}
return $response;
}
253
Programación PHP profesional con Slim, Paris y Twig
function __construct()
{
$this->api = new OvhApi(OVH_USER, OVH_PASS, self::SANDBOX_MODE, ‘es’);
}
254
17. Caso práctico
$this->assertTrue($this->api->registerDomain($domain, $owner));
}
# personal configuration
Tests/config.php
<?php
define("OVH_USER", "xxxxx-ovh");
define("OVH_PASS", "123456");
Te preguntarás que de dónde obtener entonces los datos que hay que poner en
lugar de los que hay de muestra.
Para que la API funcione no es necesario que recargues crédito, pero sí que
aceptes los contratos oportunos.
También vas a necesitar una cuenta en PayPal para poder hacer las transac-
ciones con tus futuros clientes.
Recuerda que en PayPal una vez realizados todos los pasos debes activar tu
cuenta como vendedor, esto no es un proceso inmediato, tenlo en cuenta de
cara a saber cuándo vas a poder tener tu tienda en producción al 100%.
255
Programación PHP profesional con Slim, Paris y Twig
</form>
El reto
Te propongo terminar lo que hemos empezado a describir en este capítulo, con
las siguientes condiciones:
5
https://developer.paypal.com
256
17. Caso práctico
Cuando pongas datos personales, como datos de acceso a cualquier API uti-
liza un archivo que no esté seguido por git (su patrón estará incluido en el
archivo .gitignore) y en su lugar (además de) pon el mismo archivo indicando
que es una muestra o para distribución, en el caso de My-simple-web habrás
visto que existe un archivo app/config/dbconfig_sample.php. Este archivo es la
base para que solo copiando y renombrando a dbconfig.php, y evidentemente
cambiando el contenido por datos reales en cada caso, sea suficiente para que
la aplicación funcione.
Todos los proyectos que me lleguen en los que se haya resuelto correctamente
el ejercicio propuesto de compra de dominios, gestión del área del usuario con
posibilidad de renovaciones, cambio de DNS, etc., y todo lo que se les ocurra
añadir o incluso cualquier solución de tienda electrónica basada en la estructu-
ra de My-simple-web, serán mencionados en próximas revisiones de este libro
si el autor me permite expresamente mencionar su nombre.
257
Programación PHP profesional con Slim, Paris y Twig
Para poder llevar a cabo de manera material el proyecto vas a necesitar tener
una cuenta en PayPal y otra en OVH, en ambos la creación de una cuenta es
gratuita. Para el caso de Paypal tendrás que asociar una tarjeta de crédito y
luego tendrás que habilitar tu cuenta para poder vender con ella. En el caso de
OVH las compras son siempre contra un saldo prepago6, con lo cual y para em-
pezar a probar puedes hacer las compras en modo sandbox7 y posteriormente
pasar a producción, si no tuvieras saldo en la cuenta el proveedor te va avisar
de ello mediante un correo electrónico permitiendo finalizar el pedido del domi-
nio en cuestión y no perder la venta.
Conclusión
Como colofón quiero aprovechar para motivarte y hacer que muevas un poco
los dedos tecleando, así como de paso recibir feedback por tu parte. Todo es-
critor espera que le lean, y qué mejor manera de comprobarlo viendo que sus
lectores ponen en práctica las explicaciones que se han dado en el texto.
Para no dejar a nadie de lado, aquellas personas que tengan interés no hace
falta que terminen el proyecto para poder avanzar: si es tu caso y te quedas
atascado en algún punto puedes contactarme en la misma dirección de e-mail
(jlaso@joseluislaso.es). Estaré encantado de orientarte dentro de mis posibili-
dades. Ten en cuenta que el proceso de aprendizaje es eso: un proceso.
6
Esto es así a la fecha de cierre de edición, en todo caso verifica esta información en
http://www.ovh.com
7
OVH lo llama DryRun.
258
Índices
Ejemplos de código
Ejemplo 1: extracto de inicialización del bootstrap (index.php) .......................... 12
Ejemplo 49: app models core Form Form idget::form table head ............153
Ejemplo 50: app models core Form Form idget::form table row ..............155