Algunos ejemplos sobre el ejercicio empresa

Poner una validación personalizada para el NIF:

 'nif' => array(
 'notBlank' => array(
 'rule' => array('notBlank'),
 'message' => 'El nif no puede estar en blanco',
 ),
 'alfa' => array(
 'rule' => array('custom', '/^(X(-|\.)?0?\d{7}(-|\.)?[A-Z]|[A-Z](-|\.)?\d{7}(-|\.)?[0-9A-Z]|\d{8}(-|\.)?[A-Z])$/i'),
 'message' => 'Debe ser un nif válido',
 ),
 )

Los productos no se borran, sólo pasan a ser inactivos. En el delete no borramos el producto, solo pasamos el campo activo a 0:

 public function delete($id = null) {
 $this->Producto->id = $id;

 if (!$this->Producto->exists()) {
 throw new NotFoundException(__('Invalid producto'));
 }
 $this->request->allowMethod('post', 'delete');
 if ($this->Producto->saveField('activo', 0)) {
 $this->Flash->success(__('The producto has been deleted.'));
 } else {
 $this->Flash->error(__('The producto could not be deleted. Please, try again.'));
 }
 return $this->redirect(array('action' => 'index'));
 }

En el beforefind del modelo productos ponemos como condición que activo sea 1:

 public function beforeFind($query) {
 $query['conditions']['activo'] = 1;
 return $query;
 }

En la base de datos ponemos como valor por defecto de activo ‘1’ y eliminamos de cualquier vista la referencia al campo activo.

En la acción upload del controlador Productos compruebo que lo que me suben es una imagen:

 $archivo = $this->request->data['Producto']['archivo'];
 if (explode('/', $archivo['type'])[0] == 'image') {
 move_uploaded_file($archivo['tmp_name'], WWW_ROOT . 'img' . DS . $this->request->data['Producto']['referencia'] . DS . $archivo['name']);
 $this->Flash->success(__('Archivo subido.'));
 } else {
 $this->Flash->error(__('Solo se permiten imágenes.'));
 }

Para ver las imágenes que hemos subido dentro de la vista de producto. En el controlador pasamos las imágenes encontradas:

 public function view($id = null) {

 if (!$this->Producto->exists($id)) {
 throw new NotFoundException(__('Invalid producto'));
 }
 $options = array('conditions' => array('Producto.' . $this->Producto->primaryKey => $id));
 $producto = $this->Producto->find('first', $options);
 $this->set('producto', $producto);
 $dir = new Folder(WWW_ROOT . "img/" . $producto['Producto']['referencia']."/");
 $this->set('files', $dir->find(".*"));
 }

En la vista (view.ctp) añadimos las líneas que nos mostrarán las imágenes:

 <?php
 foreach ($files as $file):
 $this->Html->image($producto['Producto']['referencia']."/".$file);
 endforeach;
 ?>

Ejemplo componentes

class UtilidadesComponent extends Component {

 public $components = array();

 function areaCirculo($radio) {
 return pi() * $radio * $radio;
 }

 function Circulo($radio) {
 return 2 * pi() * $radio;
 }

 public function frase() {
 $spam = array('Muy bueno', 'Estupenda entrada', 'Eres un crack', 'Lo mejor que he leído', 'Ole y ole', 'Me ha gustado', 'Bastante bueno', 'Muy interesante', 'De gran utilidad', 'Está bastante bien', 'Me ha hecho pensar');
 $com = $spam[rand(0, count($spam) - 1)];
 return $com;
 }

 function importeConIva($cantidad) {
 return $cantidad * 1.21;
 }

}

Funciones globales

Éstas son las funciones globales disponibles en CakePHP. Muchas de ellas simplemente facilitan la llamada a funciones de PHP con nombres largos, pero otras (como vendor() y uses()) se pueden usar para incluir código o realizar otras funciones útiles. Lo más probable es que si estás buscando una función para realizar una tarea con mucha frecuencia, la encuentres aquí.

__

__(string $string_id, boolean $return =  false)

Esta función gestiona la localización en las aplicaciones CakePHP. El parámetro $string_id identifica la ID de una traducción, mientras que el segundo parámetro indica si se debe mostrar automáticamente la cadena (por defecto), o devolverla para su procesamiento (pasar el valor true para que esto suceda).

Visita la sección Localización e Internacionalización para más información.

am

am(array $uno, $dos, $tres...)

Combina todos los arrays pasados como parámetros y devuelve el array resultante.

config

Puede ser usado para cargar archivos desde el directorio config mediante include_once. La función checa si existe el archivo antes de incluir y regresa un booleano. Toma un número opcional de argumento.

