Autenticación cakePHP 4

La vista en Usuarios:

<div class="usuarios form">
<?php echo $this->Session->flash('auth'); ?>
<?php echo $this->Form->create('Usuario'); ?>
 <fieldset>
 <legend>
 <?php echo __('Please enter your username and password'); ?>
 </legend>
 <?php echo $this->Form->input('username');
 echo $this->Form->input('password');
 ?>
 
 </fieldset>
<?php echo $this->Form->end(__('Login')); ?>
</div>

Crear cakephp rest client

Para probar si funcionan  bien los webservices creados vamos a crear una web que los ‘consuma’. En cualquier cakephp (puede ser el mismo) crearemos un controlador ClienteController:

 <?php

App::uses('AppController', 'Controller');
App::uses('HttpSocket', 'Network/Http');

class ClienteController extends AppController {

 public $components = array('Security', 'RequestHandler');
//Aquí pondréis la url de vuestra web
 public $url = "http://localhost/cakeempresa/";

 public function index() {
 
 }

 public function rest_index() {
 $link = $this->url . 'rest_productos.json';

 $data = null;
 $httpSocket = new HttpSocket();
 $response = $httpSocket->get($link, $data);
 $this->set('response_code', $response->code);
 $this->set('response_body', $response->body);

 $this->render('/Cliente/respuesta');
 }

 public function rest_view($id) {

 $link = $this->url . 'rest_productos/' . $id . '.json';

 $data = null;
 $httpSocket = new HttpSocket();
 $response = $httpSocket->get($link, $data);
 $this->set('response_code', $response->code);
 $this->set('response_body', $response->body);

 $this->render('/Cliente/respuesta');
 }

 public function rest_add() {

 $link = $this->url . "rest_productos.json";
 $data = null;
 $httpSocket = new HttpSocket();
 $data['Producto']['precio'] = 74;
 $data['Producto']['proveedor_id'] = 1;
 $data['Producto']['referencia'] = 'Extra';
 $response = $httpSocket->post($link, $data);

 $this->set('response_code', $response->code);
 $this->set('response_body', $response->body);

 $this->render('/Cliente/respuesta');
 }

 public function rest_edit($id) {

 $link = $this->url . 'rest_productos/' . $id . '.json';
 $data = null;
 $httpSocket = new HttpSocket();
 $data['Producto']['precio'] = 27;
 $data['Producto']['referencia'] = 'Actualizada referencia';
 $response = $httpSocket->put($link, $data);
 $this->set('response_code', $response->code);
 $this->set('response_body', $response->body);

 $this->render('/Cliente/respuesta');
 }

 public function rest_delete($id) {

 // remotely post the information to the server
 $link = $this->url . 'rest_productos/' . $id . '.json';
 $data = null;
 $httpSocket = new HttpSocket();
 $response = $httpSocket->delete($link, $data);
 $this->set('response_code', $response->code);
 $this->set('response_body', $response->body);

 $this->render('/Cliente/respuesta');
 }

}

Vista en View\Cliente\index.ctp:

 
<h1>Acciones</h1>
<p>
Escoja
<ul>
<li><?php echo $this->Html->link('Lista Productos', array('controller' => 'cliente', 'action' => 'rest_index')); ?></li><li><?php echo $this->Html->link('Ver producto con ID 1', array('controller' => 'cliente', 'action' => 'rest_view', 1)); ?></li>
<li><?php echo $this->Html->link('Añadir producto', array('controller' => 'cliente', 'action' => 'rest_add')); ?></li>
<li><?php echo $this->Html->link('Actualizar producto 2', array('controller' => 'cliente', 'action' => 'rest_edit', 2)); ?></li>
<li><?php echo $this->Html->link('Borrar producto 3', array('controller' => 'cliente', 'action' => 'rest_delete', 3)); ?></li></ul> </p>

Vista en View\Cliente\respuesta.ctp:

 
<h1>Código respuesta</h1>
<p><?php echo $response_code; ?></p>

<h1>Cuerpo respuesta</h1>
<p><?php echo $response_body; ?></p>

Crear Cakephp Rest

Añadimos las siguientes líneas en routes.php (antes de’ require CAKE . ‘Config’ . DS . ‘routes.php’;’):

  Router::mapResources('rest_productos');
 Router::parseExtensions();

Y añadimos el controlador restProductos:

class RestProductosController extends AppController {

 public $uses = array('Producto');
 public $components = array('RequestHandler');

 public function index() {

 $productos = $this->Producto->find('all');
 $this->set(array(
 'productos' => $productos,
 '_serialize' => array('productos')
 ));
 }

 public function add() {
 $this->Producto->create();
 if ($this->Producto->save($this->request->data)) {
 $message = 'Created';
 } else {
 $message = 'Error';
 }

 $this->set(array(
 'message' => $message,
 '_serialize' => array('message')
 ));
 }

 public function view($id) {

 $producto = $this->Producto->findById($id);
 $this->set(array(
 'producto' => $producto,
 '_serialize' => array('producto')
 ));
 }

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

 if ($this->Producto->save($this->request->data)) {
 $message = 'Saved';
 } else {
 $message = 'Error';
 }
 $this->set(array(
 'message' => $message,
 '_serialize' => array('message')
 ));
 }

 public function delete($id) {
 if ($this->Producto->delete($id)) {
 $message = 'Deleted';
 } else {
 $message = 'Error';
 }
 $this->set(array(
 'message' => $message,
 '_serialize' => array('message')
 ));
 }

}

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');

Componentes en cakePHP

Los componentes son paquetes de lógica que son compartidos entre los controladores. CakePHP ya dispone de un conjunto muy útil de componentes que veremos más adelante, pero podemos crear cualquiera a medida.

Por ejemplo, si tenemos un cálculo de importe que es compartido por varios controladores lo adecuado es instalarlo en un componente y que cada controlador use el componente y acceda a la funcionalidad.

Ejemplo:

class ImportesComponent extends Component{
function calculaImporte($amount1, $amount2) {
return $amount1 + $amount2*.21;
}
}

Helpers

Para usar helpers en nuestro controlador tenemos que indicar cual vamos a usar:

<?php
class BakeriesController extends AppController {
var $helpers = array(‘Form’, ‘Html’, ‘Javascript’, ‘Time’);
}
?>

Disponemos de los siguientes helpers en el núcleo de cakePHP:

Helper de CakePHP Descripción
Ajax Usado en conjunto con Prototype Javascript Library para crear funcionalidad en las vistas. Contiene métodos rápidos para drag/drop (levantar/tirar), formularios ajax & enlaces, observadores, y más.
Cache Es usado por el núcleo (core) para almacenar el contenido de las vistas en caché.
Form Crea formularios HTML y elementos de formulario que se poblan solas y manejan problemas de validación.
Html Métodos convenientes para crear código (markup) bien formateado. Imágenes, links, tablas, tags de headers y más.
Javascript Usado para ‘escapar’ valores para usarse en JavaScript, escribir tus propios objetos JSON, y dar formato a bloques de código.
Number Formato para números y tipo de cambio.
Paginator Paginar y ordenar información de modelos.
Rss Métodos convenientes para regresar datos RSS feed XML.
Session Aceso para escribir valores de la sesión en las vistas.
Text Enlaces inteligentes, marcadores, truncado inteligente de palabras.
Time Detección de proximidad (es este siguiente año?), buen formateo de cadenas de caracteres (Hoy, 10:20 am) y conversiones de usos horarios.
Xml Métodos convenientes para crear elementos y headers XML.

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.