Ejemplo: config('some_file', 'myconfig');

convertSlash

convertSlash(string $cadena)

Sustituye las barras (“/”) por subrayados (“_”) y elimina el primer y el último subrayados en una cadena. Devuelve la cadena convertida.

debug

debug(mixed $var, boolean $showHtml = false)

Si el nivel de depuración, variable de configuración DEBUG, es distinto de cero, se muestra $var. Si $showHTML es true, los datos se formatean para mostrarlos adecuadamente en los navegadores web.

env

env(string $key)

Obtiene una variable de entorno a partir de las fuentes disponibles. Alternativa si las variables $_SERVER o $_ENV están deshabilitadas.

También permite emular las variables PHP_SELF y DOCUMENT_ROOT en los servidores que no permitan su uso. De hecho, es una buena práctica usar env() en lugar de $_SERVER o getenv() (sobretodo si pensamos distribuir el código), ya que ofrece la misma funcionalidad y es totalmente compatible.

fileExistsInPath

fileExistsInPath(string $archivo)

Comprueba que el fichero $archivo está en el include_path actual de PHP. Devuelve un valor booleano.

h

h(string $texto, string $charset)

Alias de la función htmlspecialchars().

pr

pr(mixed $var)

Alias de la función print_r(), añadiendo la etiqueta <pre> a la salida.

stripslashes_deep

stripslashes_deep(array $valor)

Elimina recursivamente las barras invertidas de $valor. Devuelve el array modificado.

Traducción en cakePHP

CakePHP usa los archivos ‘po’ para manejar las traducciones. Para poder realizar las traducciones es conveniente usar un editor:

https://poedit.net/download

Lo primero que debemos hacer es obtener las cadenas de texto susceptibles de ser traducidas. Nos colocamos en la carpeta ‘app’ de nuestra aplicación y ejecutamos lo siguiente:

Console/cake i18n extract

Nos hace una serie de preguntas a las que podemos contestar con ‘intro’ escogiendo siempre la opción por defecto. En Locale nos va a generar varios archivos ‘.pot’ con las cadenas de las traducciones. A nosotros nos interesa ‘default.pot’.

En el poedit generamos un archivo nuevo, le llamaremos ‘default.po’. Añadimos las cadenas desde el default.pot y las traducimos. El archivo resultante lo podemos guardar en:

\app\Locale\eng\LC_MESSAGES\default.po

O bien en la carpeta local correspondiente, por ejemplo:

/app/Locale/esp/LC_MESSAGES/default.po

Para indicarle que estamos usando otra traducción debemos indicarlo con la siguiente línea en \app\Config\core.php:

Configure::write('Config.language', 'esp');

Vistas: Layouts, elements y helpers

Para construir vistas tenemos una serie de elementos:

  • *layouts* (diseños): ficheros de vista que contienen el código de presentación que se renderiza cuando mostramos una vista. Los ficheros de diseño deberían situarse en /app/views/layouts. El diseño por defecto de CakePHP puede ser sustituido creando un nuevo diseño por defecto en /app/views/layouts/default.ctp.
  • *elements* (elementos): trozo de código de vista más pequeño y reutilizable. Los elementos generalmente son renderizados dentro de vistas.Los elementos están en la carpeta /app/views/elements/ y tienen la extensión de archivo .ctp. Son mostrados usando el método element() de la vista.
    <?php echo $this->element('cajaayuda'); ?>
    
  • *helpers* (ayudantes): estas clases encapsulan lógica de vista que es necesaria en muchas partes en la capa vista. Se usan, por ejemplo, para construir formularios o elementos comunes.

Ejercicio repaso

Creamos la base de datos ‘empresa’ con las siguientes tablas:

CREATE TABLE `empresa`.`proveedores` (
 `id` INT NOT NULL AUTO_INCREMENT,
 `razon` VARCHAR(45) NULL,
 `nif` VARCHAR(15) NULL,
 `fecha_alta` DATE NULL,
 `credito` DECIMAL(6,2) NULL,
 PRIMARY KEY (`id`));

CREATE TABLE `empresa`.`productos` (
 `id` INT NOT NULL AUTO_INCREMENT,
 `referencia` VARCHAR(45) NULL,
 `proveedor_id` INT NULL,
 `precio` DECIMAL(6,2) NULL,
 `activo` TINYINT NULL,
 PRIMARY KEY (`id`));

Queremos hacer mantenimiento en cakePHP.

Ningún campo puede tener un valor nulo.

Validaciones: crédito y precio, decimales. Referencia y nif, alfanuméricos. Referencia es, además, única.

La fecha tiene que validarse y tener un datepicker para escogerla.

El producto puede tener imágenes. Estas se añadirán en una vista aparte, guardándose dentro de la carpeta img de la raiz con una subcarpeta con la referencia del producto.

En la vista del producto (view) se deberán ver las imágenes si existen.

El campo ‘activo’ nunca será visible desde el mantenimiento en cakephp. Con este parámetro haremos otra cosa, pero más adelante.

Parámetros en cakePHP

Vimos que el formato por defecto de enrutamiento es:

http://example.com/controller/action/param1/param2/param3

Por ejemplo, si yo accedo a:

http://localhost/cakeblog/autors/index/ola/k/ase

Dentro de la función index del controlador de autores tengo la variable $this->passedArgs que vale lo siguiente:

Array
(
[0] => ola
[1] => k
[2] => ase
)

Si yo paso parámetros con nombre el array es asociativo con el nombre del parámetro como clave. Ejemplo

http://localhost/cakeblog/autors/index/ola:5/k:7/ase:patata

Me da lo siguiente:

Array
(
[ola] => 5
[k] => 7
[ase] => patata
)

 

Ejemplos callback

//Antes de guardar en la base de datos convierte la fecha de formato y al mail añade 'mailto:'
 public function beforeSave($options = array()) {
 if (isset($this->data['Autor']['fecha'])) {
 $this->data['Autor']['fecha'] = date("Y-m-d", strtotime($this->data['Autor']['fecha']));
 }
 if (isset($this->data['Autor']['mail'])) {
 $this->data['Autor']['mail'] = 'mailto:' . $this->data['Autor']['mail'];
 }
 }

//Después de recuperar los datos de la base de datos convierte la fecha de mysql a d/m/y y quita el 'mailto:' de los mails
 public function afterFind($results, $primary = false) {

 foreach ($results as $key => $val) {
 if (isset($results[$key]['Autor']['fecha'])) {
 $results[$key]['Autor']['fecha'] = date("d-m-Y", strtotime($results[$key]['Autor']['fecha']));
 }
 if (isset($results[$key]['Autor']['mail'])) {
 $results[$key]['Autor']['mail'] = str_replace('mailto:', '', $results[$key]['Autor']['mail']);
 }
 }
 return $results;
 }

//Filtra todos los resultados que no tengan el mail 'aaa.com'
 public function beforeFind($query) {
 $query['conditions']['Autor.mail LIKE'] = "%@aaa.com%";

 return $query;
 }

//Cuando añadimos un autor nuevo crea una carpeta con su id
 public function afterSave($created, $options = array()) {

 if ($created) {
 $dir = new Folder(WWW_ROOT . 'autores/' . $this->data['Autor']['id'], true);
 }
 }

//Impedimos que se borre el autor con id 3
 public function beforeDelete($cascade = true) {
 
 if ($this->id == 3) {
 return false;
 } else {
 return true;
 }
 }

//Cuando borramos un autor eliminamos su carpeta
 public function afterDelete(){
 $folder = new Folder();
 $folder->delete(WWW_ROOT . 'autores/' . $this->id);
 }

 

Paginación en cakePHP: Paginator (componente en controladores)

Para poder ver todos los registros de nuestra base de datos con paginación y posibilidad de ordenar, cakePHP nos ofrece el componente ‘Paginator’. Para usarlo basta con indicarlo en el controlador:

 public $components = array('Paginator');

Y podemos configurar diferentes opciones:

 public $paginate = array(
        'fields' => array('Post.id', 'Post.created'),
        'limit' => 25,
        'order' => array(
            'Post.title' => 'asc'
        )
    );

Para usarlas lo tendremos que indicar en la acción del controlador:

 public function index() {
 $this->Actor->recursive = 0;
 $this->Paginator->settings = $this->paginate;
 $this->set('actors', $this->Paginator->paginate( );
 }

En el index habitualmente ponemos la opción ‘recursive’ a 0 para que no nos devuelva los registros relacionados. Si queremos usar las opciones modificadas del paginador lo indicamos en settings. Y para obtener los registros basta con llamar a la función ‘paginate’.

También podemos pasar condiciones a la hora de paginar:

 public function index() {
 $this->Actor->recursive = 0;
 $this->Paginator->settings = $this->paginate;
 $this->set('actors', $this->Paginator->paginate( array('first_name LIKE' => 'a%')));
 }

En este caso nos muestra los actores cuyo nombre empieza por ‘a’.