From 260cd049ad45808d5e2ad52d0428250b827372c8 Mon Sep 17 00:00:00 2001 From: Andres Campanario Date: Mon, 22 Jan 2024 11:32:15 +0100 Subject: [PATCH 1/5] translate componente to spanish --- .../components/check-http-cache.rst | 26 ++- es/controllers/components/flash.rst | 88 +++++++++- es/controllers/components/form-protection.rst | 155 +++++++++++++++++- 3 files changed, 247 insertions(+), 22 deletions(-) diff --git a/es/controllers/components/check-http-cache.rst b/es/controllers/components/check-http-cache.rst index 0324b1a250..ea040838a2 100644 --- a/es/controllers/components/check-http-cache.rst +++ b/es/controllers/components/check-http-cache.rst @@ -1,11 +1,21 @@ -Checking HTTP Cache -=================== +Comprobando la Caché HTTP +========================= -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. +.. php:class:: CheckHttpCacheComponent(ComponentCollection $collection, array $config = []) - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. +El modelo de validación de caché HTTP es uno de los procesos utilizados por las pasarelas de caché, también conocidas como proxies inversos, para determinar si pueden servir una copia almacenada de una respuesta al cliente. Bajo este modelo, principalmente se ahorra ancho de banda, pero cuando se utiliza correctamente, también se puede ahorrar algo de procesamiento de CPU, reduciendo los tiempos de respuesta:: - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. \ No newline at end of file + // En un Controlador + public function initialize(): void + { + parent::initialize(); + + $this->addComponent('CheckHttpCache'); + } + +Habilitar el componente ``CheckHttpCache`` en su controlador activa automáticamente una comprobación ``beforeRender``. Esta comprobación compara los encabezados de caché establecidos en el objeto de respuesta con los encabezados de caché enviados en la solicitud para determinar si la respuesta no se ha modificado desde la última vez que el cliente la solicitó. Se utilizan los siguientes encabezados de solicitud: + + ``If-None-Match`` se compara con el encabezado ``Etag`` de la respuesta. + ``If-Modified-Since`` se compara con el encabezado ``Last-Modified`` de la respuesta. + +Si los encabezados de la respuesta coinciden con los criterios de los encabezados de la solicitud, se omite el renderizado de la vista. Esto ahorra a su aplicación la generación de una vista, ahorrando ancho de banda y tiempo. Cuando los encabezados de la respuesta coinciden, se devuelve una respuesta vacía con un código de estado ``304 No Modificado``. diff --git a/es/controllers/components/flash.rst b/es/controllers/components/flash.rst index 57f94b1065..979ee70015 100644 --- a/es/controllers/components/flash.rst +++ b/es/controllers/components/flash.rst @@ -1,11 +1,85 @@ -FlashComponent -############## +Flash +##### + +.. php:namespace:: Cake\Controller\Component + +.. php:class:: FlashComponent(ComponentCollection $collection, array $config = []) + +FlashComponent proporciona una manera de establecer mensajes de notificación de una sola vez que se mostrarán después de procesar un formulario o reconocer datos. CakePHP se refiere a estos mensajes como "flash messages" (mensajes flash). FlashComponent escribe mensajes flash en ``$_SESSION``, para ser renderizados en una Vista usando +:doc:`FlashHelper `. + +Configuración de Mensajes Flash +=============================== + +FlashComponent proporciona dos formas de establecer mensajes flash: su método mágico ``__call()`` y su método ``set()``. Para dotar a su aplicación de verbosidad, el método mágico ``__call()`` de FlashComponent le permite usar un nombre de método que se asigna a un elemento ubicado en el directorio templates/element/flash. Por convención, los métodos camelcased se asignarán al nombre del elemento en minúsculas y con guiones bajos:: + + // Utiliza templates/element/flash/success.php + $this->Flash->success('Esto fue un exito'); + + // Utiliza templates/element/flash/great_success.php + $this->Flash->greatSuccess('Esto fue un gran exito'); + +Alternativamente, para establecer un mensaje de texto plano sin renderizar un elemento, puede usar el método ``set()``:: + + $this->Flash->set('Este es un mensaje'); + +Los mensajes flash se agregan a un array internamente. Llamadas sucesivas a +``set()`` o ``__call()`` con la misma clave agregarán los mensajes en la +``$_SESSION``. Si desea sobrescribir mensajes existentes al establecer un mensaje flash, establezca la opción clear en true al configurar el componente. + +Los métodos ``__call()`` y ``set()`` de FlashComponent opcionalmente toman un segundo +parámetro, un array de opciones: + +* ``key`` Por defecto es 'flash'. La clave del array que se encuentra bajo la clave Flash en la sesión. +* ``element`` Por defecto es null, pero se establecerá automáticamente al usar el método mágico __call(). El nombre del elemento a usar para renderizar. +* ``params`` Un array opcional de claves/valores para poner a disposición como variables dentro de un elemento. +* ``clear`` espera un bool y le permite borrar todos los mensajes en el stack actual y comenzar uno nuevo. + +Un ejemplo de uso de estas opciones:: + + // En su Controlador + $this->Flash->success('El usuario ha sido guardado', [ + 'key' => 'positivo', + 'clear' => true, + 'params' => [ + 'nombre' => $usuario->nombre, + 'email' => $usuario->email, + ], + ]); + + // En su Vista + Flash->render('positivo') ?> + + +
+ : , . +
+ +Tenga en cuenta que el parámetro `element` siempre se anulará al usar ``__call()``. Para recuperar un elemento específico de un plugin, debe establecer el parámetro plugin. Por ejemplo:: + + // En su Controlador + $this->Flash->warning('Mi mensaje', ['plugin' => 'NombreDelPlugin']); + +El código anterior utilizará el elemento warning.php bajo +plugins/NombreDelPlugin/templates/element/flash para renderizar el mensaje flash. .. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. + Por defecto, CakePHP escapa el contenido en los mensajes flash para prevenir + scripting entre sitios. Los datos de usuario en sus mensajes flash se codificarán en HTML y + estarán seguros para ser impresos. Si desea incluir HTML en sus mensajes flash, + debe pasar la opción ``escape`` y ajustar sus plantillas de mensajes flash + para permitir desactivar el escape cuando se pasa la opción de escape. + +HTML en Mensajes Flash +====================== + +Es posible imprimir HTML en mensajes flash usando la clave 'escape' en las opciones:: + + $this->Flash->info(sprintf('%s %s', h($resaltar), h($mensaje)), ['escape' => false]); + +Asegúrese de escapar manualmente la entrada, entonces. En el ejemplo anterior, +`$resaltar` y `$mensaje` son entradas no HTML y, por lo tanto, están escapadas. - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. +Para obtener más información sobre cómo renderizar sus mensajes flash, consulte la +sección :doc:`FlashHelper `. diff --git a/es/controllers/components/form-protection.rst b/es/controllers/components/form-protection.rst index 4ccf013abd..0e01dbda06 100644 --- a/es/controllers/components/form-protection.rst +++ b/es/controllers/components/form-protection.rst @@ -1,15 +1,156 @@ FormProtection ############## +.. php:class:: FormProtection(ComponentCollection $collection, array $config = []) + +El Componente FormProtection proporciona protección contra la manipulación de datos de formularios. + +Al igual que todos los componentes, se configura mediante varios parámetros configurables. +Todas estas propiedades se pueden establecer directamente o a través de métodos de configuración del mismo nombreen los métodos ``initialize()`` o ``beforeFilter()`` de su controlador. + +Si está utilizando otros componentes que procesan datos de formularios en sus devoluciones de llamada ``startup()``, asegúrese de colocar el Componente FormProtection antes de esos componentes en su método ``initialize()``. + +.. note:: + + Al utilizar el Componente FormProtection, **debe** utilizar FormHelper para crear + sus formularios. Además, **no** debe anular ninguno de los atributos "name" de los campos. + El Componente FormProtection busca ciertos indicadores que son + creados y gestionados por FormHelper (especialmente aquellos creados en + :php:meth:`~Cake\\View\\Helper\\FormHelper::create()` y + :php:meth:`~Cake\\View\\Helper\\FormHelper::end()`). Modificar dinámicamente + los campos que se envían en una solicitud POST, como deshabilitar, eliminar + o crear nuevos campos mediante JavaScript, probablemente causará que la validación del token de formulario + falle. + +Prevención de manipulación de formularios +========================================= + +De forma predeterminada, el ``FormProtectionComponent`` evita que los usuarios manipulen +formularios de maneras específicas. Evitará las siguientes cosas: + +* La acción (URL) del formulario no se puede modificar. +* No se pueden agregar campos desconocidos al formulario. +* No se pueden eliminar campos del formulario. +* No se pueden modificar los valores en los campos de entrada ocultos. + +Evitar estos tipos de manipulación se logra trabajando con el ``FormHelper`` +y haciendo un seguimiento de qué campos están en un formulario. También se hace un seguimiento +de los valores para los campos ocultos. Todos estos datos se combinan y se convierten en un hash y campos de token ocultos se insertan automáticamente en los formularios. Cuando se envía un formulario, el ``FormProtectionComponent`` utilizará los datos POST para construir la misma estructura y comparar el hash. + .. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. + El ``FormProtectionComponent`` **no** evitará que se agreguen/cambien opciones de selección. + Tampoco evitará que se agreguen/cambien opciones de radio. + +Uso +=== + +La configuración del componente de protección de formularios se realiza generalmente en los métodos +``initialize()`` o ``beforeFilter()`` del controlador. + +Las opciones disponibles son: + +validate + Establecer en ``false`` para omitir completamente la validación de solicitudes POST, + esencialmente desactivando la validación del formulario. + +unlockedFields + Establecer a una lista de campos de formulario para excluir de la validación POST. Los campos pueden ser + desbloqueados ya sea en el componente o con + :php:meth:`FormHelper::unlockField()`. Los campos que han sido desbloqueados no son + obligatorios para formar parte del POST y los campos ocultos desbloqueados no tienen + sus valores verificados. + +unlockedActions + Acciones para excluir de las comprobaciones de validación POST. + +validationFailureCallback + Devolución de llamada para llamar en caso de fallo de validación. Debe ser un Closure válido. + No está configurado por defecto, en cuyo caso se lanzará una excepción en caso de fallo de validación. + +Desactivar las comprobaciones de manipulación de formularios +============================================================ + +:: + + namespace App\Controller; + + use App\Controller\AppController; + use Cake\Event\EventInterface; + + class WidgetsController extends AppController + { + public function initialize(): void + { + parent::initialize(); + + $this->loadComponent('FormProtection'); + } + + public function beforeFilter(EventInterface $event) + { + parent::beforeFilter($event); + + if ($this->request->getParam('prefix') === 'Admin') { + $this->FormProtection->setConfig('validate', false); + } + } + } + +El ejemplo anterior desactivaría la prevención de manipulación de formularios para rutas con prefijo de administrador. + +Desactivar la manipulación de formularios para acciones específicas +=================================================================== + +Puede haber casos en los que desee desactivar la prevención de manipulación de formularios para una acción (por ejemplo, solicitudes AJAX). Puede "desbloquear" estas acciones enumerándolas en ``$this->FormProtection->setConfig('unlockedActions', ['edit']);`` en su ``beforeFilter()::`` + +:: + + namespace App\Controller; + + use App\Controller\AppController; + use Cake\Event\EventInterface; + + class WidgetController extends AppController + { + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('FormProtection'); + } + + public function beforeFilter(EventInterface $event) + { + parent::beforeFilter($event); + + $this->FormProtection->setConfig('unlockedActions', ['edit']); + } + } + +Este ejemplo desactivaría todas las comprobaciones de seguridad para la acción de edición. + +Manejo de fallos de validación a través de devoluciones de llamada +================================================================== + +Si falla la validación de la protección de formularios, por defecto resultará en un error 400. +Puede configurar este comportamiento estableciendo la opción de configuración ``validationFailureCallback`` +a una función de devolución de llamada en el controlador. + +Al configurar un método de devolución de llamada, puede personalizar cómo funciona el proceso de manejo de fallos:: + + public function beforeFilter(EventInterface $event) + { + parent::beforeFilter($event); - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. + $this->FormProtection->setConfig( + 'validationFailureCallback', + function (BadRequestException $exception) { + // Puede devolver una instancia de respuesta o lanzar la excepción + // recibida como argumento. + } + ); + } .. meta:: - :title lang=en: FormProtection - :keywords lang=en: configurable parameters,form protection component,configuration parameters,protection features,tighter security,php class,meth,array,submission,security class,disable security,unlockActions \ No newline at end of file + :title lang=es: FormProtection + :keywords lang=es: parametros configurables,componente de protección de formularios,parametros de configuracion,características de protección,tighter security,clase php,método,array,envío,clase de seguridad,desactivar seguridad,desbloquear acciones From d3786a29d3d914140106395cca258af631ead15b Mon Sep 17 00:00:00 2001 From: Andres Campanario Date: Mon, 22 Jan 2024 12:51:44 +0100 Subject: [PATCH 2/5] translate core libraries collection, events and form --- es/core-libraries/collections.rst | 1049 ++++++++++++++++++++++++++++- es/core-libraries/events.rst | 396 ++++++++++- es/core-libraries/form.rst | 226 ++++++- 3 files changed, 1642 insertions(+), 29 deletions(-) diff --git a/es/core-libraries/collections.rst b/es/core-libraries/collections.rst index de4626b311..5e34fabced 100644 --- a/es/core-libraries/collections.rst +++ b/es/core-libraries/collections.rst @@ -1,19 +1,1046 @@ +Colecciones +########### + .. php:namespace:: Cake\Collection -.. _collection-objects: +.. php:class:: Collection -Collections -########### +Las clases de colección proporcionan un conjunto de herramientas para manipular matrices u objetos ``Traversable``. Si alguna vez has utilizado underscore.js, tienes una idea de lo que puedes esperar de las clases de colección. + +Las instancias de colección son inmutables; modificar una colección generará en su lugar una nueva colección. Esto hace que trabajar con objetos de colección sea más predecible, ya que las operaciones no tienen efectos secundarios. + +Ejemplo Rápido +============== + +Las colecciones se pueden crear usando una matriz u objeto ``Traversable``. También interactuarás con colecciones cada vez que interactúes con el ORM en CakePHP. Un uso simple de una colección sería:: + + use Cake\Collection\Collection; + + $items = ['a' => 1, 'b' => 2, 'c' => 3]; + $coleccion = new Collection($items); + + // Crea una nueva colección que contiene elementos + // con un valor mayor que uno. + $mayorUno = $coleccion->filter(function ($valor, $clave, $iterador) { + return $valor > 1; + }); + +También puedes usar la función ``collection()`` en lugar de ``new +Collection()``:: + + $items = ['a' => 1, 'b' => 2, 'c' => 3]; + + // Ambos crean una instancia de Collection. + $coleccionA = new Collection($items); + $coleccionB = collection($items); + +La ventaja del método de ayuda es que es más fácil encadenar que +``(new Collection($items))``. + +El :php:trait:`~Cake\\Collection\\CollectionTrait` te permite integrar +características similares a las de una colección en cualquier objeto ``Traversable`` que tengas en tu +aplicación. + +Lista de Métodos +================ + +.. csv-table:: + :class: docutils internal-toc + + :php:meth:`append`, :php:meth:`appendItem`, :php:meth:`avg`, + :php:meth:`buffered`, :php:meth:`chunk`, :php:meth:`chunkWithKeys` + :php:meth:`combine`, :php:meth:`compile`, :php:meth:`contains` + :php:meth:`countBy`, :php:meth:`each`, :php:meth:`every` + :php:meth:`extract`, :php:meth:`filter`, :php:meth:`first` + :php:meth:`firstMatch`, :php:meth:`groupBy`, :php:meth:`indexBy` + :php:meth:`insert`, :php:meth:`isEmpty`, :php:meth:`last` + :php:meth:`listNested`, :php:meth:`map`, :php:meth:`match` + :php:meth:`max`, :php:meth:`median`, :php:meth:`min` + :php:meth:`nest`, :php:meth:`prepend`, :php:meth:`prependItem` + :php:meth:`reduce`, :php:meth:`reject`, :php:meth:`sample` + :php:meth:`shuffle`, :php:meth:`skip`, :php:meth:`some` + :php:meth:`sortBy`, :php:meth:`stopWhen`, :php:meth:`sumOf` + :php:meth:`take`, :php:meth:`through`, :php:meth:`transpose` + :php:meth:`unfold`, :php:meth:`zip` + +Iteración +========= + +.. php:method:: each($callback) + +Las colecciones pueden ser iteradas y/o transformadas en nuevas colecciones con los métodos ``each()`` y ``map()``. El método ``each()`` no creará una nueva colección, pero te permitirá modificar cualquier objeto dentro de la colección:: + + $coleccion = new Collection($elementos); + $coleccion = $coleccion->each(function ($valor, $clave) { + echo "Elemento $clave: $valor"; + }); + +El retorno de ``each()`` será el objeto de la colección. Each iterará la colección aplicando inmediatamente el callback a cada valor en la colección. + +.. php:method:: map($callback) + +El método ``map()`` creará una nueva colección basada en la salida del callback aplicado a cada objeto en la colección original:: + + $elementos = ['a' => 1, 'b' => 2, 'c' => 3]; + $coleccion = new Collection($elementos); + + $nueva = $coleccion->map(function ($valor, $clave) { + return $valor * 2; + }); + + // $resultado contiene [2, 4, 6]; + $resultado = $nueva->toList(); + + // $resultado contiene ['a' => 2, 'b' => 4, 'c' => 6]; + $resultado = $nueva->toArray(); + +El método ``map()`` creará un nuevo iterador que crea perezosamente los elementos resultantes cuando se itera. + +.. php:method:: extract($path) + +Uno de los usos más comunes de una función ``map()`` es extraer una sola columna de una colección. Si estás buscando construir una lista de elementos que contengan los valores de una propiedad específica, puedes usar el método ``extract()``:: + + $coleccion = new Collection($personas); + $nombres = $coleccion->extract('nombre'); + + // $resultado contiene ['mark', 'jose', 'barbara']; + $resultado = $nombres->toList(); + +Como con muchas otras funciones en la clase de colección, se te permite especificar un camino separado por puntos para extraer columnas. Este ejemplo devolverá una colección que contiene los nombres de los autores de una lista de artículos:: + + $coleccion = new Collection($articulos); + $nombres = $coleccion->extract('autor.nombre'); + + // $resultado contiene ['Maria', 'Stacy', 'Larry']; + $resultado = $nombres->toList(); + +Finalmente, si la propiedad que estás buscando no se puede expresar como un camino, puedes usar una función de devolución de llamada para obtenerla:: + + $coleccion = new Collection($articulos); + $nombres = $coleccion->extract(function ($articulo) { + return $articulo->autor->nombre . ', ' . $articulo->autor->apellido; + }); + +A menudo, las propiedades que necesitas extraer son una clave común presente en múltiples matrices u objetos que están profundamente anidados dentro de otras estructuras. Para esos casos, puedes usar el comodín ``{*}`` en la clave del camino. Este comodín es útil cuando se coinciden datos de asociaciones HasMany y BelongsToMany:: + + $datos = [ + [ + 'nombre' => 'James', + 'numeros_telefonicos' => [ + ['numero' => 'numero-1'], + ['numero' => 'numero-2'], + ['numero' => 'numero-3'], + ], + ], + [ + 'nombre' => 'James', + 'numeros_telefonicos' => [ + ['numero' => 'numero-4'], + ['numero' => 'numero-5'], + ], + ], + ]; + + $numeros = (new Collection($datos))->extract('numeros_telefonicos.{*}.numero'); + $resultado = $numeros->toList(); + // $resultado contiene ['numero-1', 'numero-2', 'numero-3', 'numero-4', 'numero-5'] + +Este último ejemplo usa ``toList()`` a diferencia de otros ejemplos, lo cual es importante cuando obtenemos resultados con claves posiblemente duplicadas. Al usar ``toList()``, nos aseguraremos de obtener todos los valores incluso si hay claves duplicadas. + +A diferencia de :php:meth:`Cake\\Utility\\Hash::extract()`, este método solo admite el comodín ``{*}``. Ningún otro comodín o coincidente de atributos es compatible. + +.. php:method:: combine($keyPath, $valuePath, $groupPath = null) + +Las colecciones te permiten crear una nueva colección a partir de claves y valores en una colección existente. Tanto el camino de la clave como el de los valores pueden especificarse con notación de puntos:: + + $elementos = [ + ['id' => 1, 'name' => 'foo', 'parent' => 'a'], + ['id' => 2, 'name' => 'bar', 'parent' => 'b'], + ['id' => 3, 'name' => 'baz', 'parent' => 'a'], + ]; + $combinada = (new Collection($elementos))->combine('id', 'name'); + $resultado = $combinada->toArray(); + + // $resultado contiene + [ + 1 => 'foo', + 2 => 'bar', + 3 => 'baz', + ]; + +También puedes opcionalmente usar un ``groupPath`` para agrupar resultados basados en un camino:: + + $combinada = (new Collection($elementos))->combine('id', 'name', 'parent'); + $resultado = $combinada->toArray(); + + // $resultado contiene + [ + 'a' => [1 => 'foo', 3 => 'baz'], + 'b' => [2 => 'bar'] + ]; + +Finalmente, puedes usar *cierres* para construir caminos de claves/valores/grupos dinámicamente, por ejemplo, cuando trabajas con entidades y fechas (convertidas a instancias de ``I18n\DateTime`` por el ORM) es posible que desees agrupar los resultados por fecha:: + + $combinada = (new Collection($entidades))->combine( + 'id', + function ($entidad) { return $entidad; }, + function ($entidad) { return $entidad->date->toDateString(); } + ); + $resultado = $combinada->toArray(); + + // $resultado contiene + [ + 'cadena de fecha como 2015-05-01' => ['entidad1->id' => entidad1, 'entidad2->id' => entidad2, ..., 'entidadN->id' => entidadN] + 'cadena de fecha como 2015-06-01' => ['entidad1->id' => entidad1, 'entidad2->id' => entidad2, ..., 'entidadN->id' => entidadN] + ] + +.. php:method:: stopWhen(callable $c) + +Puedes detener la iteración en cualquier punto usando el método ``stopWhen()``. Llamarlo en una colección creará una nueva que dejará de generar resultados si el callable pasado devuelve true para uno de los elementos:: + + $elementos = [10, 20, 50, 1, 2]; + $coleccion = new Collection($elementos); + + $nueva = $coleccion->stopWhen(function ($valor, $clave) { + // Detener en el primer valor mayor que 30 + return $valor > 30; + }); + + // $resultado contiene [10, 20]; + $resultado = $nueva->toList(); + +.. php:method:: unfold(callable $callback) + +A veces, los elementos internos de una colección contendrán matrices o iteradores con más elementos. Si deseas aplanar la estructura interna para iterar una vez sobre todos los elementos, puedes usar el método ``unfold()``. Creará una nueva colección que generará cada elemento único anidado en la colección:: + + $elementos = [[1, 2, 3], [4, 5]]; + $coleccion = new Collection($elementos); + $nueva = $coleccion->unfold(); + + // $resultado contiene [1, 2, 3, 4, 5]; + $resultado = $nueva->toList(); + +Cuando pasas un callable a ``unfold()``, puedes controlar qué elementos se desplegarán de cada elemento en la colección original. Esto es útil para devolver datos de servicios paginados:: + + $paginas = [1, 2, 3, 4]; + $coleccion = new Collection($paginas); + $elementos = $coleccion->unfold(function ($pagina, $clave) { + // Un servicio web imaginario que devuelve una página de resultados + return MyService::fetchPage($pagina)->toList(); + }); + + $todosLosElementosDeLasPaginas = $elementos->toList(); + +Si estás usando PHP 5.5+, puedes usar la palabra clave ``yield`` dentro de ``unfold()`` para devolver tantos elementos para cada elemento en la colección como puedas necesitar:: + + $numerosImpares = [1, 3, 5, 7]; + $coleccion = new Collection($numerosImpares); + $nueva = $coleccion->unfold(function ($numeroImpar) { + yield $numeroImpar; + yield $numeroImpar + 1; + }); + + // $resultado contiene [1, 2, 3, 4, 5, 6, 7, 8]; + $resultado = $nueva->toList(); + +.. php:method:: chunk($chunkSize) + +Cuando se trata de grandes cantidades de elementos en una colección, puede tener sentido procesar los elementos en lotes en lugar de uno por uno. Para dividir una colección en varios conjuntos de un tamaño determinado, puedes usar la función ``chunk()``:: + + $elementos = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; + $coleccion = new Collection($elementos); + $troceada = $coleccion->chunk(2); + $troceada->toList(); // [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]] + +La función ``chunk`` es particularmente útil al realizar procesamiento por lotes, por ejemplo, con un resultado de base de datos:: + + $coleccion = new Collection($artículos); + $coleccion->map(function ($artículo) { + // Cambiar una propiedad en el artículo + $artículo->propiedad = 'cambiada'; + }) + ->chunk(20) + ->each(function ($lote) { + myBulkSave($lote); // Esta función se llamará para cada lote + }); + +.. php:method:: chunkWithKeys($chunkSize) + +Al igual que :php:meth:`chunk()`, ``chunkWithKeys()`` te permite dividir +una colección en lotes más pequeños pero conservando las claves. Esto es útil cuando se dividen arrays asociativos:: + + $coleccion = new Collection([ + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => [4, 5] + ]); + $troceada = $coleccion->chunkWithKeys(2); + $resultado = $troceada->toList(); + + // $resultado contiene + [ + ['a' => 1, 'b' => 2], + ['c' => 3, 'd' => [4, 5]] + ] + +Filtrado +======== + +.. php:method:: filter($callback) + +Las colecciones te permiten filtrar y crear nuevas colecciones basadas en el resultado de funciones de devolución de llamada. Puedes usar ``filter()`` para crear una nueva colección de elementos que coincidan con un criterio de devolución de llamada:: + + $coleccion = new Collection($personas); + $damas = $coleccion->filter(function ($persona, $clave) { + return $persona->gender === 'female'; + }); + $caballeros = $coleccion->filter(function ($persona, $clave) { + return $persona->gender === 'male'; + }); + +.. php:method:: reject(callable $c) + +La inversa de ``filter()`` es ``reject()``. Este método hace un filtro negativo, eliminando elementos que coincidan con la función de filtro:: + + $coleccion = new Collection($personas); + $damas = $coleccion->reject(function ($persona, $clave) { + return $persona->gender === 'male'; + }); + +.. php:method:: every($callback) + +Puedes hacer pruebas de verdad con funciones de filtro. Para ver si cada elemento en una colección coincide con una prueba, puedes usar ``every()``:: + + $coleccion = new Collection($personas); + $todosJóvenes = $coleccion->every(function ($persona) { + return $persona->age < 21; + }); + +.. php:method:: some($callback) + +Puedes ver si la colección contiene al menos un elemento que coincida con una función de filtro usando el método ``some()``:: + + $coleccion = new Collection($personas); + $hayJóvenes = $coleccion->some(function ($persona) { + return $persona->age < 21; + }); + +.. php:method:: match($conditions) + +Si necesitas extraer una nueva colección que contenga solo los elementos que contienen un conjunto dado de propiedades, debes usar el método ``match()``:: + + $coleccion = new Collection($comentarios); + $comentariosDeMark = $coleccion->match(['user.name' => 'Mark']); + +.. php:method:: firstMatch($conditions) + +El nombre de la propiedad puede ser un camino separado por puntos. Puedes navegar hacia entidades anidadas y coincidir con los valores que contienen. Cuando solo necesitas el primer elemento que coincide de una colección, puedes usar ``firstMatch()``:: + + $coleccion = new Collection($comentarios); + $comentario = $coleccion->firstMatch([ + 'user.name' => 'Mark', + 'active' => true + ]); + +Como se puede ver en lo anterior, tanto ``match()`` como ``firstMatch()`` te permiten proporcionar múltiples condiciones para coincidir. Además, las condiciones pueden ser para diferentes caminos, lo que te permite expresar condiciones complejas a las que coincidir. + +Agregación +========== + +.. php:method:: reduce($callback, $initial) + +El contraparte de una operación ``map()`` suele ser un ``reduce``. Esta función te ayudará a construir un resultado único a partir de todos los elementos en una colección:: + + $precioTotal = $coleccion->reduce(function ($acumulado, $lineaDePedido) { + return $acumulado + $lineaDePedido->precio; + }, 0); + +En el ejemplo anterior, ``$precioTotal`` será la suma de todos los precios individuales contenidos en la colección. Observa que el segundo argumento para la función ``reduce()`` toma el valor inicial para la operación de reducción que estás realizando:: + + $todasLasEtiquetas = $coleccion->reduce(function ($acumulado, $articulo) { + return array_merge($acumulado, $articulo->etiquetas); + }, []); + +.. php:method:: min(string|callable $callback, $type = SORT_NUMERIC) + +Para extraer el valor mínimo de una colección basado en una propiedad, simplemente utiliza la función ``min()``. Esto devolverá el elemento completo de la colección y no solo el valor más pequeño encontrado:: + + $coleccion = new Collection($personas); + $másJoven = $coleccion->min('edad'); + + echo $másJoven->nombre; + +También puedes expresar la propiedad a comparar proporcionando un camino o una función de devolución de llamada:: + + $coleccion = new Collection($personas); + $personaConHijoMásJoven = $coleccion->min(function ($persona) { + return $persona->hijo->edad; + }); + + $personaConPadreMásJoven = $coleccion->min('padre.edad'); + +.. php:method:: max(string|callable $callback, $type = SORT_NUMERIC) + +Lo mismo se puede aplicar a la función ``max()``, que devolverá un solo elemento de la colección con el valor de propiedad más alto:: + + $coleccion = new Collection($personas); + $mayor = $coleccion->max('edad'); + + $personaConHijoMayor = $coleccion->max(function ($persona) { + return $persona->hijo->edad; + }); + + $personaConPadreMayor = $coleccion->max('padre.edad'); + +.. php:method:: sumOf($path = null) + +Finalmente, el método ``sumOf()`` devolverá la suma de una propiedad de todos los elementos:: + + $coleccion = new Collection($personas); + $sumaDeEdades = $coleccion->sumOf('edad'); + + $sumaDeEdadesDeHijos = $coleccion->sumOf(function ($persona) { + return $persona->hijo->edad; + }); + + $sumaDeEdadesDePadres = $coleccion->sumOf('padre.edad'); + +.. php:method:: avg($path = null) + +Calcula el valor promedio de los elementos en la colección. Opcionalmente, proporciona un camino de coincidencia o una función para extraer valores y generar el promedio:: + + $elementos = [ + ['factura' => ['total' => 100]], + ['factura' => ['total' => 200]], + ]; + + // $promedio contiene 150 + $promedio = (new Collection($elementos))->avg('factura.total'); + +.. php:method:: median($path = null) + +Calcula el valor mediano de un conjunto de elementos. Opcionalmente, proporciona un camino de coincidencia o una función para extraer valores y generar la mediana:: + + $elementos = [ + ['factura' => ['total' => 400]], + ['factura' => ['total' => 500]], + ['factura' => ['total' => 100]], + ['factura' => ['total' => 333]], + ['factura' => ['total' => 200]], + ]; + + // $mediana contiene 333 + $mediana = (new Collection($elementos))->median('factura.total'); + +Agrupación y Conteo +------------------- + +.. php:method:: groupBy($callback) + +Los valores de una colección se pueden agrupar por diferentes claves en una nueva colección cuando comparten el mismo valor para una propiedad:: + + $estudiantes = [ + ['nombre' => 'Mark', 'grado' => 9], + ['nombre' => 'Andrew', 'grado' => 10], + ['nombre' => 'Stacy', 'grado' => 10], + ['nombre' => 'Barbara', 'grado' => 9] + ]; + $coleccion = new Collection($estudiantes); + $estudiantesPorGrado = $coleccion->groupBy('grado'); + $resultado = $estudiantesPorGrado->toArray(); + + // $resultado contiene + [ + 10 => [ + ['nombre' => 'Andrew', 'grado' => 10], + ['nombre' => 'Stacy', 'grado' => 10] + ], + 9 => [ + ['nombre' => 'Mark', 'grado' => 9], + ['nombre' => 'Barbara', 'grado' => 9] + ] + ] + +Como de costumbre, es posible proporcionar ya sea un camino separado por puntos para propiedades anidadas o tu propia función de devolución de llamada para generar los grupos dinámicamente:: + + $comentariosPorIdDeUsuario = $comentarios->groupBy('usuario.id'); + + $resultadosDeClase = $estudiantes->groupBy(function ($estudiante) { + return $estudiante->grado > 6 ? 'aprobado' : 'denegado'; + }); + +.. php:method:: countBy($callback) + +Si solo deseas conocer el número de ocurrencias por grupo, puedes hacerlo mediante el método ``countBy()``. Toma los mismos argumentos que ``groupBy``, así que ya debería ser familiar para ti:: + + $resultadosDeClase = $estudiantes->countBy(function ($estudiante) { + return $estudiante->grado > 6 ? 'aprobado' : 'denegado'; + }); + + // El resultado podría parecerse a esto cuando se convierte a un array: + ['aprobado' => 70, 'denegado' => 20] + +.. php:method:: indexBy($callback) + +Habrá casos en los que sepas que un elemento es único para la propiedad por la que deseas agrupar. Si deseas un solo resultado por grupo, puedes usar la función ``indexBy()``:: + + $usuariosPorId = $usuarios->indexBy('id'); + + // Cuando se convierte a un array, el resultado podría parecerse a + [ + 1 => 'markstory', + 3 => 'jose_zap', + 4 => 'jrbasso' + ] + +Al igual que la función ``groupBy()``, también puedes usar un camino de propiedad o una devolución de llamada:: + + $articulosPorIdDeAutor = $articulos->indexBy('autor.id'); + + $archivosPorHash = $archivos->indexBy(function ($archivo) { + return md5($archivo); + }); + +.. php:method:: zip($items) + +Los elementos de diferentes colecciones se pueden agrupar juntos usando el método ``zip()``. Devolverá una nueva colección que contiene una matriz que agrupa los elementos de cada colección que están ubicados en la misma posición:: + + $impares = new Collection([1, 3, 5]); + $pares = new Collection([2, 4, 6]); + $combinados = $impares->zip($pares)->toList(); // [[1, 2], [3, 4], [5, 6]] + +También puedes agrupar múltiples colecciones a la vez:: + + $años = new Collection([2013, 2014, 2015, 2016]); + $salarios = [1000, 1500, 2000, 2300]; + $incrementos = [0, 500, 500, 300]; + + $filas = $años->zip($salarios, $incrementos); + $resultado = $filas->toList(); + + // $resultado contiene + [ + [2013, 1000, 0], + [2014, 1500, 500], + [2015, 2000, 500], + [2016, 2300, 300] + ] + +Como ya puedes ver, el método ``zip()`` es muy útil para transponer matrices multidimensionales:: + + $datos = [ + 2014 => ['ene' => 100, 'feb' => 200], + 2015 => ['ene' => 300, 'feb' => 500], + 2016 => ['ene' => 400, 'feb' => 600], + ]; + + // Obteniendo datos de enero y febrero juntos + + $primerAño = new Collection(array_shift($datos)); + $resultado = $primerAño->zip($datos[0], $datos[1])->toList(); + + // O $primerAño->zip(...$datos) en PHP >= 5.6 + + // $resultado contiene + [ + [100, 300, 400], + [200, 500, 600] + ] + +Ordenación +========== + +.. php:method:: sortBy($callback, $order = SORT_DESC, $sort = SORT_NUMERIC) + +Los valores de una colección se pueden ordenar en orden ascendente o descendente basándose en una columna o función personalizada. Para crear una nueva colección ordenada a partir de los valores de otra, puedes usar ``sortBy``:: + + $colección = new Collection($personas); + $ordenada = $colección->sortBy('edad'); + +Como se muestra arriba, puedes ordenar pasando el nombre de una columna o propiedad que esté presente en los valores de la colección. También puedes especificar un camino de propiedad en lugar de usar la notación de punto. El siguiente ejemplo ordenará los artículos por el nombre de su autor:: + + $colección = new Collection($artículos); + $ordenada = $colección->sortBy('autor.nombre'); + +El método ``sortBy()`` es lo suficientemente flexible como para permitirte especificar una función de extracción que te permitirá seleccionar dinámicamente el valor a usar para comparar dos valores diferentes en la colección:: + + $colección = new Collection($artículos); + $ordenada = $colección->sortBy(function ($artículo) { + return $artículo->autor->nombre . '-' . $artículo->título; + }); + +Para especificar en qué dirección debe ordenarse la colección, debes proporcionar ya sea ``SORT_ASC`` o ``SORT_DESC`` como el segundo parámetro para ordenar en dirección ascendente o descendente, respectivamente. Por defecto, las colecciones se ordenan en dirección descendente:: + + $colección = new Collection($personas); + $ordenada = $colección->sortBy('edad', SORT_ASC); + +A veces necesitarás especificar qué tipo de datos estás tratando de comparar para obtener resultados consistentes. Para este propósito, debes suministrar un tercer argumento en la función ``sortBy()`` con una de las siguientes constantes: + +- **SORT_NUMERIC**: Para comparar números. +- **SORT_STRING**: Para comparar valores de cadena. +- **SORT_NATURAL**: Para ordenar cadenas que contienen números y deseas que esos números se ordenen de manera natural. Por ejemplo: mostrar "10" después de "2". +- **SORT_LOCALE_STRING**: Para comparar cadenas basadas en la configuración regional actual. + +Por defecto, se utiliza ``SORT_NUMERIC``:: + + $colección = new Collection($artículos); + $ordenada = $colección->sortBy('título', SORT_ASC, SORT_NATURAL); + +.. warning:: + + A menudo es costoso iterar sobre colecciones ordenadas más de una vez. Si planeas hacerlo, considera convertir la colección en un array o simplemente usa el método ``compile()`` en ella. + +Trabajando con Datos en Forma de Árbol +====================================== + +.. php:method:: nest($idPath, $parentPath, $nestingKey = 'children') + +No todos los datos están destinados a ser representados de manera lineal. Las colecciones facilitan la construcción y aplanamiento de estructuras jerárquicas o anidadas. Crear una estructura anidada donde los hijos están agrupados por una propiedad de identificación del padre se puede hacer con el método ``nest()``. + +Se requieren dos parámetros para esta función. El primero es la propiedad que representa la identificación del elemento. El segundo parámetro es el nombre de la propiedad que representa la identificación del elemento padre:: + + $colección = new Collection([ + ['id' => 1, 'parent_id' => null, 'nombre' => 'Aves'], + ['id' => 2, 'parent_id' => 1, 'nombre' => 'Aves Terrestres'], + ['id' => 3, 'parent_id' => 1, 'nombre' => 'Águila'], + ['id' => 4, 'parent_id' => 1, 'nombre' => 'Gaviota'], + ['id' => 5, 'parent_id' => 6, 'nombre' => 'Pez Payaso'], + ['id' => 6, 'parent_id' => null, 'nombre' => 'Peces'], + ]); + $anidada = $colección->nest('id', 'parent_id'); + $resultado = $anidada->toList(); + + // $resultado contiene + [ + [ + 'id' => 1, + 'parent_id' => null, + 'nombre' => 'Aves', + 'children' => [ + ['id' => 2, 'parent_id' => 1, 'nombre' => 'Aves Terrestres', 'children' => []], + ['id' => 3, 'parent_id' => 1, 'nombre' => 'Águila', 'children' => []], + ['id' => 4, 'parent_id' => 1, 'nombre' => 'Gaviota', 'children' => []], + ], + ], + [ + 'id' => 6, + 'parent_id' => null, + 'nombre' => 'Peces', + 'children' => [ + ['id' => 5, 'parent_id' => 6, 'nombre' => 'Pez Payaso', 'children' => []], + ], + ], + ]; + +Los elementos hijos están anidados dentro de la propiedad ``children`` dentro de cada uno de los elementos en la colección. Este tipo de representación de datos es útil para renderizar menús o recorrer elementos hasta cierto nivel en el árbol. + +.. php:method:: listNested($order = 'desc', $nestingKey = 'children') + +El inverso de ``nest()`` es ``listNested()``. Este método te permite aplanar una estructura de árbol de nuevo en una estructura lineal. Toma dos parámetros; el primero es el modo de recorrido (asc, desc o leaves), y el segundo es el nombre de la propiedad que contiene los hijos para cada elemento en la colección. + +Tomando la entrada de la colección anidada construida en el ejemplo anterior, podemos aplanarla:: + + $resultado = $anidada->listNested()->toList(); + + // $resultado contiene + [ + ['id' => 1, 'parent_id' => null, 'nombre' => 'Aves', 'children' => [...]], + ['id' => 2, 'parent_id' => 1, 'nombre' => 'Aves Terrestres'], + ['id' => 3, 'parent_id' => 1, 'nombre' => 'Águila'], + ['id' => 4, 'parent_id' => 1, 'nombre' => 'Gaviota'], + ['id' => 6, 'parent_id' => null, 'nombre' => 'Peces', 'children' => [...]], + ['id' => 5, 'parent_id' => 6, 'nombre' => 'Pez Payaso'] + ] + +De forma predeterminada, el árbol se recorre desde la raíz hasta las hojas. También puedes indicar que solo devuelva los elementos hoja en el árbol:: + + $resultado = $anidada->listNested('leaves')->toList(); + + // $resultado contiene + [ + ['id' => 2, 'parent_id' => 1, 'nombre' => 'Aves Terrestres', 'children' => [], ], + ['id' => 3, 'parent_id' => 1, 'nombre' => 'Águila', 'children' => [], ], + ['id' => 4, 'parent_id' => 1, 'nombre' => 'Gaviota', 'children' => [], ], + ['id' => 5, 'parent_id' => 6, 'nombre' => 'Pez Payaso', 'children' => [], ], + ] + + +Una vez que has convertido un árbol en una lista anidada, puedes usar el método ``printer()`` +para configurar cómo debe formatearse la salida de la lista:: + + $resultado = $anidada->listNested()->printer('nombre', 'id', '--')->toArray(); + + // $resultado contiene + [ + 1 => 'Aves', + 2 => '--Aves Terrestres', + 3 => '--Águila', + 4 => '--Gaviota', + 6 => 'Peces', + 5 => '--Pez Payaso', + ] + +El método ``printer()`` también te permite usar una devolución de llamada para generar las claves y/o valores:: + + $anidada->listNested()->printer( + function ($el) { + return $el->nombre; + }, + function ($el) { + return $el->id; + } + ); + +Otros Métodos +============= + +.. php:method:: isEmpty() + +Te permite ver si una colección contiene algún elemento:: + + $colección = new Collection([]); + // Devuelve true + $colección->isEmpty(); + + $colección = new Collection([1]); + // Devuelve false + $colección->isEmpty(); + +.. php:method:: contains($value) + +Las colecciones te permiten verificar rápidamente si contienen un valor en particular: utilizando el método ``contains()``:: + + $elementos = ['a' => 1, 'b' => 2, 'c' => 3]; + $colección = new Collection($elementos); + $tieneTres = $colección->contains(3); + +Las comparaciones se realizan utilizando el operador ``===``. Si deseas realizar comparaciones menos estrictas, puedes usar el método ``some()``. + +.. php:method:: shuffle() + +A veces puede que desees mostrar una colección de valores en un orden aleatorio. Para crear una nueva colección que devolverá cada valor en una posición aleatoria, utiliza ``shuffle``:: + + $colección = new Collection(['a' => 1, 'b' => 2, 'c' => 3]); + + // Esto podría devolver [2, 3, 1] + $colección->shuffle()->toList(); + +.. php:method:: transpose() + +Cuando transpones una colección, obtienes una nueva colección que contiene una fila hecha de cada una de las columnas originales:: + + $elementos = [ + ['Productos', '2012', '2013', '2014'], + ['Producto A', '200', '100', '50'], + ['Producto B', '300', '200', '100'], + ['Producto C', '400', '300', '200'], + ]; + $transpuesta = (new Collection($elementos))->transpose(); + $resultado = $transpuesta->toList(); + + // $resultado contiene + [ + ['Productos', 'Producto A', 'Producto B', 'Producto C'], + ['2012', '200', '300', '400'], + ['2013', '100', '200', '300'], + ['2014', '50', '100', '200'], + ] + +Extracción de Elementos +----------------------- + +.. php:method:: sample($length = 10) + +Barajar una colección a menudo es útil al realizar un análisis estadístico rápido. Otra operación común al hacer este tipo de tarea es extraer algunos valores aleatorios de una colección para realizar más pruebas con ellos. Por ejemplo, si quisieras seleccionar 5 usuarios aleatorios a los que le aplicarás algunas pruebas A/B, puedes usar la función ``sample()``:: + + $colección = new Collection($personas); + + // Extraer como máximo 20 usuarios aleatorios de esta colección + $sujetosPrueba = $colección->sample(20); + +``sample()`` tomará como máximo el número de valores que especifiques en el primer +argumento. Si no hay suficientes elementos en la colección para satisfacer la +muestra, se devolverá la colección completa en un orden aleatorio. + +.. php:method:: take($length, $offset) + +Cuando desees tomar un segmento de una colección, utiliza la función ``take()``, creará una nueva colección con como máximo el número de valores que especifiques en el primer argumento, comenzando desde la posición indicada en el segundo argumento:: + + $cincoPrimeros = $colección->sortBy('edad')->take(5); + + // Tomar 5 personas de la colección empezando desde la posición 4 + $próximosCinco = $colección->sortBy('edad')->take(5, 4); + +Las posiciones son de base cero, por lo tanto, el primer número de posición es ``0``. + +.. php:method:: skip($length) + +Mientras que el segundo argumento de ``take()`` puede ayudarte a saltar algunos elementos antes de obtenerlos de la colección, también puedes usar ``skip()`` para el mismo propósito como una manera de tomar el resto de los elementos después de cierta posición:: + + $colección = new Collection([1, 2, 3, 4]); + $todosExceptoPrimerosDos = $colección->skip(2)->toList(); // [3, 4] + +.. php:method:: first() + +Uno de los usos más comunes de ``take()`` es obtener el primer elemento en la colección. Un método abreviado para lograr el mismo objetivo es usando el método ``first()``:: + + $colección = new Collection([5, 4, 3, 2]); + $colección->first(); // Devuelve 5 + +.. php:method:: last() + +De manera similar, puedes obtener el último elemento de una colección usando el método ``last()``:: + + $colección = new Collection([5, 4, 3, 2]); + $colección->last(); // Devuelve 2 + +Expansión de Colecciones +------------------------ + +.. php:method:: append(array|Traversable $items) + +Puedes componer múltiples colecciones en una sola. Esto te permite recopilar datos de diversas fuentes, concatenarlos y aplicar otras funciones de colección de manera muy + + fluida. El método ``append()`` devolverá una nueva colección que contiene los valores de ambas fuentes:: + + $tweetsCakePHP = new Collection($tweets); + $miLíneaDeTiempo = $tweetsCakePHP->append($tweetsPHP); + + // Tweets que contienen `cakefest` de ambas fuentes + $miLíneaDeTiempo->filter(function ($tweet) { + return strpos($tweet, 'cakefest'); + }); + +.. php:method:: appendItem($value, $key) + +Te permite agregar un elemento con una clave opcional a la colección. Si especificas una clave que ya existe en la colección, el valor no se sobrescribirá:: + + $tweetsCakePHP = new Collection($tweets); + $miLíneaDeTiempo = $tweetsCakePHP->appendItem($nuevoTweet, 99); + +.. php:method:: prepend($items) + +El método ``prepend()`` devolverá una nueva colección que contiene los valores de ambas fuentes:: + + $tweetsCakePHP = new Collection($tweets); + $miLíneaDeTiempo = $tweetsCakePHP->prepend($tweetsPHP); + +.. php:method:: prependItem($value, $key) + +Te permite agregar un elemento con una clave opcional a la colección. Si especificas una clave que ya existe en la colección, el valor no se sobrescribirá:: + + $tweetsCakePHP = new Collection($tweets); + $miLíneaDeTiempo = $tweetsCakePHP->prependItem($nuevoTweet, 99); + +.. warning:: + + Al agregar desde diferentes fuentes, puedes esperar que algunas claves de ambas + colecciones sean iguales. Por ejemplo, al agregar dos arrays simples. + Esto puede presentar un problema al convertir una colección a un array usando + ``toArray()``. Si no quieres que los valores de una colección sobrescriban + a otros en la colección anterior basándote en su clave, asegúrate de llamar + a ``toList()`` para eliminar las claves y preservar todos los valores. + +Modificación de Elementos +------------------------- + +.. php:method:: insert($path, $items) + +En ocasiones, es posible que tengas dos conjuntos de datos separados que te gustaría insertar en cada uno de los elementos del otro conjunto. Este es un caso muy común cuando obtienes datos de una fuente de datos que no admite combinación de datos o uniones de forma nativa. + +Las colecciones ofrecen un método ``insert()`` que te permitirá insertar cada uno de los elementos de una colección en una propiedad dentro de cada uno de los elementos de otra colección:: + + $usuarios = [ + ['username' => 'mark'], + ['username' => 'juan'], + ['username' => 'jose'] + ]; + + $lenguajes = [ + ['PHP', 'Python', 'Ruby'], + ['Bash', 'PHP', 'Javascript'], + ['Javascript', 'Prolog'] + ]; + + $fusionado = (new Collection($usuarios))->insert('skills', $lenguajes); + $resultado = $fusionado->toArray(); + + // $resultado contiene + [ + ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], + ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], + ['username' => 'jose', 'skills' => ['Javascript', 'Prolog']] + ]; + +El primer parámetro para el método ``insert()`` es una ruta de propiedades separada por puntos para seguir, de modo que los elementos se puedan insertar en esa posición. El segundo argumento puede ser cualquier cosa que se pueda convertir en un objeto de colección. + +Observa que los elementos se insertan en la posición en la que se encuentran, por lo tanto, el primer elemento del segundo conjunto se fusiona en el primer elemento del primer conjunto. + +Si no hay suficientes elementos en el segundo conjunto para insertar en el primero, entonces la propiedad de destino no estará presente:: + + $lenguajes = [ + ['PHP', 'Python', 'Ruby'], + ['Bash', 'PHP', 'Javascript'] + ]; + + $fusionado = (new Collection($usuarios))->insert('skills', $lenguajes); + $resultado = $fusionado->toArray(); + + // $resultado contiene + [ + ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], + ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], + ['username' => 'jose'] + ]; + +El método ``insert()`` puede operar en elementos de arrays u objetos que implementen la interfaz ``ArrayAccess``. + +Haciendo Métodos de Colección Reutilizables +------------------------------------------- + +El uso de cierres para los métodos de colección es excelente cuando el trabajo a realizar es pequeño y enfocado, pero puede volverse desordenado rápidamente. Esto se hace más evidente cuando se deben llamar a muchos métodos diferentes o cuando la longitud de los métodos de cierre es más que solo unas pocas líneas. + +También hay casos en los que la lógica utilizada para los métodos de colección se puede reutilizar en varias partes de tu aplicación. Se recomienda considerar la posibilidad de extraer la lógica de colección compleja a clases separadas. Por ejemplo, imagina un cierre extenso como este:: + + $colección + ->map(function ($fila, $clave) { + if (!empty($fila['items'])) { + $fila['total'] = collection($fila['items'])->sumOf('price'); + } + + if (!empty($fila['total'])) { + $fila['tax_amount'] = $fila['total'] * 0.25; + } + + // Más código aquí... + + return $filaModificada; + }); + +Esto se puede refactorizar creando otra clase:: + + class TotalOrderCalculator + { + public function __invoke($fila, $clave) + { + if (!empty($fila['items'])) { + $fila['total'] = collection($fila['items'])->sumOf('price'); + } + + if (!empty($fila['total'])) { + $fila['tax_amount'] = $fila['total'] * 0.25; + } + + // Más código aquí... + + return $filaModificada; + } + } + + // Utiliza la lógica en tu llamada map() + $colección->map(new TotalOrderCalculator) + +.. php:method:: through($callback) + +A veces, una cadena de llamadas a métodos de colección puede volverse reutilizable en otras partes de tu aplicación, pero solo si se llaman en ese orden específico. En esos casos, puedes usar ``through()`` en combinación con una clase que implemente ``__invoke`` para distribuir tus llamadas útiles de procesamiento de datos:: + + $colección + ->map(new ShippingCostCalculator) + ->map(new TotalOrderCalculator) + ->map(new GiftCardPriceReducer) + ->buffered() + ... + +Las llamadas a métodos anteriores se pueden extraer a una nueva clase para que no necesiten repetirse cada vez:: + + class FinalCheckOutRowProcessor + { + public function __invoke($colección) + { + return $colección + ->map(new ShippingCostCalculator) + ->map(new TotalOrderCalculator) + ->map(new GiftCardPriceReducer) + ->buffered() + ... + } + } + + // Ahora puedes usar el método through() para llamar a todos los métodos a la vez + $colección->through(new FinalCheckOutRowProcessor); + +Optimización de Colecciones +--------------------------- + +.. php:method:: buffered() + +A menudo, las colecciones realizan la mayoría de las operaciones que creas usando sus funciones de manera perezosa. Esto significa que, aunque puedas llamar a una función, no significa que se ejecute de inmediato. Esto es cierto para una gran cantidad de funciones en esta clase. La evaluación perezosa te permite ahorrar recursos en situaciones en las que no usas todos los valores en una colección. Es posible que no uses todos los valores cuando la iteración se detiene temprano o cuando se alcanza un caso de excepción/error temprano. + +Además, la evaluación perezosa ayuda a acelerar algunas operaciones. Considera el siguiente ejemplo:: + + $colección = new Collection($unMillonDeElementos); + $colección = $colección->map(function ($elemento) { + return $elemento * 2; + }); + $elementosAMostrar = $colección->take(30); + +Si las colecciones no fueran perezosas, habríamos ejecutado un millón de operaciones, aunque solo quisiéramos mostrar 30 elementos. En cambio, nuestra operación de map se aplicó solo a los 30 elementos que usamos. También podemos obtener beneficios de esta evaluación perezosa para colecciones + + más pequeñas cuando realizamos más de una operación en ellas. Por ejemplo: llamando a ``map()`` dos veces y luego ``filter()``. + +La evaluación perezosa también tiene sus inconvenientes. Podrías estar realizando las mismas operaciones más de una vez si optimizas una colección prematuramente. Considera este ejemplo:: + + $edades = $colección->extract('age'); + + $menorDe30 = $edades->filter(function ($elemento) { + return $elemento < 30; + }); + + $mayorDe30 = $edades->filter(function ($elemento) { + return $elemento > 30; + }); + +Si iteramos tanto ``menorDe30`` como ``mayorDe30``, lamentablemente, la colección ejecutaría la operación ``extract()`` dos veces. Esto se debe a que las colecciones son inmutables y la operación de extracción perezosa se realizaría para ambos filtros. + +Afortunadamente, podemos superar este problema con una sola función. Si planeas reutilizar los valores de ciertas operaciones más de una vez, puedes compilar los resultados en otra colección usando la función ``buffered()``:: + + $edades = $colección->extract('age')->buffered(); + $menorDe30 = ... + $mayorDe30 = ... + +Ahora, cuando ambas colecciones se iteran, solo llamarán a la operación de extracción una vez. + +Haciendo Colecciones Reiniciables +--------------------------------- + +El método ``buffered()`` también es útil para convertir iteradores no reiniciables en colecciones que se pueden iterar más de una vez:: + + // En PHP 5.5+ + public function results() + { + ... + foreach ($elementosTransitorios as $e) { + yield $e; + } + } + $reinicable = (new Collection(results()))->buffered(); + +Clonación de Colecciones +------------------------ + +.. php:method:: compile($preserveKeys = true) -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. +A veces necesitas obtener un clon de los elementos de otra colección. Esto es útil cuando necesitas iterar el mismo conjunto desde lugares diferentes al mismo tiempo. Para clonar una colección a partir de otra, utiliza el método ``compile()``:: - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. + $edades = $colección->extract('age')->compile(); - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. + foreach ($edades as $edad) { + foreach ($colección as $elemento) { + echo h($elemento->name) . ' - ' . $edad; + } + } .. meta:: - :title lang=es: Collections - :keywords lang=es: collections, cakephp, append, sort, compile, contains, countBy, each, every, extract, filter, first, firstMatch, groupBy, indexBy, jsonSerialize, map, match, max, min, reduce, reject, sample, shuffle, some, random, sortBy, take, toArray, insert + :title lang=es: Colecciones + :keywords lang=es: colecciones, cakephp, anexar, ordenar, compilar, contiene, contarPor, cada, todos, extraer, filtrar, primero, primerCoincidencia, agruparPor, indexarPor, jsonSerialize, map, coincidir, máximo, mínimo, reducir, rechazar, muestra, mezclar, algunos, aleatorio, ordenarPor, tomar, toArray, insertar diff --git a/es/core-libraries/events.rst b/es/core-libraries/events.rst index 99ad4eff40..ffced708fb 100644 --- a/es/core-libraries/events.rst +++ b/es/core-libraries/events.rst @@ -1,15 +1,393 @@ -Events System -############# +Sistema de eventos +################## + +Crear aplicaciones mantenibles es tanto una ciencia como un arte. Es bien sabido que una clave para tener un código de buena calidad es hacer que tus objetos estén débilmente acoplados y fuertemente cohesivos al mismo tiempo. La cohesión significa que todos los métodos y propiedades de una clase están fuertemente relacionados con la clase en sí y no están tratando de hacer el trabajo que deberían hacer otros objetos, mientras que el acoplamiento débil es la medida de cuán "conectada" está una clase a objetos externos y cuánto depende de ellos. + +Existen casos particulares en los que necesitas comunicarte de manera limpia con otras partes de una aplicación, sin tener que codificar dependencias de manera rígida, perdiendo así cohesión e incrementando el acoplamiento de clases. Utilizar el patrón Observer, que permite que los objetos notifiquen a otros objetos y a oyentes anónimos sobre cambios, es un patrón útil para lograr este objetivo. + +Los oyentes en el patrón Observer pueden suscribirse a eventos y elegir actuar sobre ellos si son relevantes. Si has utilizado JavaScript, es probable que ya estés familiarizado con la programación basada en eventos. + +CakePHP emula varios aspectos de cómo se desencadenan y gestionan eventos en bibliotecas populares de JavaScript como jQuery. En la implementación de CakePHP, se despacha un objeto de evento a todos los oyentes. El objeto de evento contiene información sobre el evento y proporciona la capacidad de detener la propagación del evento en cualquier momento. Los oyentes pueden registrarse ellos mismos o pueden delegar esta tarea a otros objetos y tienen la oportunidad de alterar el estado y el propio evento para el resto de las devoluciones de llamada. + +El subsistema de eventos es el corazón de las devoluciones de llamada del Modelo, Comportamiento, Controlador, Vista y Ayudante. Si alguna vez has utilizado alguno de ellos, ya estás algo familiarizado con los eventos en CakePHP. + +Uso Ejemplar de Eventos +======================= + +Supongamos que estás construyendo un complemento de Carrito y te gustaría centrarte solo en manejar la lógica de pedidos. Realmente no quieres incluir la lógica de envío, enviar correos electrónicos al usuario o disminuir el ítem del stock, pero estas son tareas importantes para las personas que utilizan tu complemento. Si no estuvieras usando eventos, podrías intentar implementar esto adjuntando comportamientos a modelos o agregando componentes a tus controladores. Hacerlo representa un desafío la mayor parte del tiempo, ya que tendrías que idear el código para cargar externamente esos comportamientos o adjuntar ganchos a tus controladores de complementos. + +En cambio, puedes usar eventos para permitirte separar limpiamente las preocupaciones de tu código y permitir que preocupaciones adicionales se conecten a tu complemento mediante eventos. Por ejemplo, en tu complemento de Carrito tienes un modelo Orders que se encarga de crear pedidos. Te gustaría notificar al resto de la aplicación que se ha creado un pedido. Para mantener limpio tu modelo Orders, podrías usar eventos:: + + // Cart/Model/Table/OrdersTable.php + namespace Cart\Model\Table; + + use Cake\Event\Event; + use Cake\ORM\Table; + + class OrdersTable extends Table + { + public function place($order) + { + if ($this->save($order)) { + $this->Cart->remove($order); + $event = new Event('Order.afterPlace', $this, [ + 'order' => $order + ]); + $this->getEventManager()->dispatch($event); + return true; + } + return false; + } + } + +El código anterior te permite notificar a otras partes de la aplicación que se ha creado un pedido. Luego puedes realizar tareas como enviar notificaciones por correo electrónico, actualizar el stock, registrar estadísticas relevantes y otras tareas en objetos separados que se centran en esas preocupaciones. + +Acceso a los Gestores de Eventos +================================ + +En CakePHP, los eventos se desencadenan contra gestores de eventos. Los gestores de eventos están disponibles en cada Table, View y Controller mediante ``getEventManager()``:: + + $events = $this->getEventManager(); + +Cada modelo tiene un gestor de eventos separado, mientras que la Vista y el Controlador comparten uno. Esto permite que los eventos del modelo sean independientes y permitan que componentes o controladores actúen sobre eventos creados en la vista si es necesario. + +Gestor de Eventos Global +------------------------ + +Además de los gestores de eventos a nivel de instancia, CakePHP proporciona un gestor de eventos global que te permite escuchar cualquier evento disparado en una aplicación. Esto es útil cuando adjuntar oyentes a una instancia específica puede ser engorroso o difícil. El gestor global es una instancia única de :php:class:`Cake\\Event\\EventManager`. Los oyentes adjuntos al despachador global se activarán antes que los oyentes de instancia con la misma prioridad. Puedes acceder al gestor global mediante un método estático:: + + // En cualquier archivo de configuración o fragmento de código que se ejecute antes del evento + use Cake\Event\EventManager; + + EventManager::instance()->on( + 'Order.afterPlace', + $unCallback + ); + +Una cosa importante que debes considerar es que hay eventos que se dispararán con el mismo nombre pero con sujetos diferentes, por lo que generalmente se requiere verificarlo en el objeto de evento en cualquier función que se adjunte globalmente para evitar algunos errores. Recuerda que con la flexibilidad de usar el gestor global, se incurre en cierta complejidad adicional. + +El método :php:meth:`Cake\\Event\\EventManager::dispatch()` acepta el objeto de evento como argumento y notifica a todos los oyentes y devoluciones de llamada pasando este objeto. + +.. _seguimiento-eventos: + +Seguimiento de Eventos +---------------------- + +Para mantener una lista de eventos que se disparan en un determinado ``EventManager``, puedes habilitar el seguimiento de eventos. Para hacerlo, simplemente adjunta una :php:class:`Cake\\Event\\EventList` al gestor:: + + EventManager::instance()->setEventList(new EventList()); + +Después de disparar un evento en el gestor, puedes recuperarlo de la lista de eventos:: + + $eventosDisparados = EventManager::instance()->getEventList(); + $primerEvento = $eventosDisparados[0]; + +El seguimiento se puede desactivar eliminando la lista de eventos o llamando a :php:meth:`Cake\\Event\\EventList::trackEvents(false)`. + +Eventos Principales +=================== + +Hay una serie de eventos principales dentro del framework a los que tu aplicación puede escuchar. Cada capa de CakePHP emite eventos que puedes utilizar en tu aplicación. + +* :ref:`Eventos de ORM/Modelo ` +* :ref:`Eventos de Controlador ` +* :ref:`Eventos de Vista ` + +.. _registro-oyentes-eventos: + +Registro de Oyentes +=================== + +Los oyentes son la forma preferida de registrar devoluciones de llamada para un evento. Esto se hace implementando la interfaz :php:class:`Cake\\Event\\EventListenerInterface` en cualquier clase que desees registrar algunas devoluciones de llamada. Las clases que la implementen deben proporcionar el método ``implementedEvents()``. Este método debe devolver una matriz asociativa con todos los nombres de eventos que la clase manejará. + +Para continuar con nuestro ejemplo anterior, imaginemos que tenemos una clase UserStatistic encargada de calcular el historial de compras de un usuario y compilar estadísticas globales del sitio. Este es un buen lugar para usar una clase oyente. Al hacerlo, puedes concentrar la lógica de estadísticas en un solo lugar y reaccionar a eventos según sea necesario. Nuestro oyente ``UserStatistics`` podría comenzar así:: + + namespace App\Event; + + use Cake\Event\EventListenerInterface; + + class UserStatistic implements EventListenerInterface + { + public function implementedEvents(): array + { + return [ + // Nombres de eventos personalizados te permiten diseñar los eventos de tu aplicación + // según sea necesario. + 'Order.afterPlace' => 'updateBuyStatistic', + ]; + } + + public function updateBuyStatistic($event) + { + // Código para actualizar las estadísticas + } + } + + // Desde tu controlador, adjunta el objeto UserStatistic al gestor de eventos del Pedido + $estadisticas = new UserStatistic(); + $this->Orders->getEventManager()->on($estadisticas); + +Como puedes ver en el código anterior, la función ``on()`` aceptará instancias de la interfaz ``EventListener``. Internamente, el gestor de eventos utilizará ``implementedEvents()`` para adjuntar las devoluciones de llamada correctas. + +Registro de Oyentes Anónimos +---------------------------- + +Si bien los objetos de oyente de eventos son generalmente una mejor manera de implementar oyentes, también puedes vincular cualquier "callable" como un oyente de eventos. Por ejemplo, si quisiéramos poner todos los pedidos en los archivos de registro, podríamos usar una función anónima simple para hacerlo:: + + use Cake\Log\Log; + + // Desde un controlador, o durante el inicio de la aplicación. + $this->Orders->getEventManager()->on('Order.afterPlace', function ($event) { + Log::write( + 'info', + 'Se realizó un nuevo pedido con id: ' . $event->getSubject()->id + ); + }); + +Además de las funciones anónimas, puedes usar cualquier otro tipo "callable" que PHP admita:: + + $eventos = [ + 'envio-correo' => 'EmailSender::sendBuyEmail', + 'inventario' => [$this->InventoryManager, 'decrementar'], + ]; + foreach ($eventos as $llamable) { + $gestorEventos->on('Order.afterPlace', $llamable); + } + +Cuando trabajas con complementos que no desencadenan eventos específicos, puedes aprovechar los oyentes de eventos en los eventos predeterminados. Tomemos un ejemplo de un complemento 'UserFeedback' que maneja formularios de retroalimentación de usuarios. Desde tu aplicación, te gustaría saber cuándo se ha guardado un registro de retroalimentación y, en última instancia, actuar en consecuencia. Puedes escuchar el evento global ``Model.afterSave``. Sin embargo, también puedes tomar un enfoque más directo y escuchar solo el evento que realmente necesitas:: + + // Puedes crear lo siguiente antes de la + // operación de guardado, es decir, config/bootstrap.php + use Cake\Datasource\FactoryLocator; + // Si se envían correos electrónicos + use Cake\Mailer\Email; + + FactoryLocator::get('Table')->get('ThirdPartyPlugin.Feedbacks') + ->getEventManager() + ->on('Model.afterSave', function($event, $entity) + { + // Por ejemplo, podemos enviar un correo electrónico al administrador + $correo = new Email('default'); + $correo->setFrom(['info@tusitio.com' => 'Tu Sitio']) + ->setTo('admin@tusitio.com') + ->setSubject('Nueva Retroalimentación - Tu Sitio') + ->send('Cuerpo del mensaje'); + }); + +Puedes usar este mismo enfoque para vincular objetos de oyente. + +Interactuar con Oyentes Existentes +---------------------------------- + +Supongamos que se han registrado varios oyentes de eventos y se desea realizar alguna acción basada en la presencia o ausencia de un patrón de evento en particular:: + + // Adjuntar oyentes al gestor de eventos. + $this->getEventManager()->on('User.Registration', [$this, 'userRegistration']); + $this->getEventManager()->on('User.Verification', [$this, 'userVerification']); + $this->getEventManager()->on('User.Authorization', [$this, 'userAuthorization']); + + // En otro lugar de tu aplicación. + $eventos = $this->getEventManager()->matchingListeners('Verification'); + if (!empty($eventos)) { + // Realizar lógica relacionada con la presencia del oyente 'Verification'. + // Por ejemplo, eliminar el oyente si está presente. + $this->getEventManager()->off('User.Verification'); + } else { + // Realizar lógica relacionada con la ausencia del oyente 'Verification'. + } .. note:: - La documentación no es compatible actualmente con el idioma español en esta página. - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. + El patrón pasado al método ``matchingListeners`` es sensible a mayúsculas y minúsculas. + +.. _prioridades-eventos: + +Establecimiento de Prioridades +------------------------------ + +En algunos casos, es posible que desees controlar el orden en que se invocan los oyentes. Por ejemplo, si volvemos a nuestro ejemplo de estadísticas de usuario, sería ideal que este oyente se llamara al final de la pila. Al colocarlo al final de la pila de oyentes, podemos asegurarnos de que el evento no se haya cancelado y de que ningún otro oyente haya generado excepciones. También podemos obtener el estado final de los objetos en el caso de que otros oyentes hayan modificado el objeto o el objeto del evento. + +Las prioridades se definen como un número entero al agregar un oyente. Cuanto mayor sea el número, más tarde se ejecutará el método. La prioridad predeterminada para todos los oyentes es ``10``. Si necesitas que tu método se ejecute antes, cualquier valor por debajo de esta prioridad predeterminada funcionará. Por otro lado, si deseas que la devolución de llamada se ejecute después de las demás, utilizar un número superior a ``10`` funcionará. + +Si dos devoluciones de llamada tienen el mismo valor de prioridad, se ejecutarán en el orden en que se adjuntaron. Puedes establecer prioridades utilizando el método ``on()`` para devoluciones de llamada y declarándolo en la función ``implementedEvents()`` para los objetos de escucha de eventos:: + + // Estableciendo prioridad para una devolución de llamada + $devolucionLlamada = [$this, 'hacerAlgo']; + $this->getEventManager()->on( + 'Order.afterPlace', + ['prioridad' => 2], + $devolucionLlamada + ); + + // Estableciendo prioridad para un objeto de escucha + class EstadisticaUsuario implements EventListenerInterface + { + public function implementedEvents() + { + return [ + 'Order.afterPlace' => [ + 'callable' => 'actualizarEstadisticasCompra', + 'prioridad' => 100 + ], + ]; + } + } + +Como puedes ver, la principal diferencia para los objetos ``EventListener`` es que necesitas usar un array para especificar el método llamable y la preferencia de prioridad. La clave ``callable`` es una entrada especial de array que el gestor leerá para saber qué función de la clase debe llamar. + +Obtención de Datos de Evento como Parámetros de Función +------------------------------------------------------- + +Cuando los eventos tienen datos proporcionados en su constructor, los datos proporcionados se convierten en argumentos para los oyentes. Un ejemplo de la capa de Vista es la devolución de llamada ``afterRender``:: + + $this->getEventManager() + ->dispatch(new Event('View.afterRender', $this, ['vista' => $nombreArchivoVista])); + +Los oyentes de la devolución de llamada ``View.afterRender`` deberían tener la siguiente firma:: + + function (EventInterface $event, $nombreArchivoVista) + +Cada valor proporcionado al constructor de Evento se convertirá en parámetros de función en el orden en que aparecen en la matriz de datos. Si utilizas un array asociativo, el resultado de ``array_values`` determinará el orden de los argumentos de la función. + +Despachando Eventos +=================== + +Una vez que hayas obtenido una instancia de un gestor de eventos, puedes despachar eventos utilizando :php:meth:`~Cake\\Event\\EventManager::dispatch()`. Este método toma una instancia de la clase :php:class:`Cake\\Event\\Event`. Veamos cómo se despacha un evento: + +:: + + // Una escucha de eventos debe ser instanciado antes de despachar un evento. + // Crea un nuevo evento y lanzalo. + $event = new Event('Order.afterPlace', $this, ['order' => $order]); + $this->getEventManager()->dispatch($event); + +:php:class:`Cake\\Event\\Event` acepta 3 argumentos en su constructor. El primero es el nombre del evento; debes tratar de mantener este nombre tan único como sea posible, al mismo tiempo que lo haces legible. Sugerimos una convención como sigue: ``Capa.nombreEvento`` para eventos generales que ocurren a nivel de capa (por ejemplo, ``Controller.startup``, ``View.beforeRender``) y ``Capa.Clase.nombreEvento`` para eventos que ocurren en clases específicas en una capa, por ejemplo, ``Modelo.Usuario.despuesRegistro`` o ``Controlador.Cursos.accesoInvalido``. + +El segundo argumento es el ``sujeto``, lo que significa el objeto asociado al evento; generalmente, cuando es la misma clase la que dispara eventos sobre sí misma, usar ``$this`` será el caso más común. Aunque un componente también podría activar eventos de controlador. La clase del sujeto es importante porque los escuchas tendrán acceso inmediato a las propiedades del objeto y tendrán la oportunidad de inspeccionarlas o cambiarlas sobre la marcha. + +Finalmente, el tercer argumento es cualquier dato adicional del evento. Esto puede ser cualquier dato que consideres útil pasar para que los escuchas puedan actuar sobre él. Aunque esto puede ser un argumento de cualquier tipo, recomendamos pasar un array asociativo. + +El método :php:meth:`~Cake\\Event\\EventManager::dispatch()` acepta un objeto de evento como argumento y notifica a todos los escuchas suscritos. + +.. _stopping-events: + +Deteniendo Eventos +------------------ + +Al igual que con los eventos del DOM, es posible que desees detener un evento para evitar que se notifiquen oyentes adicionales. Puedes ver esto en acción durante las devoluciones de llamada del modelo (por ejemplo, antes de guardar), donde es posible detener la operación de guardado si el código detecta que no puede continuar. + +Para detener eventos, puedes devolver ``false`` en tus devoluciones de llamada o llamar al método ``stopPropagation()`` en el objeto de evento:: + + public function hacerAlgo($evento) + { + // ... + return false; // Detiene el evento + } + + public function actualizarEstadisticasCompra($evento) + { + // ... + $evento->stopPropagation(); + } + +Detener un evento evitará que se llamen a devoluciones de llamada adicionales. Además, el código que activa el evento puede comportarse de manera diferente según si el evento se detuvo o no. En general, no tiene sentido detener eventos 'después', pero detener eventos 'antes' se usa a menudo para evitar que ocurra toda la operación. + +Para verificar si se detuvo un evento, puedes llamar al método ``isStopped()`` en el objeto de evento:: + + public function realizarPedido($orden) + { + $evento = new Event('Order.beforePlace', $this, ['order' => $orden]); + $this->getEventManager()->dispatch($evento); + if ($evento->isStopped()) { + return false; + } + if ($this->Orders->save($orden)) { + // ... + } + // ... + } + +En el ejemplo anterior, la orden no se guardaría si el evento se detiene durante el proceso ``beforePlace``. + +Obteniendo Resultados de Eventos +-------------------------------- + +Cada vez que una devolución de llamada devuelve un valor no nulo ni falso, se almacena en la propiedad ``$result`` del objeto de evento. Esto es útil cuando deseas permitir que las devoluciones de llamada modifiquen la ejecución del evento. Volvamos a nuestro ejemplo de ``beforePlace`` y permitamos que las devoluciones de llamada modifiquen los datos de ``$order``. + +Los resultados del evento se pueden modificar tanto usando directamente la propiedad de resultado del objeto de evento como devolviendo el valor en la devolución de llamada en sí:: + + // Una devolución de llamada de un escucha + public function hacerAlgo($evento) + { + // ... + $datosModificados = $evento->getData('order') + $masDatos; + return $datosModificados; + } + + // Otra devolución de llamada de un escucha + public function hacerOtraCosa($evento) + { + // ... + $evento->setResult(['order' => $datosModificados] + $this->result()); + } + + // Utilizando el resultado del evento + public function realizarPedido($orden) + { + $evento = new Event('Order.beforePlace', $this, ['order' => $orden]); + $this->getEventManager()->dispatch($evento); + if (!empty($evento->getResult()['order'])) { + $orden = $evento->getResult()['order']; + } + if ($this->Orders->save($orden)) { + // ... + } + // ... + } + +Es posible modificar cualquier propiedad del objeto de evento y pasar los nuevos datos a la siguiente devolución de llamada. En la mayoría de los casos, proporcionar objetos como datos o resultado del evento y modificar directamente el objeto es la mejor solución, ya que la referencia se mantiene igual y las modificaciones se comparten en todas las llamadas de devolución de llamada. + +Eliminación de Devoluciones de Llamada y Escuchas +------------------------------------------------- + +Si por alguna razón deseas eliminar cualquier devolución de llamada del gestor de eventos, simplemente llama al método :php:meth:`Cake\\Event\\EventManager::off()` utilizando como argumentos los dos primeros parámetros que usaste para adjuntarlo:: + + // Adjuntando una función + $this->getEventManager()->on('Mi.evento', [$this, 'hacerAlgo']); + + // Desadjuntando la función + $this->getEventManager()->off('Mi.evento', [$this, 'hacerAlgo']); + + // Adjuntando una función anónima + $miFuncion = function ($evento) { ... }; + $this->getEventManager()->on('Mi.evento', $miFuncion); + + // Desadjuntando la función anónima + $this->getEventManager()->off('Mi.evento', $miFuncion); + + // Agregando un EventListener + $escucha = new MiEscuchaEvento(); + $this->getEventManager()->on($escucha); + + // Desadjuntando una clave de evento única de un escucha + $this->getEventManager()->off('Mi.evento', $escucha); + + // Desadjuntando todas las devoluciones de llamada implementadas por un escucha + $this->getEventManager()->off($escucha); + +Los eventos son una excelente manera de separar preocupaciones en tu aplicación y hacer que las clases sean cohesivas y desacopladas entre sí. Los eventos pueden utilizarse para desacoplar el código de la aplicación y hacer que los complementos sean extensibles. + +Ten en cuenta que con un gran poder viene una gran responsabilidad. El uso excesivo de eventos puede dificultar la depuración y requerir pruebas de integración adicionales. + +Lecturas Adicionales +==================== - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. +* :doc:`/orm/behaviors` +* :doc:`/console-commands/commands` +* :doc:`/controllers/components` +* :doc:`/views/helpers` +* :ref:`testing-events` .. meta:: - :title lang=es: Events system - :keywords lang=es: events, dispatch, decoupling, cakephp, callbacks, triggers, hooks, php + :title lang=es: Sistema de Eventos + :keywords lang=es: eventos, despacho, desacoplamiento, cakephp, devoluciones de llamada, desencadenadores, ganchos, php diff --git a/es/core-libraries/form.rst b/es/core-libraries/form.rst index 77345fb7ed..7a1738870e 100644 --- a/es/core-libraries/form.rst +++ b/es/core-libraries/form.rst @@ -1,15 +1,223 @@ -Modelless Forms -############### +Formularios sin Modelo +###################### .. php:namespace:: Cake\Form -.. php:class:: Form +.. php:class:: Form (Formulario) -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. +La mayoría de las veces, tendrás formularios respaldados por :doc:`entidades ORM ` +y :doc:`tablas ORM ` u otras fuentes persistentes, +pero hay momentos en los que necesitarás validar la entrada del usuario y luego realizar +una acción si los datos son válidos. El ejemplo más común de esto es un formulario de contacto. - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. +Creación de un Formulario +========================= - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. +Generalmente, al utilizar la clase Form, querrás usar una subclase para definir tu +formulario. Esto facilita las pruebas y te permite reutilizar tu formulario. Los formularios se colocan +en **src/Form** y generalmente tienen ``Form`` como sufijo de la clase. Por ejemplo, +un formulario de contacto simple se vería así:: + + // en src/Form/ContactForm.php + namespace App\Form; + + use Cake\Form\Form; + use Cake\Form\Schema; + use Cake\Validation\Validator; + + class ContactForm extends Form + { + protected function _buildSchema(Schema $schema): Schema + { + return $schema->addField('name', 'string') + ->addField('email', ['type' => 'string']) + ->addField('body', ['type' => 'text']); + } + + public function validationDefault(Validator $validator): Validator + { + $validator->minLength('name', 10) + ->email('email'); + + return $validator; + } + + protected function _execute(array $data): bool + { + // Enviar un correo electrónico. + return true; + } + } + +En el ejemplo anterior, vemos los 3 métodos de gancho que proporcionan los formularios: + +* ``_buildSchema`` se utiliza para definir los datos del esquema que utiliza FormHelper + para crear un formulario HTML. Puedes definir el tipo de campo, longitud y precisión. +* ``validationDefault`` obtiene una instancia de :php:class:`Cake\\Validation\\Validator` + a la que puedes adjuntar validadores. +* ``_execute`` te permite definir el comportamiento que deseas que ocurra cuando + se llama a ``execute()`` y los datos son válidos. + +Siempre puedes definir métodos públicos adicionales según sea necesario. + +Procesamiento de Datos de Solicitud +=================================== + +Una vez que hayas definido tu formulario, puedes usarlo en tu controlador para procesar +y validar los datos de la solicitud:: + + // En un controlador + namespace App\Controller; + + use App\Controller\AppController; + use App\Form\ContactForm; + + class ContactController extends AppController + { + public function index() + { + $contact = new ContactForm(); + if ($this->request->is('post')) { + if ($contact->execute($this->request->getData())) { + $this->Flash->success('Nos pondremos en contacto contigo pronto.'); + } else { + $this->Flash->error('Hubo un problema al enviar tu formulario.'); + } + } + $this->set('contact', $contact); + } + } + +En el ejemplo anterior, usamos el método ``execute()`` para ejecutar el método +``_execute()`` de nuestro formulario solo cuando los datos son válidos, y establecemos mensajes flash +según corresponda. Si queremos usar un conjunto de validación no predeterminado, podemos usar la opción ``validate``:: + + if ($contact->execute($this->request->getData(), 'update')) { + // Manejar el éxito del formulario. + } + +Esta opción también se puede establecer en ``false`` para desactivar la validación. + +También podríamos haber usado el método ``validate()`` para validar solo +los datos de la solicitud:: + + $isValid = $form->validate($this->request->getData()); + + // También puedes usar otros conjuntos de validación. Lo siguiente + // utilizaría las reglas definidas por `validationUpdate()` + $isValid = $form->validate($this->request->getData(), 'update'); + +Establecer Valores del Formulario +================================= + +Puedes establecer valores predeterminados para formularios sin modelo utilizando el método ``setData()``. +Los valores establecidos con este método sobrescribirán los datos existentes en el objeto del formulario:: + + // En un controlador + namespace App\Controller; + + use App\Controller\AppController; + use App\Form\ContactForm; + + class ContactController extends AppController + { + public function index() + { + $contact = new ContactForm(); + if ($this->request->is('post')) { + if ($contact->execute($this->request->getData())) { + $this->Flash->success('Nos pondremos en contacto contigo pronto.'); + } else { + $this->Flash->error('Hubo un problema al enviar tu formulario.'); + } + } + + if ($this->request->is('get')) { + $contact->setData([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com' + ]); + } + + $this->set('contact', $contact); + } + } + +Los valores solo deben definirse si el método de solicitud es GET; de lo contrario, +sobrescribirás los datos anteriores del POST que podrían tener errores de validación +que necesitan correcciones. Puedes usar ``set()`` para agregar o reemplazar campos individuales +o un subconjunto de campos:: + + // Establecer un campo. + $contact->set('name', 'John Doe'); + + // Establecer varios campos; + $contact->set([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', + ]); + +Obtener Errores del Formulario +============================== + +Una vez que un formulario ha sido validado, puedes recuperar los errores de él:: + + $errors = $form->getErrors(); + /* $errors contiene + [ + 'name' => ['length' => 'El nombre debe tener al menos dos caracteres de longitud'], + 'email' => ['format' => 'Se requiere una dirección de correo electrónico válida'], + ] + */ + + $error = $form->getError('email'); + /* $error contiene + [ + 'format' => 'Se requiere una dirección de correo electrónico válida', + ] + */ + +Invalidar Campos Individuales del Formulario desde el Controlador +================================================================= + +Es posible invalidar campos individuales desde el controlador sin la +necesidad de usar la clase Validator. El caso de uso más común para esto es cuando la +validación se realiza en un servidor remoto. En tal caso, debes invalidar manualmente +los campos de acuerdo con la retroalimentación del servidor remoto:: + + // en src/Form/ContactForm.php + public function setErrors($errors) + { + $this->_errors = $errors; + } + +Según la forma en que la clase validadora habría devuelto los errores, ``$errors`` +debe tener este formato:: + + ['nombreCampo' => ['nombreValidador' => 'El mensaje de error a mostrar']] + +Ahora podrás invalidar campos del formulario configurando el nombre del campo y +luego establecer los mensajes de error:: + + // En un controlador + $contact = new ContactForm(); + $contact->setErrors(['email' => ['_required' => 'Tu correo electrónico es obligatorio']]); + +Continúa con la creación de HTML con FormHelper para ver los resultados. + +Creación de HTML con FormHelper +=============================== + +Una vez que hayas creado una clase Form, es probable que desees crear un formulario HTML para +ella. FormHelper comprende objetos Form al igual que las entidades ORM:: + + echo $this->Form->create($contact); + echo $this->Form->control('name'); + echo $this->Form->control('email'); + echo $this->Form->control('body'); + echo $this->Form->button('Enviar'); + echo $this->Form->end(); + +Lo anterior crearía un formulario HTML para el ``ContactForm`` que definimos anteriormente. +Los formularios HTML creados con FormHelper utilizarán el esquema y el validador definidos para +determinar los tipos de campos, longitudes máximas y errores de validación. From 8fc344885496d4ad9d60e128f1b5ea8da2f91b8a Mon Sep 17 00:00:00 2001 From: Andres Campanario Date: Mon, 29 Jan 2024 11:13:32 +0100 Subject: [PATCH 3/5] translate core-libraries: hash, httpclient and global functions --- .../global-constants-and-functions.rst | 205 ++++- es/core-libraries/hash.rst | 859 +++++++++++++++++- es/core-libraries/httpclient.rst | 468 +++++++++- 3 files changed, 1507 insertions(+), 25 deletions(-) diff --git a/es/core-libraries/global-constants-and-functions.rst b/es/core-libraries/global-constants-and-functions.rst index dddb8aa52a..52089d4f06 100644 --- a/es/core-libraries/global-constants-and-functions.rst +++ b/es/core-libraries/global-constants-and-functions.rst @@ -1,15 +1,200 @@ -Constants & Functions -##################### +Constantes y Funciones +###################### -.. note:: - La documentación no es compatible actualmente con el idioma español en esta página. +Mientras que la mayoría de tu trabajo diario en CakePHP será utilizando clases y métodos principales, CakePHP cuenta con una serie de funciones globales de conveniencia que pueden resultar útiles. Muchas de estas funciones son para su uso con clases de CakePHP (cargando clases de modelo o componente), pero muchas otras facilitan el trabajo con matrices o cadenas. - Por favor, siéntase libre de enviarnos un pull request en - `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios. +También cubriremos algunas de las constantes disponibles en las aplicaciones de CakePHP. El uso de estas constantes ayudará a que las actualizaciones sean más fluidas, pero también son formas convenientes de apuntar a ciertos archivos o directorios en tu aplicación de CakePHP. - Usted puede hacer referencia a la versión en Inglés en el menú de selección superior - para obtener información sobre el tema de esta página. +Funciones Globales +================== + +Aquí están las funciones globalmente disponibles de CakePHP. La mayoría de ellas son simplemente envoltorios de conveniencia para otras funcionalidades de CakePHP, como depuración y traducción de contenido. Por defecto, solo las funciones con espacio de nombres están cargadas automáticamente, sin embargo, opcionalmente puedes cargar alias globales agregando:: + + require CAKE . 'functions.php'; + +A tu archivo ``config/bootstrap.php`` de la aplicación. Hacer esto cargará alias globales para *todas* las funciones listadas a continuación. + +.. php:namespace:: Cake\I18n + +.. php:function:: \_\_(string $string_id, [$formatArgs]) + + Esta función maneja la localización en las aplicaciones de CakePHP. El ``$string_id`` identifica el ID para una traducción. Puedes proporcionar argumentos adicionales para reemplazar marcadores de posición en tu cadena:: + + __('Tienes {0} mensajes sin leer', $number); + + También puedes proporcionar un array de reemplazos indexados por nombre:: + + __('Tienes {unread} mensajes sin leer', ['unread' => $number]); + + .. note:: + + Consulta la sección de :doc:`/core-libraries/internationalization-and-localization` para obtener más información. + +.. php:function:: __d(string $domain, string $msg, mixed $args = null) + + Permite anular el dominio actual para una búsqueda de mensaje única. + + Útil al internacionalizar un plugin: + + ``echo __d('nombre_del_plugin', 'Este es mi plugin');`` + + .. note:: + + Asegúrate de usar la versión con guiones bajos del nombre del plugin aquí como dominio. + +.. php:function:: __dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null) + + Permite anular el dominio actual para una búsqueda de mensaje plural única. Devuelve la forma plural correcta del mensaje identificado por ``$singular`` y ``$plural`` para el recuento ``$count`` desde el dominio ``$domain``. + +.. php:function:: __dx(string $domain, string $context, string $msg, mixed $args = null) + + Permite anular el dominio actual para una búsqueda de mensaje única. También te permite especificar un contexto. + + El contexto es un identificador único para la cadena de traducciones que lo hace único dentro del mismo dominio. + +.. php:function:: __dxn(string $domain, string $context, string $singular, string $plural, integer $count, mixed $args = null) + + Permite anular el dominio actual para una búsqueda de mensaje plural única. También te permite especificar un contexto. Devuelve la forma plural correcta del mensaje identificado por ``$singular`` y ``$plural`` para el recuento ``$count`` desde el dominio ``$domain``. Algunos idiomas tienen más de una forma para mensajes plurales dependientes del recuento. + + El contexto es un identificador único para la cadena de traducciones que lo hace único dentro del mismo dominio. + +.. php:function:: __n(string $singular, string $plural, integer $count, mixed $args = null) + + Devuelve la forma plural correcta del mensaje identificado por ``$singular`` y ``$plural`` para el recuento ``$count``. Algunos idiomas tienen más de una forma para mensajes plurales dependientes del recuento. + +.. php:function:: __x(string $context, string $msg, mixed $args = null) + + El contexto es un identificador único para la cadena de traducciones que lo hace único dentro del mismo dominio. + +.. php:function:: __xn(string $context, string $singular, string $plural, integer $count, mixed $args = null) + + Devuelve la forma plural correcta del mensaje identificado por ``$singular`` y ``$plural`` para el recuento ``$count`` desde el dominio ``$domain``. También te permite especificar un contexto. Algunos idiomas tienen más de una forma para mensajes plurales dependientes del recuento. + + El contexto es un identificador único para la cadena de traducciones que lo hace único dentro del mismo dominio. + +.. php:namespace:: Cake\Collection + +.. php:function:: collection(mixed $items) + + Envoltorio de conveniencia para instanciar un nuevo objeto :php:class:`Cake\\Collection\\Collection`, envolviendo el argumento pasado. El parámetro ``$items`` puede tomar un objeto ``Traversable`` o una matriz. + +.. php:namespace:: Cake\Core + +.. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) + + Si la variable central ``$debug`` es ``true``, se imprime ``$var``. + Si ``$showHTML`` es ``true`` o se deja como ``null``, los datos se renderizan para ser + amigables con el navegador. Si ``$showFrom`` no se establece en ``false``, la salida de depuración + comenzará con la línea desde la que se llamó. También consulta + :doc:`/development/debugging` + +.. php:function:: dd(mixed $var, boolean $showHtml = null) + + Se comporta como ``debug()``, pero también se detiene la ejecución. + Si la variable central ``$debug`` es ``true``, se imprime ``$var``. + Si ``$showHTML`` es ``true`` o se deja como ``null``, los datos se renderizan para ser + amigables con el navegador. También consulta :doc:`/development/debugging` + +.. php:function:: pr(mixed $var) + + Envoltorio de conveniencia para ``print_r()``, con la adición de + envolver las etiquetas ``
`` alrededor de la salida.
+
+.. php:function:: pj(mixed $var)
+
+    Función de conveniencia para imprimir bonito en JSON, con la adición de
+    envolver las etiquetas ``
`` alrededor de la salida.
+
+    Está destinado a depurar la representación JSON de objetos y matrices.
+
+.. php:function:: env(string $key, string $default = null)
+
+    Obtiene una variable de entorno de las fuentes disponibles. Se usa como respaldo si
+    ``$_SERVER`` o ``$_ENV`` están desactivados.
+
+    Esta función también emula ``PHP_SELF`` y ``DOCUMENT_ROOT`` en
+    servidores que no lo soportan. De hecho, es una buena idea usar siempre ``env()``
+    en lugar de ``$_SERVER`` o ``getenv()`` (especialmente si planeas
+    distribuir el código), ya que es un envoltorio de emulación completo.
+
+.. php:function:: h(string $text, boolean $double = true, string $charset = null)
+
+    Envoltorio de conveniencia para ``htmlspecialchars()``.
+
+.. php:function:: pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null)
+
+    Divide un nombre de plugin de sintaxis de punto en su plugin y nombre de clase. Si ``$name``
+    no tiene un punto, entonces el índice 0 será ``null``.
+
+    Comúnmente utilizado como ``list($plugin, $name) = pluginSplit('Usuarios.Usuario');``
+
+.. php:function:: namespaceSplit(string $class)
+
+    Divide el espacio de nombres del nombre de clase.
+
+    Comúnmente utilizado como ``list($namespace, $nombreClase) = namespaceSplit('Cake\Core\App');``
+
+Constantes de Definición Principal
+==================================
+
+La mayoría de las siguientes constantes se refieren a rutas en tu aplicación.
+
+.. php:const:: APP
+
+   Ruta absoluta al directorio de tu aplicación, incluyendo una barra diagonal al final.
+
+.. php:const:: APP_DIR
+
+    Equivale a ``app`` o al nombre de tu directorio de aplicación.
+
+.. php:const:: CACHE
+
+    Ruta al directorio de archivos de caché. Puede ser compartido entre hosts en una configuración multi-servidor.
+
+.. php:const:: CAKE
+
+    Ruta al directorio de Cake.
+
+.. php:const:: CAKE_CORE_INCLUDE_PATH
+
+    Ruta al directorio raíz lib.
+
+.. php:const:: CONFIG
+
+   Ruta al directorio de configuración.
+
+.. php:const:: CORE_PATH
+
+   Ruta al directorio CakePHP con la barra diagonal de directorio final.
+
+.. php:const:: DS
+
+    Abreviatura de ``DIRECTORY_SEPARATOR`` de PHP, que es ``/`` en Linux y ``\`` en Windows.
+
+.. php:const:: LOGS
+
+    Ruta al directorio de registros.
+
+.. php:const:: RESOURCES
+
+   Ruta al directorio de recursos.
+
+.. php:const:: ROOT
+
+    Ruta al directorio raíz.
+
+.. php:const:: TESTS
+
+    Ruta al directorio de pruebas.
+
+.. php:const:: TMP
+
+    Ruta al directorio de archivos temporales.
+
+.. php:const:: WWW_ROOT
+
+    Ruta completa al directorio raíz de la web.
 
 .. meta::
-    :title lang=es: Global Constants and Functions
-    :keywords lang=es: internationalization and localization,global constants,example config,array php,convenience functions,core libraries,component classes,optional number,global functions,string string,core classes,format strings,unread messages,placeholders,useful functions,sprintf,arrays,parameters,existence,translations
+    :title lang=es: Constantes y Funciones Globales
+    :keywords lang=es: internacionalización y localización,constantes globales,configuración de ejemplo,matriz php,funciones de conveniencia,bibliotecas principales,clases de componentes,número opcional,funciones globales,cadena de caracteres,clases principales,formato de cadenas,mensajes no leídos,marcadores de posición,funciones útiles,sprintf,matrices,parámetros,existencia,traducciones
diff --git a/es/core-libraries/hash.rst b/es/core-libraries/hash.rst
index f29389a73b..1d7d8ec238 100644
--- a/es/core-libraries/hash.rst
+++ b/es/core-libraries/hash.rst
@@ -1,15 +1,860 @@
 Hash
 ####
 
-.. note::
-    La documentación no es compatible actualmente con el idioma español en esta página.
+.. php:namespace:: Cake\Utility
 
-    Por favor, siéntase libre de enviarnos un pull request en
-    `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios.
+.. php:class:: Hash
 
-    Usted puede hacer referencia a la versión en Inglés en el menú de selección superior
-    para obtener información sobre el tema de esta página.
+La gestión de matrices, si se hace correctamente, puede ser una herramienta muy potente y útil para construir código más inteligente y optimizado. CakePHP ofrece un conjunto muy útil de utilidades estáticas en la clase Hash que te permiten hacer exactamente eso.
+
+La clase Hash de CakePHP se puede llamar desde cualquier modelo o controlador de la misma manera que se llama a Inflector. Ejemplo: :php:meth:`Hash::combine()`.
+
+.. _hash-path-syntax:
+
+Sintaxis de Ruta de Hash
+========================
+
+La sintaxis de ruta descrita a continuación es utilizada por todos los métodos en ``Hash``. No todas las partes de la sintaxis de ruta están disponibles en todos los métodos. Una expresión de ruta está compuesta por cualquier número de tokens. Los tokens están compuestos por dos grupos. Las expresiones se utilizan para recorrer los datos de la matriz, mientras que los matchers se utilizan para calificar elementos. Aplicas matchers a los elementos de la expresión.
+
+Tipos de Expresiones
+--------------------
+
++--------------------------------+--------------------------------------------+
+| Expresión                      | Definición                                 |
++================================+============================================+
+| ``{n}``                        | Representa una clave numérica. Coincidirá  |
+|                                | con cualquier clave de cadena o numérica.  |
++--------------------------------+--------------------------------------------+
+| ``{s}``                        | Representa una cadena. Coincidirá con      |
+|                                | cualquier valor de cadena, incluidos los   |
+|                                | valores de cadena numérica.                |
++--------------------------------+--------------------------------------------+
+| ``{*}``                        | Coincide con cualquier valor.              |
++--------------------------------+--------------------------------------------+
+| ``Foo``                        | Coincide con claves con el mismo valor.    |
++--------------------------------+--------------------------------------------+
+
+Todos los elementos de expresión son compatibles con todos los métodos. Además de los elementos de expresión, puedes usar la coincidencia de atributos con ciertos métodos. Son ``extract()``, ``combine()``, ``format()``, ``check()``, ``map()``, ``reduce()``, ``apply()``, ``sort()``, ``insert()``, ``remove()`` y ``nest()``.
+
+Tipos de Coincidencia de Atributos
+----------------------------------
+
++--------------------------------+--------------------------------------------+
+| Matcher                        | Definición                                 |
++================================+============================================+
+| ``[id]``                       | Coincidir con elementos con una clave de   |
+|                                | matriz dada.                               |
++--------------------------------+--------------------------------------------+
+| ``[id=2]``                     | Coincidir con elementos con id igual a 2.  |
++--------------------------------+--------------------------------------------+
+| ``[id!=2]``                    | Coincidir con elementos con id diferente   |
+|                                | de 2.                                      |
++--------------------------------+--------------------------------------------+
+| ``[id>2]``                     | Coincidir con elementos con id mayor que   |
+|                                | 2.                                         |
++--------------------------------+--------------------------------------------+
+| ``[id>=2]``                    | Coincidir con elementos con id mayor o     |
+|                                | igual a 2.                                 |
++--------------------------------+--------------------------------------------+
+| ``[id<2]``                     | Coincidir con elementos con id menor que   |
+|                                | 2.                                         |
++--------------------------------+--------------------------------------------+
+| ``[id<=2]``                    | Coincidir con elementos con id menor o     |
+|                                | igual a 2.                                 |
++--------------------------------+--------------------------------------------+
+| ``[text=/.../]``               | Coincidir con elementos que tienen valores |
+|                                | que coinciden con la expresión regular     |
+|                                | dentro de ``...``.                         |
++--------------------------------+--------------------------------------------+
+
+.. php:staticmethod:: get(array|\ArrayAccess $data, $path, $default = null)
+
+    ``get()`` es una versión simplificada de ``extract()``, solo admite expresiones de ruta directas. Las rutas con ``{n}``, ``{s}``, ``{*}`` o matchers no son compatibles. Usa ``get()`` cuando quieras exactamente un valor de una matriz. Si no se encuentra una ruta coincidente, se devolverá el valor predeterminado.
+
+.. php:staticmethod:: extract(array|\ArrayAccess $data, $path)
+
+    ``Hash::extract()`` admite todos los componentes de expresiones y matchers de
+    :ref:`hash-path-syntax`. Puedes usar extract para recuperar datos de matrices
+    u objetos que implementen la interfaz ``ArrayAccess``, a lo largo de rutas arbitrarias
+    rápidamente sin tener que recorrer las estructuras de datos. En su lugar, usas
+    expresiones de ruta para calificar qué elementos quieres devolver ::
+
+        // Uso común:
+        $usuarios = [
+            ['id' => 1, 'nombre' => 'mark'],
+            ['id' => 2, 'nombre' => 'jane'],
+            ['id' => 3, 'nombre' => 'sally'],
+            ['id' => 4, 'nombre' => 'jose'],
+        ];
+        $resultados = Hash::extract($usuarios, '{n}.id');
+        // $resultados es igual a:
+        // [1,2,3,4];
+
+.. php:staticmethod:: Hash::insert(array $data, $path, $values = null)
+
+    Inserta ``$values`` en una matriz según lo definido por ``$path``::
+
+        $a = [
+            'páginas' => ['nombre' => 'página']
+        ];
+        $resultado = Hash::insert($a, 'archivos', ['nombre' => 'archivos']);
+        // $resultado ahora se ve así:
+        [
+            [páginas] => [
+                [nombre] => página
+            ]
+            [archivos] => [
+                [nombre] => archivos
+            ]
+        ]
+
+    Puedes usar rutas usando ``{n}``, ``{s}`` y ``{*}`` para insertar datos en múltiples
+    puntos::
+
+        $usuarios = Hash::insert($usuarios, '{n}.nuevo', 'valor');
+
+    Los matchers de atributos también funcionan con ``insert()``::
+
+        $datos = [
+            0 => ['up' => true, 'Item' => ['id' => 1, 'título' => 'primero']],
+            1 => ['Item' => ['id' => 2, 'título' => 'segundo']],
+            2 => ['Item' => ['id' => 3, 'título' => 'tercero']],
+            3 => ['up' => true, 'Item' => ['id' => 4, 'título' => 'cuarto']],
+            4 => ['Item' => ['id' => 5, 'título' => 'quinto']],
+        ];
+        $resultado = Hash::insert($datos, '{n}[up].Item[id=4].nuevo', 9);
+        /* $resultado ahora se ve así:
+            [
+                ['up' => true, 'Item' => ['id' => 1, 'título' => 'primero']],
+                ['Item' => ['id' => 2, 'título' => 'segundo']],
+                ['Item' => ['id' => 3, 'título' => 'tercero']],
+                ['up' => true, 'Item' => ['id' => 4, 'título' => 'cuarto', 'nuevo' => 9]],
+                ['Item' => ['id' => 5, 'título' => 'quinto']],
+            ]
+        */
+
+.. php:staticmethod:: remove(array $data, $path)
+
+    Elimina todos los elementos de una matriz que coinciden con ``$path``. ::
+
+        $a = [
+            'páginas' => ['nombre' => 'página'],
+            'archivos' => ['nombre' => 'archivos']
+        ];
+        $resultado = Hash::remove($a, 'archivos');
+        /* $resultado ahora se ve así:
+            [
+                [páginas] => [
+                    [nombre] => página
+                ]
+
+            ]
+        */
+
+    Usando ``{n}``, ``{s}`` y ``{*}`` te permitirá eliminar varios valores a la vez.
+    También puedes usar matchers de atributos con ``remove()``::
+
+        $datos = [
+            0 => ['clear' => true, 'Item' => ['id' => 1, 'título' => 'primero']],
+            1 => ['Item' => ['id' => 2, 'título' => 'segundo']],
+            2 => ['Item' => ['id' => 3, 'título' => 'tercero']],
+            3 => ['clear' => true, 'Item' => ['id' => 4, 'título' => 'cuarto']],
+            4 => ['Item' => ['id' => 5, 'título' => 'quinto']],
+        ];
+        $resultado = Hash::remove($datos, '{n}[clear].Item[id=4]');
+        /* $resultado ahora se ve así:
+            [
+                ['clear' => true, 'Item' => ['id' => 1, 'título' => 'primero']],
+                ['Item' => ['id' => 2, 'título' => 'segundo']],
+                ['Item' => ['id' => 3, 'título' => 'tercero']],
+                ['clear' => true],
+                ['Item' => ['id' => 5, 'título' => 'quinto']],
+            ]
+        */
+
+.. php:staticmethod:: combine(array $data, $keyPath, $valuePath = null, $groupPath = null)
+
+    Crea un array asociativo utilizando ``$keyPath`` como la ruta para construir sus claves,
+    y opcionalmente ``$valuePath`` como la ruta para obtener los valores. Si ``$valuePath`` no está
+    especificado, o no coincide con nada, los valores se inicializarán a null.
+    Opcionalmente, puedes agrupar los valores por lo que se obtiene al seguir la
+    ruta especificada en ``$groupPath``. ::
+
+        $a = [
+            [
+                'User' => [
+                    'id' => 2,
+                    'group_id' => 1,
+                    'Data' => [
+                        'user' => 'mariano.iglesias',
+                        'name' => 'Mariano Iglesias'
+                    ]
+                ]
+            ],
+            [
+                'User' => [
+                    'id' => 14,
+                    'group_id' => 2,
+                    'Data' => [
+                        'user' => 'phpnut',
+                        'name' => 'Larry E. Masters'
+                    ]
+                ]
+            ],
+        ];
+
+        $result = Hash::combine($a, '{n}.User.id');
+        /* $result ahora luce así:
+            [
+                [2] =>
+                [14] =>
+            ]
+        */
+
+        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.user');
+        /* $result ahora luce así:
+            [
+                [2] => 'mariano.iglesias'
+                [14] => 'phpnut'
+            ]
+        */
+
+        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data');
+        /* $result ahora luce así:
+            [
+                [2] => [
+                        [user] => mariano.iglesias
+                        [name] => Mariano Iglesias
+                ]
+                [14] => [
+                        [user] => phpnut
+                        [name] => Larry E. Masters
+                ]
+            ]
+        */
+
+        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name');
+        /* $result ahora luce así:
+            [
+                [2] => Mariano Iglesias
+                [14] => Larry E. Masters
+            ]
+        */
+
+        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.group_id');
+        /* $result ahora luce así:
+            [
+                [1] => [
+                        [2] => [
+                                [user] => mariano.iglesias
+                                [name] => Mariano Iglesias
+                        ]
+                ]
+                [2] => [
+                        [14] => [
+                                [user] => phpnut
+                                [name] => Larry E. Masters
+                        ]
+                ]
+            ]
+        */
+
+        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id');
+        /* $result ahora luce así:
+            [
+                [1] => [
+                        [2] => Mariano Iglesias
+                ]
+                [2] => [
+                        [14] => Larry E. Masters
+                ]
+            ]
+        */
+
+        // Desde la versión 3.9.0 $keyPath puede ser null
+        $result = Hash::combine($a, null, '{n}.User.Data.name');
+        /* $result ahora luce así:
+            [
+                [0] => Mariano Iglesias
+                [1] => Larry E. Masters
+            ]
+        */
+
+    Puedes proporcionar arrays tanto para ``$keyPath`` como para ``$valuePath``. Si haces esto,
+    el primer valor se usará como una cadena de formato, para los valores extraídos por las
+    otras rutas::
+
+        $result = Hash::combine(
+            $a,
+            '{n}.User.id',
+            ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
+            '{n}.User.group_id'
+        );
+        /* $result ahora luce así:
+            [
+                [1] => [
+                        [2] => mariano.iglesias: Mariano Iglesias
+                ]
+                [2] => [
+                        [14] => phpnut: Larry E. Masters
+                ]
+            ]
+        */
+
+        $result = Hash::combine(
+            $a,
+            ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
+            '{n}.User.id'
+        );
+        /* $result ahora luce así:
+            [
+                [mariano.iglesias: Mariano Iglesias] => 2
+                [phpnut: Larry E. Masters] => 14
+            ]
+        */
+
+.. php:staticmethod:: format(array $data, array $paths, $format)
+
+    Devuelve una serie de valores extraídos de un array, formateados con una cadena de formato::
+
+        $data = [
+            [
+                'Person' => [
+                    'first_name' => 'Nate',
+                    'last_name' => 'Abele',
+                    'city' => 'Boston',
+                    'state' => 'MA',
+                    'something' => '42'
+                ]
+            ],
+            [
+                'Person' => [
+                    'first_name' => 'Larry',
+                    'last_name' => 'Masters',
+                    'city' => 'Boondock',
+                    'state' => 'TN',
+                    'something' => '{0}'
+                ]
+            ],
+            [
+                'Person' => [
+                    'first_name' => 'Garrett',
+                    'last_name' => 'Woodworth',
+                    'city' => 'Venice Beach',
+                    'state' => 'CA',
+                    'something' => '{1}'
+                ]
+            ]
+        ];
+
+        $res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%2$d, %1$s');
+        /*
+        [
+            [0] => 42, Nate
+            [1] => 0, Larry
+            [2] => 0, Garrett
+        ]
+        */
+
+        $res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%1$s, %2$d');
+        /*
+        [
+            [0] => Nate, 42
+            [1] => Larry, 0
+            [2] => Garrett, 0
+        ]
+        */
+
+.. php:staticmethod:: contains(array $data, array $needle)
+
+    Determina si un Hash o un array contiene exactamente las claves y valores de otro::
+
+        $a = [
+            0 => ['name' => 'main'],
+            1 => ['name' => 'about']
+        ];
+        $b = [
+            0 => ['name' => 'main'],
+            1 => ['name' => 'about'],
+            2 => ['name' => 'contact'],
+            'a' => 'b',
+        ];
+
+        $result = Hash::contains($a, $a);
+        // true
+        $result = Hash::contains($a, $b);
+        // false
+        $result = Hash::contains($b, $a);
+        // true
+
+.. php:staticmethod:: check(array $data, string $path = null)
+
+    Comprueba si una ruta particular está establecida en un array::
+
+        $set = [
+            'My Index 1' => ['First' => 'The first item']
+        ];
+        $result = Hash::check($set, 'My Index 1.First');
+        // $result == true
+
+        $result = Hash::check($set, 'My Index 1');
+        // $result == true
+
+        $set = [
+            'My Index 1' => [
+                'First' => [
+                    'Second' => [
+                        'Third' => [
+                            'Fourth' => 'Heavy. Nesting.'
+                        ]
+                    ]
+                ]
+            ]
+        ];
+        $result = Hash::check($set, 'My Index 1.First.Second');
+        // $result == true
+
+        $result = Hash::check($set, 'My Index 1.First.Second.Third');
+        // $result == true
+
+        $result = Hash::check($set, 'My Index 1.First.Second.Third.Fourth');
+        // $result == true
+
+        $result = Hash::check($set, 'My Index 1.First.Seconds.Third.Fourth');
+        // $result == false
+
+.. php:staticmethod:: filter(array $data, $callback = ['Hash', 'filter'])
+
+    Filtra los elementos vacíos de un array, excluyendo '0'. También puedes proporcionar un ``$callback`` personalizado para filtrar los elementos del array. El callback debería devolver ``false`` para eliminar elementos del array resultante::
+
+        $data = [
+            '0',
+            false,
+            true,
+            0,
+            ['una cosa', 'te puedo decir', 'es que tienes que ser', false]
+        ];
+        $res = Hash::filter($data);
+
+        /* $res ahora luce así:
+            [
+                [0] => 0
+                [2] => true
+                [3] => 0
+                [4] => [
+                        [0] => una cosa
+                        [1] => te puedo decir
+                        [2] => es que tienes que ser
+                ]
+            ]
+        */
+
+.. php:staticmethod:: flatten(array $data, string $separator = '.')
+
+    Colapsa un array multidimensional en una sola dimensión::
+
+        $arr = [
+            [
+                'Post' => ['id' => '1', 'title' => 'Primer Post'],
+                'Author' => ['id' => '1', 'user' => 'Kyle'],
+            ],
+            [
+                'Post' => ['id' => '2', 'title' => 'Segundo Post'],
+                'Author' => ['id' => '3', 'user' => 'Crystal'],
+            ],
+        ];
+        $res = Hash::flatten($arr);
+        /* $res ahora luce así:
+            [
+                [0.Post.id] => 1
+                [0.Post.title] => Primer Post
+                [0.Author.id] => 1
+                [0.Author.user] => Kyle
+                [1.Post.id] => 2
+                [1.Post.title] => Segundo Post
+                [1.Author.id] => 3
+                [1.Author.user] => Crystal
+            ]
+        */
+
+.. php:staticmethod:: expand(array $data, string $separator = '.')
+
+    Expande un array que fue previamente aplanado con :php:meth:`Hash::flatten()`::
+
+        $data = [
+            '0.Post.id' => 1,
+            '0.Post.title' => Primer Post,
+            '0.Author.id' => 1,
+            '0.Author.user' => Kyle,
+            '1.Post.id' => 2,
+            '1.Post.title' => Segundo Post,
+            '1.Author.id' => 3,
+            '1.Author.user' => Crystal,
+        ];
+        $res = Hash::expand($data);
+        /* $res ahora luce así:
+        [
+            [
+                'Post' => ['id' => '1', 'title' => 'Primer Post'],
+                'Author' => ['id' => '1', 'user' => 'Kyle'],
+            ],
+            [
+                'Post' => ['id' => '2', 'title' => 'Segundo Post'],
+                'Author' => ['id' => '3', 'user' => 'Crystal'],
+            ],
+        ];
+        */
+
+.. php:staticmethod:: merge(array $data, array $merge[, array $n])
+
+    Esta función puede pensarse como una combinación entre ``array_merge`` y ``array_merge_recursive`` de PHP. La diferencia con ambas es que si una clave de array contiene otro array, entonces la función se comporta recursivamente (a diferencia de ``array_merge``), pero no lo hace para claves que contienen cadenas (a diferencia de ``array_merge_recursive``).
+
+    .. note::
+
+        Esta función funcionará con una cantidad ilimitada de argumentos y convertirá parámetros no-array en arrays.
+
+    ::
+
+        $array = [
+            [
+                'id' => '48c2570e-dfa8-4c32-a35e-0d71cbdd56cb',
+                'name' => 'mysql raleigh-workshop-08 < 2008-09-05.sql ',
+                'description' => 'Importando un volcado sql',
+            ],
+            [
+                'id' => '48c257a8-cf7c-4af2-ac2f-114ecbdd56cb',
+                'name' => 'pbpaste | grep -i Unpaid | pbcopy',
+                'description' => 'Eliminar todas las líneas que dicen "Unpaid".',
+            ]
+        ];
+        $arrayB = 4;
+        $arrayC = [0 => "array de prueba", "gatos" => "perros", "personas" => 1267];
+        $arrayD = ["gatos" => "felinos", "perro" => "enojado"];
+        $res = Hash::merge($array, $arrayB, $arrayC, $arrayD);
+
+        /* $res ahora luce así:
+        [
+            [0] => [
+                    [id] => 48c2570e-dfa8-4c32-a35e-0d71cbdd56cb
+                    [name] => mysql raleigh-workshop-08 < 2008-09-05.sql
+                    [description] => Importando un volcado sql
+            ]
+            [1] => [
+                    [id] => 48c257a8-cf7c-4af2-ac2f-114ecbdd56cb
+                    [name] => pbpaste | grep -i Unpaid | pbcopy
+                    [description] => Eliminar todas las líneas que dicen "Unpaid".
+            ]
+            [2] => 4
+            [3] => array de prueba
+            [gatos] => felinos
+            [personas] => 1267
+            [perro] => enojado
+        ]
+        */
+
+.. php:staticmethod:: numeric(array $data)
+
+    Verifica si todos los valores en el array son numéricos::
+
+        $data = ['uno'];
+        $res = Hash::numeric(array_keys($data));
+        // $res es true
+
+        $data = [1 => 'uno'];
+        $res = Hash::numeric($data);
+        // $res es false
+
+.. php:staticmethod:: dimensions (array $data)
+
+    Cuenta las dimensiones de un array. Este método solo considerará la dimensión del primer elemento en el array::
+
+        $data = ['uno', '2', 'tres'];
+        $result = Hash::dimensions($data);
+        // $result == 1
+
+        $data = ['1' => '1.1', '2', '3'];
+        $result = Hash::dimensions($data);
+        // $result == 1
+
+        $data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => '3.1.1']];
+        $result = Hash::dimensions($data);
+        // $result == 2
+
+        $data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
+        $result = Hash::dimensions($data);
+        // $result == 1
+
+        $data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
+        $result = Hash::dimensions($data);
+        // $result == 2
+
+.. php:staticmethod:: maxDimensions(array $data)
+
+    Similar a :php:meth:`~Hash::dimensions()`, sin embargo, este método devuelve el número más profundo de dimensiones de cualquier elemento en el array::
+
+        $data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
+        $result = Hash::maxDimensions($data);
+        // $result == 2
+
+        $data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
+        $result = Hash::maxDimensions($data);
+        // $result == 3
+
+.. php:staticmethod:: map(array $data, $path, $function)
+
+    Crea un nuevo array, extrayendo ``$path``, y mapeando ``$function`` a través de los resultados. Puedes usar tanto expresiones como elementos coincidentes con este método::
+
+        // Llama a la función noop $this->noop() en cada elemento de $data
+        $result = Hash::map($data, "{n}", [$this, 'noop']);
+
+        public function noop(array $array)
+        {
+            // Haz algo con el array y devuelve el resultado
+            return $array;
+        }
+
+.. php:staticmethod:: reduce(array $data, $path, $function)
+
+    Crea un valor único, extrayendo ``$path``, y reduciendo los resultados extraídos con ``$function``. Puedes usar tanto expresiones como elementos coincidentes con este método.
+
+.. php:staticmethod:: apply(array $data, $path, $function)
+
+    Aplica un callback a un conjunto de valores extraídos usando ``$function``. La función obtendrá los valores extraídos como primer argumento::
+
+        $data = [
+            ['date' => '01-01-2016', 'booked' => true],
+            ['date' => '01-01-2016', 'booked' => false],
+            ['date' => '02-01-2016', 'booked' => true]
+        ];
+        $result = Hash::apply($data, '{n}[booked=true].date', 'array_count_values');
+        /* $result ahora luce así:
+            [
+                '01-01-2016' => 1,
+                '02-01-2016' => 1,
+            ]
+        */
+
+.. php:staticmethod:: sort(array $data, $path, $dir, $type = 'regular')
+
+    Ordena un array por cualquier valor, determinado por una :ref:`hash-path-syntax`
+    Solo se admiten elementos de expresión con este método::
+
+        $a = [
+            0 => ['Person' => ['name' => 'Jeff']],
+            1 => ['Shirt' => ['color' => 'black']]
+        ];
+        $result = Hash::sort($a, '{n}.Person.name', 'asc');
+        /* $result ahora luce así:
+            [
+                [0] => [
+                        [Shirt] => [
+                                [color] => black
+                        ]
+                ]
+                [1] => [
+                        [Person] => [
+                                [name] => Jeff
+                        ]
+                ]
+            ]
+        */
+
+    ``$dir`` puede ser ``asc`` o ``desc``. ``$type``
+    puede ser uno de los siguientes valores:
+
+    * ``regular`` para ordenamiento regular.
+    * ``numeric`` para ordenar los valores como sus equivalentes numéricos.
+    * ``string`` para ordenar los valores como su valor de cadena.
+    * ``natural`` para ordenar los valores de una manera amigable para humanos. Ordenará, por ejemplo, ``foo10`` debajo de ``foo2``.
+
+.. php:staticmethod:: diff(array $data, array $compare)
+
+    Calcula la diferencia entre dos arrays::
+
+        $a = [
+            0 => ['name' => 'main'],
+            1 => ['name' => 'about']
+        ];
+        $b = [
+            0 => ['name' => 'main'],
+            1 => ['name' => 'about'],
+            2 => ['name' => 'contact']
+        ];
+
+        $result = Hash::diff($a, $b);
+        /* $result ahora luce así:
+            [
+                [2] => [
+                        [name] => contact
+                ]
+            ]
+        */
+
+.. php:staticmethod:: mergeDiff(array $data, array $compare)
+
+    Esta función fusiona dos arrays y empuja las diferencias en
+    datos al final del array resultante.
+
+    **Ejemplo 1**
+    ::
+
+        $array1 = ['ModelOne' => ['id' => 1001, 'field_one' => 'a1.m1.f1', 'field_two' => 'a1.m1.f2']];
+        $array2 = ['ModelOne' => ['id' => 1003, 'field_one' => 'a3.m1.f1', 'field_two' => 'a3.m1.f2', 'field_three' => 'a3.m1.f3']];
+        $res = Hash::mergeDiff($array1, $array2);
+
+        /* $res ahora luce así:
+            [
+                [ModelOne] => [
+                        [id] => 1001
+                        [field_one] => a1.m1.f1
+                        [field_two] => a1.m1.f2
+                        [field_three] => a3.m1.f3
+                    ]
+            ]
+        */
+
+    **Ejemplo 2**
+    ::
+
+        $array1 = ["a" => "b", 1 => 20938, "c" => "cadena"];
+        $array2 = ["b" => "b", 3 => 238, "c" => "cadena", ["campo_extra"]];
+        $res = Hash::mergeDiff($array1, $array2);
+        /* $res ahora luce así:
+            [
+                [a] => b
+                [1] => 20938
+                [c] => cadena
+                [b] => b
+                [3] => 238
+                [4] => [
+                        [0] => campo_extra
+                ]
+            ]
+        */
+
+.. php:staticmethod:: normalize(array $data, $assoc = true, $default = null)
+
+    Normaliza un array. Si ``$assoc`` es ``true``, el array resultante será
+    normalizado para ser un array asociativo. Las claves numéricas con valores
+    serán convertidas a claves de cadena con valores ``$default``. Normalizar un array
+    hace que usar los resultados con :php:meth:`Hash::merge()` sea más fácil::
+
+        $a = ['Árbol', 'ContadorCache',
+            'Subir' => [
+                'carpeta' => 'productos',
+                'campos' => ['id_imagen_1', 'id_imagen_2']
+            ]
+        ];
+        $result = Hash::normalize($a);
+        /* $result ahora luce así:
+            [
+                [Árbol] => null
+                [ContadorCache] => null
+                [Subir] => [
+                        [carpeta] => productos
+                        [campos] => [
+                                [0] => id_imagen_1
+                                [1] => id_imagen_2
+                        ]
+                ]
+            ]
+        */
+
+        $b = [
+            'Cacheable' => ['activado' => false],
+            'Límite',
+            'Enlazable',
+            'Validador',
+            'Transaccional',
+        ];
+        $result = Hash::normalize($b);
+        /* $result ahora luce así:
+            [
+                [Cacheable] => [
+                        [activado] => false
+                ]
+
+                [Límite] => null
+                [Enlazable] => null
+                [Validador] => null
+                [Transaccional] => null
+            ]
+        */
+
+.. versionchanged:: 4.5.0
+    Se agregó el parámetro ``$default``.
+
+.. php:staticmethod:: nest(array $data, array $options = [])
+
+    Toma un conjunto de datos plano y crea una estructura de datos anidada o enroscada.
+
+    **Opciones:**
+
+    - ``children`` El nombre de clave que se usará en el conjunto de resultados para los hijos. Por defecto,
+      es 'children'.
+    - ``idPath`` La ruta a una clave que identifica cada entrada. Debe ser compatible con :php:meth:`Hash::extract()`.
+      Por defecto, es ``{n}.$alias.id``.
+    - ``parentPath`` La ruta a una clave que identifica el padre de cada entrada. Debe ser compatible con
+      :php:meth:`Hash::extract()`. Por defecto, es ``{n}.$alias.parent_id``.
+    - ``root`` El id del resultado superior deseado.
+
+    Por ejemplo, si tuvieras el siguiente array de datos::
+
+        $data = [
+            ['ThreadPost' => ['id' => 1, 'parent_id' => null]],
+            ['ThreadPost' => ['id' => 2, 'parent_id' => 1]],
+            ['ThreadPost' => ['id' => 3, 'parent_id' => 1]],
+            ['ThreadPost' => ['id' => 4, 'parent_id' => 1]],
+            ['ThreadPost' => ['id' => 5, 'parent_id' => 1]],
+            ['ThreadPost' => ['id' => 6, 'parent_id' => null]],
+            ['ThreadPost' => ['id' => 7, 'parent_id' => 6]],
+            ['ThreadPost' => ['id' => 8, 'parent_id' => 6]],
+            ['ThreadPost' => ['id' => 9, 'parent_id' => 6]],
+            ['ThreadPost' => ['id' => 10, 'parent_id' => 6]]
+        ];
+
+        $result = Hash::nest($data, ['root' => 6]);
+        /* $result ahora luce así:
+            [
+                (int) 0 => [
+                    'ThreadPost' => [
+                        'id' => (int) 6,
+                        'parent_id' => null
+                    ],
+                    'children' => [
+                        (int) 0 => [
+                            'ThreadPost' => [
+                                'id' => (int) 7,
+                                'parent_id' => (int) 6
+                            ],
+                            'children' => []
+                        ],
+                        (int) 1 => [
+                            'ThreadPost' => [
+                                'id' => (int) 8,
+                                'parent_id' => (int) 6
+                            ],
+                            'children' => []
+                        ],
+                        (int) 2 => [
+                            'ThreadPost' => [
+                                'id' => (int) 9,
+                                'parent_id' => (int) 6
+                            ],
+                            'children' => []
+                        ],
+                        (int) 3 => [
+                            'ThreadPost' => [
+                                'id' => (int) 10,
+                                'parent_id' => (int) 6
+                            ],
+                            'children' => []
+                        ]
+                    ]
+                ]
+            ]
+            */
 
 .. meta::
     :title lang=es: Hash
-    :keywords lang=es: array array,path array,array name,numeric key,regular expression,result set,person name,brackets,syntax,cakephp,elements,php,set path
+    :keywords lang=es: arreglo, ruta de arreglo, nombre de arreglo, clave numérica, expresión regular, conjunto de resultados, nombre de persona, corchetes, sintaxis, CakePHP, elementos, PHP, establecer ruta.
diff --git a/es/core-libraries/httpclient.rst b/es/core-libraries/httpclient.rst
index 311e1c7607..3c220e9401 100644
--- a/es/core-libraries/httpclient.rst
+++ b/es/core-libraries/httpclient.rst
@@ -1,19 +1,471 @@
 Http Client
 ###########
 
-.. php:namespace:: Cake\Network\Http
+.. php:namespace:: Cake\Http
 
 .. php:class:: Client(mixed $config = [])
 
-.. note::
-    La documentación no es compatible actualmente con el idioma español en esta página.
+CakePHP incluye un cliente HTTP compatible con PSR-18 que se puede utilizar para realizar solicitudes. Es una excelente manera de comunicarse con servicios web y APIs remotas.
 
-    Por favor, siéntase libre de enviarnos un pull request en
-    `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios.
+Realizar Solicitudes
+====================
 
-    Usted puede hacer referencia a la versión en Inglés en el menú de selección superior
-    para obtener información sobre el tema de esta página.
+Realizar solicitudes es simple y directo. Hacer una solicitud GET se ve así::
+
+    use Cake\Http\Client;
+
+    $http = new Client();
+
+    // Simple GET
+    $response = $http->get('http://example.com/prueba.html');
+
+    // GET simple con cadena de consulta
+    $response = $http->get('http://example.com/buscar', ['q' => 'widget']);
+
+    // GET simple con cadena de consulta y encabezados adicionales
+    $response = $http->get('http://example.com/buscar', ['q' => 'widget'], [
+      'headers' => ['X-Requested-With' => 'XMLHttpRequest'],
+    ]);
+
+Realizar solicitudes POST y PUT es igualmente simple::
+
+    // Enviar una solicitud POST con datos codificados como application/x-www-form-urlencoded
+    $http = new Client();
+    $response = $http->post('http://example.com/posts/agregar', [
+      'title' => 'prueba',
+      'body' => 'contenido en la publicación',
+    ]);
+
+    // Enviar una solicitud PUT con datos codificados como application/x-www-form-urlencoded
+    $response = $http->put('http://example.com/posts/agregar', [
+      'title' => 'prueba',
+      'body' => 'contenido en la publicación',
+    ]);
+
+    // Otros métodos también.
+    $http->delete(...);
+    $http->head(...);
+    $http->patch(...);
+
+Si has creado un objeto de solicitud PSR-7, puedes enviarlo usando ``sendRequest()``::
+
+    use Cake\Http\Client;
+    use Cake\Http\Client\Request as ClientRequest;
+
+    $solicitud = new ClientRequest(
+        'http://example.com/buscar',
+        ClientRequest::METHOD_GET
+    );
+    $cliente = new Client();
+    $respuesta = $cliente->sendRequest($solicitud);
+
+Creación de Solicitudes Multipartes con Archivos
+================================================
+
+Puedes incluir archivos en los cuerpos de las solicitudes incluyendo un identificador de archivo en el arreglo::
+
+    $http = new Client();
+    $respuesta = $http->post('http://example.com/api', [
+      'imagen' => fopen('/ruta/a/un/archivo', 'r'),
+    ]);
+
+El identificador de archivo se leerá hasta su fin; no se rebobinará antes de leerlo.
+
+Construcción de Cuerpos de Solicitudes Multipartes
+--------------------------------------------------
+
+Puede haber ocasiones en las que necesites construir un cuerpo de solicitud de una manera muy específica. En estas situaciones, a menudo puedes usar ``Cake\Http\Client\FormData`` para crear la solicitud HTTP multipartes específica que deseas::
+
+    use Cake\Http\Client\FormData;
+
+    $datos = new FormData();
+
+    // Crear una parte XML
+    $xml = $datos->newPart('xml', $cadenaXML);
+    // Establecer el tipo de contenido.
+    $xml->type('application/xml');
+    $datos->add($xml);
+
+    // Crear una carga de archivo con addFile()
+    // Esto añadirá el archivo también a los datos del formulario.
+    $archivo = $datos->addFile('carga', fopen('/algún/archivo.txt', 'r'));
+    $archivo->contentId('abc123');
+    $archivo->disposition('adjunto');
+
+    // Enviar la solicitud.
+    $respuesta = $http->post(
+        'http://example.com/api',
+        (string)$datos,
+        ['headers' => ['Content-Type' => $datos->contentType()]]
+    );
+
+Envío de Cuerpos de Solicitudes
+===============================
+
+Cuando trabajas con APIs REST, a menudo necesitas enviar cuerpos de solicitud que no están codificados como formularios. Http\\Cliente expone esto a través de la opción tipo::
+
+    // Enviar un cuerpo de solicitud JSON.
+    $http = new Client();
+    $respuesta = $http->post(
+      'http://example.com/tareas',
+      json_encode($datos),
+      ['type' => 'json']
+    );
+
+La clave ``type`` puede ser uno de 'json', 'xml' o un tipo mime completo. Cuando uses la opción ``type``, debes proporcionar los datos como una cadena. Si estás haciendo una solicitud GET que necesita tanto parámetros de cadena de consulta como un cuerpo de solicitud, puedes hacer lo siguiente::
+
+    // Enviar un cuerpo JSON en una solicitud GET con parámetros de cadena de consulta.
+    $http = new Client();
+    $respuesta = $http->get(
+      'http://example.com/tareas',
+      ['q' => 'prueba', '_content' => json_encode($datos)],
+      ['type' => 'json']
+    );
+
+.. _http_client_request_options:
+
+Opciones del Método de Solicitud
+================================
+
+Cada método HTTP toma un parámetro ``$options`` que se utiliza para proporcionar información adicional de la solicitud. Las siguientes claves se pueden usar en ``$options``:
+
+- ``headers`` - Matriz de encabezados adicionales
+- ``cookie`` - Matriz de cookies a usar.
+- ``proxy`` - Matriz de información de proxy.
+- ``auth`` - Matriz de datos de autenticación, la clave ``type`` se usa para delegar a una estrategia de autenticación. Por defecto se utiliza la autenticación Básica.
+- ``ssl_verify_peer`` - por defecto a ``true``. Establecer a ``false`` para deshabilitar la verificación de certificación SSL (no recomendado).
+- ``ssl_verify_peer_name`` - por defecto a ``true``. Establecer a ``false`` para deshabilitar la verificación del nombre de host al verificar los certificados SSL (no recomendado).
+- ``ssl_verify_depth`` - por defecto a 5. Profundidad para recorrer en la cadena de CA.
+- ``ssl_verify_host`` - por defecto a ``true``. Validar el certificado SSL contra el nombre de host.
+- ``ssl_cafile`` - por defecto al archivo cafile incorporado. Sobrescribir para usar conjuntos de CA personalizados.
+- ``timeout`` - Duración a esperar antes de agotar el tiempo en segundos.
+- ``type`` - Enviar un cuerpo de solicitud en un tipo de contenido personalizado. Requiere que ``$datos`` sea una cadena, o que se establezca la opción ``_content`` al hacer solicitudes GET.
+- ``redirect`` - Número de redirecciones a seguir. Por defecto a ``false``.
+- ``curl`` - Una matriz de opciones curl adicionales (si se utiliza el adaptador curl), por ejemplo, ``[CURLOPT_SSLKEY => 'clave.pem']``.
+
+El parámetro de opciones es siempre el 3er parámetro en cada uno de los métodos HTTP. También se pueden usar al construir ``Client`` para crear :ref
+
+:`clientes con alcance `.
+
+Autenticación
+=============
+
+``Cake\Http\Client`` admite varios sistemas de autenticación diferentes. Los desarrolladores pueden agregar diferentes estrategias de autenticación. Las estrategias de autenticación se llaman antes de enviar la solicitud y permiten agregar encabezados al contexto de la solicitud.
+
+Usando Autenticación Básica
+---------------------------
+
+Un ejemplo de autenticación básica::
+
+    $http = new Client();
+    $respuesta = $http->get('http://example.com/perfil/1', [], [
+      'auth' => ['username' => 'mark', 'password' => 'secreto'],
+    ]);
+
+Por defecto, ``Cake\Http\Client`` utilizará la autenticación básica si no hay una clave ``'type'`` en la opción de autenticación.
+
+Usando Autenticación Digest
+---------------------------
+
+Un ejemplo de autenticación digest::
+
+    $http = new Client();
+    $respuesta = $http->get('http://example.com/perfil/1', [], [
+        'auth' => [
+            'type' => 'digest',
+            'username' => 'mark',
+            'password' => 'secreto',
+            'realm' => 'mi_realm',
+            'nonce' => 'valor_único',
+            'qop' => 1,
+            'opaque' => 'algún_valor',
+        ],
+    ]);
+
+Al establecer la clave 'type' en 'digest', indicas al subsistema de autenticación que utilice la autenticación digest. La autenticación digest admite los siguientes algoritmos:
+
+* MD5
+* SHA-256
+* SHA-512-256
+* MD5-sess
+* SHA-256-sess
+* SHA-512-256-sess
+
+El algoritmo se elegirá automáticamente según el desafío del servidor.
+
+Autenticación OAuth 1
+---------------------
+
+Muchos servicios web modernos requieren autenticación OAuth para acceder a sus APIs. La autenticación OAuth incluida asume que ya tienes tu clave de consumidor y tu secreto de consumidor::
+
+    $http = new Client();
+    $respuesta = $http->get('http://example.com/perfil/1', [], [
+        'auth' => [
+            'type' => 'oauth',
+            'consumerKey' => 'clave_grande',
+            'consumerSecret' => 'secreto',
+            'token' => '...',
+            'tokenSecret' => '...',
+            'realm' => 'tickets',
+        ],
+    ]);
+
+Autenticación OAuth 2
+---------------------
+
+Dado que OAuth2 es a menudo un solo encabezado, no hay un adaptador de autenticación especializado. En su lugar, puedes crear un cliente con el token de acceso::
+
+    $http = new Client([
+        'headers' => ['Authorization' => 'Bearer ' . $accessToken],
+    ]);
+    $respuesta = $http->get('https://example.com/api/perfil/1');
+
+Autenticación de Proxy
+----------------------
+
+Algunos proxies requieren autenticación para usarlos. Generalmente esta autenticación es básica, pero puede ser implementada por cualquier adaptador de autenticación. Por defecto, Http\\Cliente asumirá la autenticación básica, a menos que se establezca la clave de tipo::
+
+    $http = new Client();
+    $respuesta = $http->get('http://example.com/prueba.php', [], [
+        'proxy' => [
+            'username' => 'mark',
+            'password' => 'prueba',
+            'proxy' => '127.0.0.1:8080',
+        ],
+    ]);
+
+El segundo parámetro de proxy debe ser una cadena con una IP o un dominio sin protocolo. La información de nombre de usuario y contraseña se pasará a través de los encabezados de la solicitud, mientras que la cadena de proxy se pasará a través de `stream_context_create()
+`_.
+
+Creación de Clientes Específicos
+================================
+
+Tener que volver a escribir el nombre de dominio, la autenticación y la configuración del proxy puede volverse tedioso y propenso a errores. Para reducir la posibilidad de errores y aliviar parte de la tediosidad, puedes crear clientes específicos::
+
+    // Crea un cliente específico.
+    $http = new Client([
+        'host' => 'api.example.com',
+        'scheme' => 'https',
+        'auth' => ['username' => 'mark', 'password' => 'prueba'],
+    ]);
+
+    // Realiza una solicitud a api.example.com
+    $respuesta = $http->get('/prueba.php');
+
+Si tu cliente específico solo necesita información de la URL, puedes usar ``createFromUrl()``::
+
+    $http = Client::createFromUrl('https://api.example.com/v1/prueba');
+
+Lo anterior crearía una instancia de cliente con las opciones ``protocol``, ``host`` y
+``basePath`` configuradas.
+
+La siguiente información se puede utilizar al crear un cliente específico:
+
+* host
+* basePath
+* scheme
+* proxy
+* auth
+* port
+* cookies
+* timeout
+* ssl_verify_peer
+* ssl_verify_depth
+* ssl_verify_host
+
+Cualquiera de estas opciones puede ser anulada especificándolas al hacer solicitudes.
+host, scheme, proxy, port se anulan en la URL de la solicitud::
+
+    // Usando el cliente específico que creamos anteriormente.
+    $respuesta = $http->get('http://foo.com/prueba.php');
+
+Lo anterior reemplazará el dominio, el esquema y el puerto. Sin embargo, esta solicitud seguirá utilizando todas las otras opciones definidas cuando se creó el cliente específico.
+
+Consulta :ref:`http_client_request_options` para obtener más información sobre las opciones soportadas.
+
+Configuración y Gestión de Cookies
+==================================
+
+Http\\Cliente también puede aceptar cookies al realizar solicitudes. Además de aceptar cookies, también almacenará automáticamente las cookies válidas establecidas en las respuestas. Cualquier respuesta con cookies se almacenará en la instancia original de Http\\Cliente. Las cookies almacenadas en una instancia de Cliente se incluyen automáticamente en futuras solicitudes a combinaciones de dominio + ruta que coincidan::
+
+    $http = new Client([
+        'host' => 'cakephp.org'
+    ]);
+
+    // Realiza una solicitud que establece algunas cookies
+    $respuesta = $http->get('/');
+
+    // Las cookies de la primera solicitud se incluirán
+    // por defecto.
+    $respuesta2 = $http->get('/changelogs');
+
+Siempre puedes anular las cookies incluidas automáticamente estableciéndolas en los parámetros ``$options`` de la solicitud::
+
+    // Reemplazar una cookie almacenada con un valor personalizado.
+    $respuesta = $http->get('/changelogs', [], [
+        'cookies' => ['sessionid' => '123abc'],
+    ]);
+
+Puedes agregar objetos de cookie al cliente después de crearlo usando el método ``addCookie()``::
+
+    use Cake\Http\Cookie\Cookie;
+
+    $http = new Client([
+        'host' => 'cakephp.org'
+    ]);
+    $http->addCookie(new Cookie('session', 'abc123'));
+
+Objetos de Respuesta
+====================
+
+.. php:namespace:: Cake\Http\Client
+
+.. php:class:: Respuesta
+
+Los objetos de respuesta tienen varios métodos para inspeccionar los datos de respuesta.
+
+Lectura de Cuerpos de Respuesta
+-------------------------------
+
+Puedes leer todo el cuerpo de respuesta como una cadena::
+
+    // Lee toda la respuesta como una cadena.
+    $respuesta->getStringBody();
+
+También puedes acceder al objeto de transmisión para la respuesta y usar sus métodos::
+
+    // Obtiene un Psr\Http\Message\StreamInterface que contiene el cuerpo de respuesta
+    $transmision = $respuesta->getBody();
+
+    // Lee una transmisión de 100 bytes a la vez.
+    while (!$transmision->eof()) {
+        echo $transmision->read(100);
+    }
+
+Lectura de Cuerpos de Respuesta JSON y XML
+------------------------------------------
+
+Dado que las respuestas JSON y XML se utilizan comúnmente, los objetos de respuesta proporcionan una forma de usar accesos para leer datos decodificados. Los datos JSON se decodifican en un array, mientras que los datos XML se decodifican en un árbol ``SimpleXMLElement``::
+
+    // Obtén algo de XML
+    $http = new Client();
+    $respuesta = $http->get('http://example.com/test.xml');
+    $xml = $respuesta->getXml();
+
+    // Obtén algo de JSON
+    $http = new Client();
+    $respuesta = $http->get('http://example.com/test.json');
+    $json = $respuesta->getJson();
+
+Los datos de respuesta decodificados se almacenan en el objeto de respuesta, por lo que acceder a ellos varias veces no tiene un costo adicional.
+
+Acceso a Encabezados de Respuesta
+---------------------------------
+
+Puedes acceder a los encabezados a través de algunos métodos diferentes. Los nombres de los encabezados siempre se tratan como valores insensibles a mayúsculas y minúsculas cuando se accede a ellos a través de métodos::
+
+    // Obtén todos los encabezados como un array asociativo.
+    $respuesta->getHeaders();
+
+    // Obtén un solo encabezado como un array.
+    $respuesta->getHeader('content-type');
+
+    // Obtén un encabezado como una cadena
+    $respuesta->getHeaderLine('content-type');
+
+    // Obtén la codificación de la respuesta
+    $respuesta->getEncoding();
+
+Acceso a Datos de Cookies
+-------------------------
+
+Puedes leer cookies con algunos métodos diferentes dependiendo de cuántos datos necesites sobre las cookies::
+
+    // Obtén todas las cookies (datos completos)
+    $respuesta->getCookies();
+
+    // Obtén el valor de una sola cookie.
+    $respuesta->getCookie('session_id');
+
+    // Obtén los datos completos de una sola cookie
+    // incluye claves de valor, caducidad, ruta, httponly, seguras.
+    $respuesta->getCookieData('session_id');
+
+Comprobación del Código de Estado
+---------------------------------
+
+Los objetos de respuesta proporcionan algunos métodos para verificar los códigos de estado::
+
+    // ¿Fue la respuesta un 20x
+    $respuesta->isOk();
+
+    // ¿Fue la respuesta un 30x
+    $respuesta->isRedirect();
+
+    // Obtén el código de estado
+    $respuesta->getStatusCode();
+
+Cambio de Adaptadores de Transporte
+===================================
+
+Por defecto, ``Http\Client`` preferirá usar un adaptador de transporte basado en ``curl``. Si la extensión de curl no está disponible, se usará un adaptador basado en transmisión en su lugar. Puedes seleccionar un adaptador de transporte específico usando una opción de constructor::
+
+    use Cake\Http\Client\Adapter\Stream;
+
+    $cliente = new Client(['adaptador' => Stream::class]);
+
+.. _httpclient-testing:
+
+Pruebas
+=======
+
+.. php:namespace:: Cake\Http\TestSuite
+
+.. php:trait:: HttpClientTrait
+
+En las pruebas, a menudo querrás crear respuestas simuladas para APIs externas. Puedes
+usar el ``HttpClientTrait`` para definir respuestas a las solicitudes que tu aplicación
+está realizando::
+
+    use Cake\Http\TestSuite\HttpClientTrait;
+    use Cake\TestSuite\TestCase;
+
+    class PruebasDeControladorDeCarrito extends TestCase
+    {
+        use HttpClientTrait;
+
+        public function testCheckout()
+        {
+            // Simula una solicitud POST que se realizará.
+            $this->mockClientPost(
+                'https://example.com/process-payment',
+                $this->newClientResponse(200, [], json_encode(['ok' => true]))
+            );
+            $this->post("/cart/checkout");
+            // Realiza aserciones.
+        }
+    }
+
+Hay métodos para simular los métodos HTTP más comúnmente utilizados::
+
+    $this->mockClientGet(...);
+    $this->mockClientPatch(...);
+    $this->mockClientPost(...);
+    $this->mockClientPut(...);
+    $this->mockClientDelete(...);
+
+.. php:method:: newClientResponse(int $code = 200, array $headers = [], string $body = '')
+
+Como se vio anteriormente, puedes usar el método ``newClientResponse()`` para crear respuestas
+para las solicitudes que tu aplicación realizará. Los encabezados deben ser una lista de
+cadenas::
+
+    $headers = [
+        'Content-Type: application/json',
+        'Connection: close',
+    ];
+    $respuesta = $this->newClientResponse(200, $headers, $body)
 
 .. meta::
     :title lang=es: HttpClient
-    :keywords lang=es: array name,array data,query parameter,query string,php class,string query,test type,string data,google,query results,webservices,apis,parameters,cakephp,meth,search results
+    :keywords lang=es: nombre del arreglo, datos del arreglo, parámetro de consulta, cadena de consulta, clase de PHP, consulta de cadena, tipo de prueba, datos de cadena, Google, resultados de la consulta, servicios web, API, parámetros, CakePHP, método, resultados de búsqueda.

From 1b1a6a508afa958938741a795082da7f30f985d5 Mon Sep 17 00:00:00 2001
From: Andres Campanario 
Date: Thu, 1 Feb 2024 12:38:29 +0100
Subject: [PATCH 4/5] translate to spanish core-libraries:
 plugin,registry-object and security

---
 es/core-libraries/plugin.rst           | 56 ++++++++++++---
 es/core-libraries/registry-objects.rst | 50 ++++++++++---
 es/core-libraries/security.rst         | 98 +++++++++++++++++++++++---
 3 files changed, 176 insertions(+), 28 deletions(-)

diff --git a/es/core-libraries/plugin.rst b/es/core-libraries/plugin.rst
index 64f10b13d0..d39701bc33 100644
--- a/es/core-libraries/plugin.rst
+++ b/es/core-libraries/plugin.rst
@@ -1,13 +1,53 @@
 Clase Plugin
 ############
 
-.. note::
-    La documentación no es compatible actualmente con el idioma español en esta
-    página.
+.. php:namespace:: Cake\Core
 
-    Por favor, siéntase libre de enviarnos un pull request en
-    `Github `_ o utilizar el botón
-    **Improve this Doc** para proponer directamente los cambios.
+.. php:class:: Plugin
 
-    Usted puede hacer referencia a la versión en Inglés en el menú de selección
-    superior para obtener información sobre el tema de esta página.
+La clase Plugin es responsable de la ubicación de recursos y la gestión de rutas de los plugins.
+
+Ubicación de Plugins
+====================
+
+.. php:staticmethod:: path(string $plugin)
+
+Los plugins pueden ser ubicados con Plugin. Usando ``Plugin::path('DebugKit');``
+por ejemplo, te dará la ruta completa al plugin DebugKit::
+
+    $path = Plugin::path('DebugKit');
+
+Comprobar si un Plugin está Cargado
+===================================
+
+Puedes comprobar dinámicamente dentro de tu código si un plugin específico ha sido cargado::
+
+    $isLoaded = Plugin::isLoaded('DebugKit');
+
+Usa ``Plugin::loaded()`` si deseas obtener una lista de todos los plugins actualmente cargados.
+
+Encontrar Rutas a Espacios de Nombres
+=====================================
+
+.. php:staticmethod:: classPath(string $plugin)
+
+Usado para obtener la ubicación de los archivos de clase del plugin::
+
+    $path = App::classPath('DebugKit');
+
+Encontrar Rutas a Recursos
+==========================
+
+.. php:staticmethod:: templatePath(string $plugin)
+
+El método devuelve la ruta a las plantillas de los plugins::
+
+    $path = Plugin::templatePath('DebugKit');
+
+Lo mismo ocurre para la ruta de configuración::
+
+    $path = Plugin::configPath('DebugKit');
+
+.. meta::
+    :title lang=es: Clase Plugin
+    :keywords lang=es: implementación compatible, comportamientos de modelo, gestión de rutas, carga de archivos, clase de PHP, carga de clases, comportamiento de modelo, ubicación de clase, modelo de componente, clase de gestión, cargador automático, nombre de clase, ubicación de directorio, anulación, convenciones, librería, textile, CakePHP, clases de PHP, cargado
diff --git a/es/core-libraries/registry-objects.rst b/es/core-libraries/registry-objects.rst
index 525e3efdeb..477cc02352 100644
--- a/es/core-libraries/registry-objects.rst
+++ b/es/core-libraries/registry-objects.rst
@@ -1,15 +1,45 @@
-Registry Objects
-################
+Objetos de Registro
+###################
 
-.. note::
-    La documentación no es compatible actualmente con el idioma español en esta página.
+Las clases de registro proporcionan una forma sencilla de crear y recuperar instancias cargadas de un tipo de objeto dado. Hay clases de registro para Componentes, Ayudantes, Tareas y Comportamientos.
 
-    Por favor, siéntase libre de enviarnos un pull request en
-    `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios.
+Si bien los ejemplos a continuación utilizarán Componentes, se puede esperar el mismo comportamiento para Ayudantes, Comportamientos y Tareas además de Componentes.
 
-    Usted puede hacer referencia a la versión en Inglés en el menú de selección superior
-    para obtener información sobre el tema de esta página.
+Carga de Objetos
+================
+
+Los objetos pueden cargarse sobre la marcha utilizando add().
+Ejemplo::
+
+    $this->loadComponent('Acl.Acl');
+    $this->addHelper('Flash')
+
+Esto resultará en la carga de la propiedad ``Acl`` y el ayudante ``Flash``.
+La configuración también se puede establecer sobre la marcha. Ejemplo::
+
+    $this->loadComponent('Cookie', ['name' => 'sweet']);
+
+Cualquier clave y valor proporcionados se pasarán al constructor del Componente. La única excepción a esta regla es ``className``. Classname es una clave especial que se utiliza para aliasar objetos en un registro. Esto le permite tener nombres de componentes que no reflejen los nombres de las clases, lo que puede ser útil al extender componentes centrales::
+
+    $this->Flash = $this->loadComponent('Flash', ['className' => 'MiFlashPersonalizado']);
+    $this->Flash->error(); // Realmente utilizando MiFlashPersonalizado::error();
+
+Desencadenar Callbacks
+======================
+
+Los callbacks no son proporcionados por los objetos de registro. Deberías usar el :doc:`sistema de eventos ` para despachar cualquier evento/callback para tu aplicación.
+
+Desactivar Callbacks
+====================
+
+En versiones anteriores, los objetos de colección proporcionaban un método ``disable()`` para desactivar objetos para recibir callbacks. Deberías usar las características en el sistema de eventos para lograr esto ahora. Por ejemplo, podrías desactivar los callbacks de componente de la siguiente manera::
+
+    // Eliminar MyComponent de los callbacks.
+    $this->getEventManager()->off($this->MyComponent);
+
+    // Re-habilitar MyComponent para los callbacks.
+    $this->getEventManager()->on($this->MyComponent);
 
 .. meta::
-    :title lang=es: Object Registry
-    :keywords lang=es: array name,loading components,several different kinds,unified api,loading objects,component names,special key,core components,callbacks,prg,callback,alias,fatal error,collections,memory,priority,priorities
+    :title lang=es: Objetos de Registro
+    :keywords lang=es: nombre de arreglo, cargando componentes, varios tipos diferentes, API unificada, cargando objetos, nombres de componentes, clave especial, componentes principales, callbacks, prg, callback, alias, error fatal, colecciones, memoria, prioridad, prioridades
diff --git a/es/core-libraries/security.rst b/es/core-libraries/security.rst
index 908fccac5b..7e6c8ad1f7 100644
--- a/es/core-libraries/security.rst
+++ b/es/core-libraries/security.rst
@@ -1,19 +1,97 @@
-Security
-########
+Utilidad de Seguridad
+#####################
 
 .. php:namespace:: Cake\Utility
 
 .. php:class:: Security
 
-.. note::
-    La documentación no es compatible actualmente con el idioma español en esta página.
+La `biblioteca de seguridad `_
+maneja medidas básicas de seguridad como proporcionar métodos para hashear y cifrar datos.
 
-    Por favor, siéntase libre de enviarnos un pull request en
-    `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios.
+Cifrado y Descifrado de Datos
+=============================
 
-    Usted puede hacer referencia a la versión en Inglés en el menú de selección superior
-    para obtener información sobre el tema de esta página.
+.. php:staticmethod:: encrypt($texto, $clave, $hmacSalt = null)
+.. php:staticmethod:: decrypt($cifrado, $clave, $hmacSalt = null)
+
+Cifra ``$texto`` usando AES-256. La ``$clave`` debe ser un valor con una gran variabilidad en los datos, similar a una buena contraseña. El resultado devuelto será el valor cifrado con una suma de verificación HMAC.
+
+La extensión `openssl `_ es necesaria para cifrar/descifrar.
+
+Un ejemplo de uso sería::
+
+    // Suponiendo que la clave se almacena en algún lugar donde pueda reutilizarse para
+    // descifrar más tarde.
+    $clave = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA';
+    $resultado = Security::encrypt($valor, $clave);
+
+Si no proporciona un salt HMAC, se utilizará el valor de ``Security::getSalt()``. Los valores cifrados pueden descifrarse usando :php:meth:`Cake\\Utility\\Security::decrypt()`.
+
+Este método **nunca** debe usarse para almacenar contraseñas.
+
+Descifra un valor previamente cifrado. Los parámetros ``$clave`` y ``$hmacSalt`` deben coincidir con los valores utilizados para cifrar o la descifrado fallará. Un ejemplo de uso sería::
+
+    // Suponiendo que la clave se almacena en algún lugar donde pueda reutilizarse para
+    // descifrar más tarde.
+    $clave = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA';
+
+    $cifrado = $usuario->secretos;
+    $resultado = Security::decrypt($cifrado, $clave);
+
+Si no se puede descifrar el valor debido a cambios en la clave o el salt HMAC, se devolverá ``false``.
+
+Hashing de Datos
+================
+
+.. php:staticmethod:: hash( $cadena, $tipo = NULL, $sal = false )
+
+Crea un hash a partir de una cadena usando el método dado. Si no está disponible,
+se usa el siguiente método disponible. Si ``$salt`` está establecido en ``true``, el salt
+de la aplicación será utilizada::
+
+    // Usando el valor de salt de la aplicación
+    $sha1 = Security::hash('CakePHP Framework', 'sha1', true);
+
+    // Usando un valor de salt personalizado
+    $sha1 = Security::hash('CakePHP Framework', 'sha1', 'mi-salt');
+
+    // Usando el algoritmo de hash predeterminado
+    $hash = Security::hash('CakePHP Framework');
+
+El método ``hash()`` soporta las siguientes estrategias de hash:
+
+- md5
+- sha1
+- sha256
+
+Y cualquier otro algoritmo de hash que soporte la función ``hash()`` de PHP.
+
+.. warning::
+
+    No deberías estar usando ``hash()`` para contraseñas en nuevas aplicaciones.
+    En su lugar, deberías usar la clase ``DefaultPasswordHasher`` que utiliza bcrypt
+    de manera predeterminada.
+
+Obtener Datos Aleatorios Seguros
+================================
+
+.. php:staticmethod:: randomBytes($longitud)
+
+Obtiene ``$longitud`` número de bytes de una fuente de aleatoriedad segura. Esta función
+extrae datos de una de las siguientes fuentes:
+
+* La función ``random_bytes`` de PHP.
+* ``openssl_random_pseudo_bytes`` de la extensión SSL.
+
+Si ninguna fuente está disponible, se emitirá una advertencia y se utilizará un valor inseguro
+por razones de compatibilidad hacia atrás.
+
+.. php:staticmethod:: randomString($longitud)
+
+Obtiene una cadena aleatoria de ``$longitud`` caracteres de una fuente de aleatoriedad segura. Este método
+extrae de la misma fuente aleatoria que ``randomBytes()`` y codificará los datos
+como una cadena hexadecimal.
 
 .. meta::
-    :title lang=es: Security
-    :keywords lang=es: security api,secret password,cipher text,php class,class security,text key,security library,object instance,security measures,basic security,security level,string type,fallback,hash,data security,singleton,inactivity,php encrypt,implementation,php security
+    :title lang=es: Utilidad de Seguridad
+    :keywords lang=es: API de seguridad, contraseña secreta, texto cifrado, clase de PHP, seguridad de clase, clave de texto, biblioteca de seguridad, instancia de objeto, medidas de seguridad, seguridad básica, nivel de seguridad, tipo de cadena, alternativa, hash, seguridad de datos, singleton, inactividad, cifrado de PHP, implementación, seguridad de PHP

From c88b520b92811c98eda2090c137b4edc5c300aec Mon Sep 17 00:00:00 2001
From: Andres Campanario 
Date: Mon, 12 Feb 2024 13:03:07 +0100
Subject: [PATCH 5/5] translate core-libraries: text, time, xml, validation,
 internationalization

---
 .../internationalization-and-localization.rst | 567 ++++++++++++++++-
 es/core-libraries/text.rst                    | 358 ++++++++++-
 es/core-libraries/time.rst                    | 487 +++++++++++++-
 es/core-libraries/validation.rst              | 595 +++++++++++++++++-
 es/core-libraries/xml.rst                     | 182 +++++-
 5 files changed, 2145 insertions(+), 44 deletions(-)

diff --git a/es/core-libraries/internationalization-and-localization.rst b/es/core-libraries/internationalization-and-localization.rst
index 2fafe177fb..c70d477360 100644
--- a/es/core-libraries/internationalization-and-localization.rst
+++ b/es/core-libraries/internationalization-and-localization.rst
@@ -1,15 +1,564 @@
-Internationalization & Localization
+Internacionalización y Localización
 ###################################
 
-.. note::
-    La documentación no es compatible actualmente con el idioma español en esta página.
+Una de las mejores formas para que una aplicación llegue a una audiencia más amplia es adaptarse a varios idiomas. Esto a menudo puede resultar una tarea desafiante, pero las características de internacionalización y localización en CakePHP hacen que sea mucho más fácil.
 
-    Por favor, siéntase libre de enviarnos un pull request en
-    `Github `_ o utilizar el botón **Improve this Doc** para proponer directamente los cambios.
+Primero, es importante entender algunos términos. *Internacionalización* se refiere a la capacidad de una aplicación de ser localizada. El término *localización* se refiere a la adaptación de una aplicación para cumplir con requisitos específicos de idioma (o cultura) (es decir, una "configuración regional"). La internacionalización y la localización a menudo se abrevian como i18n y l10n respectivamente; 18 y 10 son el número de caracteres entre el primer y el último caracter.
 
-    Usted puede hacer referencia a la versión en Inglés en el menú de selección superior
-    para obtener información sobre el tema de esta página.
+Configuración de Traducciones
+=============================
+
+Solo hay algunos pasos para pasar de una aplicación de un solo idioma a una aplicación multilingüe, el primero de los cuales es hacer uso de la función :php:func:`__()` en tu código. A continuación se muestra un ejemplo de código para una aplicación de un solo idioma:
+
+    

Artículos Populares

+ +Para internacionalizar tu código, todo lo que necesitas hacer es envolver las cadenas en :php:func:`__()` de la siguiente manera: + +

+ +Sin hacer nada más, estos dos ejemplos de código son funcionalmente idénticos: ambos enviarán el mismo contenido al navegador. La función :php:func:`__()` traducirá la cadena pasada si hay una traducción disponible, o la devolverá sin modificar. + +Archivos de Idioma +------------------ + +Las traducciones pueden estar disponibles utilizando archivos de idioma almacenados en la aplicación. El formato predeterminado para los archivos de traducción de CakePHP es el formato `Gettext `_. Los archivos deben colocarse bajo **resources/locales/** y dentro de este directorio, debería haber un subdirectorio para cada idioma que la aplicación necesite soportar: + + resources/ + locales/ + en_US/ + default.po + en_GB/ + default.po + validation.po + es/ + default.po + +El dominio predeterminado es 'default', por lo tanto, la carpeta de la configuración regional debe contener al menos el archivo **default.po** como se muestra arriba. Un dominio se refiere a cualquier agrupación arbitraria de mensajes de traducción. Cuando no se usa ningún grupo, se selecciona el grupo predeterminado. + +Los mensajes de cadenas centrales extraídos de la biblioteca CakePHP pueden almacenarse por separado en un archivo llamado **cake.po** en **resources/locales/**. La `biblioteca localizada de CakePHP `_ alberga traducciones para las cadenas traducidas orientadas al cliente en el núcleo (el dominio del pastel). Para usar estos archivos, enlázalos o cópialos en su ubicación esperada: **resources/locales//cake.po**. Si tu configuración regional está incompleta o es incorrecta, envía un PR en este repositorio para corregirla. + +Los plugins también pueden contener archivos de traducción, la convención es usar la versión con guiones bajos del nombre del plugin como el dominio para los mensajes de traducción: + + MiPlugin/ + resources/ + locales/ + fr/ + mi_plugin.po + adicional.po + de/ + mi_plugin.po + +Las carpetas de traducción pueden ser el código ISO de dos o tres letras del idioma o el nombre completo de la configuración regional de ICU como ``fr_FR``, ``es_AR``, ``da_DK`` que contiene tanto el idioma como el país donde se habla. + +Consulta https://www.localeplanet.com/icu/ para obtener la lista completa de configuraciones regionales. + +.. versionchanged:: 4.5.0 + +A partir de 4.5.0, los plugin pueden contener múltiples dominios de traducción. Usa ``MiPlugin.adicional`` para hacer referencia a los dominios del plugin. + +Un archivo de traducción de ejemplo podría verse así: + +.. code-block:: pot + + msgid "My name is {0}" + msgstr "Me llamo {0}" + + msgid "I'm {0,number} years old" + msgstr "Tengo {0,number} años" + +.. nota:: + Las traducciones se almacenan en caché. ¡Asegúrate de siempre limpiar la caché después de hacer cambios en las traducciones! Puedes usar la + :doc:`herramienta de caché ` y ejecutar, por ejemplo, + ``bin/cake cache clear _cake_core_``, o borrar manualmente la carpeta ``tmp/cache/persistent`` + (si usas una caché basada en archivos). + +Extraer Archivos Pot con I18n Shell +------------------------------------ + +Para crear los archivos pot a partir de `__()` y otros tipos de mensajes internacionalizados que se pueden encontrar en el código de la aplicación, puedes usar el comando i18n. Por favor, lee el :doc:`capítulo siguiente ` para +aprender más. + +Configurando la Configuración Regional Predeterminada +----------------------------------------------------- + +La configuración regional predeterminada se puede establecer en tu archivo **config/app.php** configurando ``App.defaultLocale``:: + + 'App' => [ + ... + 'defaultLocale' => env('APP_DEFAULT_LOCALE', 'es_ES'), + ... + ] + +Esto controlará varios aspectos de la aplicación, incluido el idioma de las traducciones predeterminadas, el formato de fecha, el formato de número y la moneda siempre que alguno de ellos se muestre utilizando las bibliotecas de localización que proporciona CakePHP. + +Cambiar la Configuración Regional en Tiempo de Ejecución +-------------------------------------------------------- + +Para cambiar el idioma de las cadenas traducidas, puedes llamar a este método:: + + use Cake\I18n\I18n; + + I18n::setLocale('de_DE'); + +Esto también cambiará cómo se formatean los números y las fechas cuando se usan una de las herramientas de localización. + +Usando Funciones de Traducción +============================== + +CakePHP proporciona varias funciones que te ayudarán a internacionalizar tu aplicación. La más utilizada es :php:func:`__()`. Esta función se utiliza para recuperar un solo mensaje de traducción o devolver la misma cadena si no se encontró ninguna traducción:: + + echo __('Artículos Populares'); + +Si necesitas agrupar tus mensajes, por ejemplo, traducciones dentro de un plugin, puedes usar la función :php:func:`__d()` para obtener mensajes de otro dominio:: + + echo __d('mi_plugin', 'Trending ahora mismo'); + +.. nota:: + + Si deseas traducir plugins que tengan espacios de nombres de vendor, debes usar la cadena de dominio ``vendor/nombre_del_plugin``. Pero el archivo de idioma relacionado será ``plugins///resources/locales//nombre_del_plugin.po`` dentro de la carpeta de tu plugin. + +A veces, las cadenas de traducción pueden ser ambiguas para las personas que las traducen. Esto puede suceder si dos cadenas son idénticas pero se refieren a cosas diferentes. Por ejemplo, 'carta' tiene múltiples significados en inglés. Para resolver ese problema, puedes usar la función :php:func:`__x()`:: + + echo __x('written communication', 'He read the first letter'); + + echo __x('alphabet learning', 'He read the first letter'); + +El primer argumento es el contexto del mensaje y el segundo es el mensaje que se va a traducir. + +.. code-block:: pot + + msgctxt "written communication" + msgid "He read the first letter" + msgstr "Er las den ersten Brief" + +Usando Variables en Mensajes de Traducción +------------------------------------------ + +Las funciones de traducción te permiten interpolar variables en los mensajes utilizando marcadores especiales definidos en el mensaje mismo o en la cadena traducida:: + + echo __("Hola, mi nombre es {0}, tengo {1} años", ['Sara', 12]); + +Los marcadores son numéricos y corresponden a las claves en el array pasado. También puedes pasar variables como argumentos independientes a la función:: + + echo __("Pequeño paso para {0}, gran salto para {1}", 'el Hombre', 'la Humanidad'); + +Todas las funciones de traducción admiten reemplazos de marcadores:: + + __d('validación', 'El campo {0} no puede dejarse vacío', 'Nombre'); + + __x('alfabeto', 'Él leyó la letra {0}', 'Z'); + +El carácter ``'`` (comilla simple) actúa como un código de escape en los mensajes de traducción. Cualquier variable entre comillas simples no será reemplazada y se trata como texto literal. Por ejemplo:: + + __("Esta variable '{0}' será reemplazada.", 'no'); + +Al usar dos comillas simples adyacentes, tus variables serán reemplazadas correctamente:: + + __("Esta variable ''{0}'' será reemplazada.", 'sí'); + +Estas funciones aprovechan el `ICU MessageFormatter `_ +para que puedas traducir mensajes y localizar fechas, números y monedas al mismo tiempo:: + + echo __( + 'Hola {0}, tu saldo el {1,date} es {2,number,currency}', + ['Carlos', new DateTime('2014-01-13 11:12:00'), 1354.37] + ); + + // Devuelve + Hola Carlos, tu saldo el 13 de enero de 2014, 11:12 AM es $ 1,354.37 + +Los números en marcadores también pueden ser formateados con un control detallado de la salida:: + + echo __( + 'Has recorrido {0,number} kilómetros en {1,number,integer} semanas', + [5423.344, 5.1] + ); + + // Devuelve + Has recorrido 5,423.34 kilómetros en 5 semanas + + echo __('Hay {0,number,#,###} personas en la tierra', 6.1 * pow(10, 8)); + + // Devuelve + Hay 6,100,000,000 personas en la tierra + +Esta es la lista de especificadores de formato que puedes colocar después de la palabra ``number``: + +* ``integer``: Elimina la parte decimal +* ``currency``: Coloca el símbolo de la moneda local y redondea los decimales +* ``percent``: Formatea el número como un porcentaje + +Las fechas también pueden ser formateadas usando la palabra ``date`` después del marcador numérico. Una lista de opciones adicionales sigue: + +* ``short`` +* ``medium`` +* ``long`` +* ``full`` + +La palabra ``time`` después del marcador numérico también es aceptada y entiende las mismas opciones que ``date``. + +También puedes usar marcadores con nombres como ``{nombre}`` en las cadenas de mensaje. Cuando uses marcadores con nombres, pasa el marcador y el reemplazo en un arreglo usando pares de clave/valor, por ejemplo:: + + // imprime: ¡Hola. Mi nombre es Sara. Tengo 12 años. + echo __("¡Hola. Mi nombre es {nombre}. Tengo {edad} años.", ['nombre' => 'Sara', 'edad' => 12]); + +Plurales +-------- + +Una parte crucial de internacionalizar tu aplicación es obtener tus mensajes pluralizados correctamente dependiendo del idioma en el que se muestran. CakePHP proporciona un par de formas de seleccionar correctamente plurales en tus mensajes. + +Usando la Selección de Plurales ICU +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +La primera es aprovechar el formato de mensaje ``ICU`` que viene por defecto en las funciones de traducción. En el archivo de traducciones podrías tener las siguientes cadenas + +.. code-block:: pot + + msgid "{0,plural,=0{No se encontraron registros} =1{Se encontró 1 registro} other{Se encontraron # registros}}" + msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{# resultados}}" + + msgid "{placeholder,plural,=0{No se encontraron registros} =1{Se encontró 1 registro} other{Se encontraron {1} registros}}" + msgstr "{placeholder,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" + +Y en la aplicación utiliza el siguiente código para mostrar cualquiera de las traducciones para dicha cadena:: + + __('{0,plural,=0{No se encontraron registros }=1{Se encontró 1 registro} other{Se encontraron # registros}}', [0]); + + // Devuelve "No se encontraron registros" ya que el argumento {0} es 0 + + __('{0,plural,=0{No se encontraron registros} =1{Se encontró 1 registro} other{Se encontraron # registros}}', [1]); + + // Devuelve "Se encontró 1 registro" porque el argumento {0} es 1 + + __('{placeholder,plural,=0{No se encontraron registros} =1{Se encontró 1 registro} other{Se encontraron {1} registros}}', [0, 'muchos', 'placeholder' => 2]) + + // Devuelve "muchos resultados" porque el argumento {placeholder} es 2 y + // el argumento {1} es 'muchos' + +Un vistazo más cercano al formato que acabamos de usar hará evidente cómo se construyen los mensajes:: + + { [contenedor de recuento],plural, caso1{mensaje} caso2{mensaje} caso3{...} ... } + +El ``[contenedor de recuento]`` puede ser el número de clave del arreglo de cualquiera de las variables que pases a la función de traducción. Se usará para seleccionar la forma plural correcta. + +Ten en cuenta que para hacer referencia a ``[contenedor de recuento]`` dentro de ``{mensaje}`` debes usar ``#``. + +Por supuesto, puedes usar identificadores de mensaje más simples si no quieres escribir la secuencia completa de selección plural en tu código. + +.. code-block:: pot + + msgid "search.results" + msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" + +Luego usa la nueva cadena en tu código:: + + __('search.results', [2, 2]); + + // Devuelve: "2 resultados" + +La última versión tiene la desventaja de que se necesita tener un archivo de mensajes de traducción incluso para el idioma predeterminado, pero tiene la ventaja de que hace que el código sea más legible y deja las complicadas cadenas de selección plural en los archivos de traducción. + +A veces, usar coincidencias directas de números en plurales es poco práctico. Por ejemplo, idiomas como el árabe requieren un plural diferente cuando te refieres a pocas cosas y otra forma plural para muchas cosas. En esos casos, puedes usar los alias de coincidencia de ICU. En lugar de escribir:: + + =0{No hay resultados} =1{...} other{...} + +Puedes hacer:: + + zero{No hay resultados} one{Un resultado} few{...} many{...} other{...} + +Asegúrate de leer la `Guía de Reglas Plurales de Idiomas `_ para obtener una visión completa de los alias que puedes usar para cada idioma. + +Usando la Selección Plural de Gettext +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +El segundo formato de selección plural aceptado es usando las capacidades integradas +de Gettext. En este caso, los plurales se almacenarán en el archivo ``.po`` +creando una línea de traducción de mensaje separada por cada forma plural: + +.. code-block:: pot + + # Un identificador de mensaje para singular + msgid "Un archivo eliminado" + # Otro para plural + msgid_plural "{0} archivos eliminados" + # Traducción en singular + msgstr[0] "Un fichero eliminado" + # Traducción en plural + msgstr[1] "{0} ficheros eliminados" + +Al usar este otro formato, se requiere usar otra función de traducción:: + + // Devuelve: "10 ficheros eliminados" + $count = 10; + __n('Un archivo eliminado', '{0} archivos eliminados', $count, $count); + + // También es posible usarlo dentro de un dominio + __dn('mi_plugin', 'Un archivo eliminado', '{0} archivos eliminados', $count, $count); + +El número dentro de ``msgstr[]`` es el número asignado por Gettext para la forma plural +del idioma. Algunos idiomas tienen más de dos formas plurales, por ejemplo el croata: + +.. code-block:: pot + + msgid "Un archivo eliminado" + msgid_plural "{0} archivos eliminados" + msgstr[0] "{0} datoteka je uklonjena" + msgstr[1] "{0} datoteke su uklonjene" + msgstr[2] "{0} datoteka je uklonjeno" + +Por favor, visita la `página de idiomas de Launchpad `_ +para obtener una explicación detallada de los números de forma plural para cada idioma. + +Creando Tus Propios Traductores +=============================== + +Si necesitas divergir de las convenciones de CakePHP con respecto a dónde y cómo se almacenan los mensajes de traducción, puedes crear tu propio cargador de mensajes de traducción. La forma más fácil de crear tu propio traductor es definiendo un cargador para un único dominio y localidad:: + + use Cake\I18n\Package; + // Antes de la versión 4.2, necesitas usar Aura\Intl\Package + + I18n::setTranslator('animales', function () { + $package = new Package( + 'default', // La estrategia de formato (ICU) + 'default' // El dominio de reserva + ); + $package->setMessages([ + 'Perro' => 'Chien', + 'Gato' => 'Chat', + 'Pájaro' => 'Oiseau' + ... + ]); + + return $package; + }, 'fr_FR'); + +El código anterior se puede agregar a tu **config/bootstrap.php** para que las traducciones se puedan encontrar antes de que se utilice cualquier función de traducción. Lo mínimo absoluto que se requiere para crear un traductor es que la función de cargador debe devolver un objeto ``Cake\I18n\Package`` (antes de la versión 4.2 debería ser un objeto ``Aura\Intl\Package``). Una vez que el código está en su lugar, puedes usar las funciones de traducción como de costumbre:: + + I18n::setLocale('fr_FR'); + __d('animales', 'Perro'); // Devuelve "Chien" + +Como ves, los objetos ``Package`` toman los mensajes de traducción como un arreglo. Puedes pasar el método ``setMessages()`` como quieras: con código en línea, incluyendo otro archivo, llamando a otra función, etc. CakePHP proporciona algunas funciones de cargador que puedes reutilizar si solo necesitas cambiar dónde se cargan los mensajes. Por ejemplo, aún puedes usar archivos **.po**, pero cargados desde otra ubicación:: + + use Cake\I18n\MessagesFileLoader as Loader; + + // Carga los mensajes desde resources/locales/folder/sub_folder/filename.po + I18n::setTranslator( + 'animales', + new Loader('filename', 'folder/sub_folder', 'po'), + 'fr_FR' + ); + +Creación de Analizadores de Mensajes +------------------------------------ + +Es posible continuar utilizando las mismas convenciones que CakePHP utiliza, pero usar un analizador de mensajes distinto a ``PoFileParser``. Por ejemplo, si deseas cargar mensajes de traducción usando ``YAML``, primero necesitarás crear la clase del analizador:: + + namespace App\I18n\Parser; + + class YamlFileParser + { + public function parse($file) + { + return yaml_parse_file($file); + } + } + +El archivo debe ser creado en el directorio **src/I18n/Parser** de tu aplicación. Luego, crea el archivo de traducciones bajo **resources/locales/fr_FR/animals.yaml** + +.. code-block:: yaml + + Dog: Chien + Cat: Chat + Bird: Oiseau + +Y finalmente, configura el cargador de traducción para el dominio y la localidad:: + + use Cake\I18n\MessagesFileLoader as Loader; + + I18n::setTranslator( + 'animals', + new Loader('animals', 'fr_FR', 'yaml'), + 'fr_FR' + ); + +.. _creating-generic-translators: + +Creación de Traductores Genéricos +------------------------------------ + +Configurar traductores llamando a ``I18n::setTranslator()`` para cada dominio y +localidad que necesites soportar puede resultar tedioso, especialmente si necesitas +soportar más que unas pocas localidades diferentes. Para evitar este problema, CakePHP te permite definir cargadores de traducción genéricos para cada dominio. + +Imagina que deseas cargar todas las traducciones para el dominio predeterminado y para cualquier idioma desde un servicio externo:: + + use Cake\I18n\Package; + // Antes de la versión 4.2 necesitas usar Aura\Intl\Package + + I18n::config('default', function ($domain, $locale) { + $locale = Locale::parseLocale($locale); + $lang = $locale['language']; + $messages = file_get_contents("http://ejemplo.com/traducciones/$lang.json"); + + return new Package( + 'default', // Formateador + null, // Reserva (ninguna para el dominio predeterminado) + json_decode($messages, true) + ) + }); + +El ejemplo anterior llama a un servicio externo de ejemplo para cargar un archivo JSON con las traducciones y luego simplemente construye un objeto ``Package`` para cualquier localidad que se solicite en la aplicación. + +Si deseas cambiar cómo se cargan los paquetes para todos los paquetes que no tienen cargadores específicos establecidos, puedes reemplazar el cargador de paquetes de reserva utilizando el paquete ``_fallback``:: + + I18n::config('_fallback', function ($domain, $locale) { + // Código personalizado que devuelve un paquete aquí. + }); + +Plurales y Contexto en Traductores Personalizados +------------------------------------------------- + +Los arreglos utilizados para ``setMessages()`` pueden ser diseñados para instruir al traductor a almacenar mensajes bajo diferentes dominios o para activar la selección de plurales al estilo Gettext. A continuación, se muestra un ejemplo de cómo almacenar traducciones para la misma clave en diferentes contextos:: + + [ + 'Él lee la letra {0}' => [ + 'alfabeto' => 'Él lee la letra {0}', + 'comunicación escrita' => 'Él lee la carta {0}', + ], + ] + +De manera similar, puedes expresar plurales al estilo Gettext utilizando el arreglo de mensajes al tener una clave de arreglo anidada por forma plural:: + + [ + 'He leído un libro' => 'He leído un libro', + 'He leído {0} libros' => [ + 'He leído un libro', + 'He leído {0} libros', + ], + ] + +Usando Diferentes Formateadores +------------------------------- + +En ejemplos anteriores hemos visto que los Paquetes se construyen utilizando ``default`` como primer argumento, y se indicó con un comentario que correspondía al formateador a utilizar. Los formateadores son clases responsables de interpolar variables en mensajes de traducción y seleccionar la forma plural correcta. + +Si estás trabajando con una aplicación heredada, o no necesitas el poder ofrecido por el formato de mensaje ICU, CakePHP también proporciona el formateador ``sprintf``:: + + return Package('sprintf', 'dominio_de_reserva', $mensajes); + +Los mensajes a traducir se pasarán a la función ``sprintf()`` para interpolar las variables:: + + __('Hola, mi nombre es %s y tengo %d años', 'José', 29); + +Es posible establecer el formateador predeterminado para todos los traductores creados por CakePHP antes de que se utilicen por primera vez. Esto no incluye los traductores creados manualmente utilizando los métodos ``setTranslator()`` y ``config()``:: + + I18n::defaultFormatter('sprintf'); + +Localización de Fechas y Números +================================ + +Cuando se muestran Fechas y Números en tu aplicación, a menudo necesitas que se formateen según el formato preferido para el país o región en la que deseas que se muestre tu página. + +Para cambiar cómo se muestran las fechas y los números, solo necesitas cambiar la configuración de localidad actual y utilizar las clases correctas:: + + use Cake\I18n\I18n; + use Cake\I18n\DateTime; + use Cake\I18n\Number; + + I18n::setLocale('fr-FR'); + + $fecha = new DateTime('2015-04-05 23:00:00'); + + echo $fecha; // Muestra 05/04/2015 23:00 + + echo Number::format(524.23); // Muestra 524,23 + +Asegúrate de leer las secciones :doc:`/core-libraries/time` y :doc:`/core-libraries/number` para aprender más sobre las opciones de formato. + +Por defecto, las fechas devueltas para los resultados del ORM utilizan la clase ``Cake\I18n\DateTime``, por lo que mostrarlas directamente en tu aplicación se verá afectado por cambiar la localidad actual. + +Análisis de Datos de Fecha y Hora Localizados +---------------------------------------------- + +Cuando aceptas datos localizados desde la solicitud, es bueno aceptar información de fecha y hora en el formato localizado del usuario. En un controlador, o en un :doc:`/controllers/middleware`, puedes configurar los tipos de Fecha, Hora y FechaHora para analizar formatos localizados:: + + use Cake\Database\TypeFactory; + + // Habilita el análisis del formato de localización predeterminado. + TypeFactory::build('datetime')->useLocaleParser(); + + // Configura un formato de analizador de fecha y hora personalizado. + TypeFactory::build('datetime')->useLocaleParser()->setLocaleFormat('dd-M-y'); + + // También puedes usar constantes de IntlDateFormatter. + TypeFactory::build('datetime')->useLocaleParser() + ->setLocaleFormat([IntlDateFormatter::SHORT, -1]); + +El formato de análisis predeterminado es el mismo que el formato de cadena predeterminado. + +Conversión de Datos de Solicitud desde la Zona Horaria del Usuario +------------------------------------------------------------------ + +Cuando manejas datos de usuarios en diferentes zonas horarias, necesitarás convertir las fechas y horas en los datos de la solicitud a la zona horaria de tu aplicación. Puedes usar ``setUserTimezone()`` desde un controlador o un :doc:`/controllers/middleware` para hacer este proceso más sencillo:: + + // Establece la zona horaria del usuario + TypeFactory::build('datetime')->setUserTimezone($usuario->zona_horaria); + +Una vez establecido, cuando tu aplicación cree o actualice entidades a partir de los datos de la solicitud, el ORM convertirá automáticamente los valores de fecha y hora desde la zona horaria del usuario a la zona horaria de tu aplicación. Esto asegura que tu aplicación siempre esté trabajando en la zona horaria definida en ``App.defaultTimezone``. + +Si tu aplicación maneja información de fecha y hora en varias acciones, puedes usar un middleware para definir tanto la conversión de zona horaria como el análisis de localización:: + + namespace App\Middleware; + + use Cake\Database\TypeFactory; + use Psr\Http\Message\ResponseInterface; + use Psr\Http\Message\ServerRequestInterface; + use Psr\Http\Server\MiddlewareInterface; + use Psr\Http\Server\RequestHandlerInterface; + + class DatetimeMiddleware implements MiddlewareInterface + { + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + // Obtén el usuario desde la solicitud. + // Este ejemplo asume que tu entidad de usuario tiene un atributo de zona horaria. + $usuario = $request->getAttribute('identidad'); + if ($usuario) { + TypeFactory::build('datetime') + ->useLocaleParser() + ->setUserTimezone($usuario->zona_horaria); + } + + return $handler->handle($request); + } + } + +Eligiendo Automáticamente el Idioma Basado en los Datos de la Solicitud +======================================================================= + +Al utilizar el ``LocaleSelectorMiddleware`` en tu aplicación, CakePHP establecerá automáticamente el idioma basado en el usuario actual:: + + // en src/Application.php + use Cake\I18n\Middleware\LocaleSelectorMiddleware; + + // Actualiza la función del middleware, añadiendo el nuevo middleware + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + // Agrega el middleware y establece los idiomas válidos + $middlewareQueue->add(new LocaleSelectorMiddleware(['en_US', 'fr_FR'])); + // Para aceptar cualquier valor de encabezado de idioma + $middlewareQueue->add(new LocaleSelectorMiddleware(['*'])); + } + +El ``LocaleSelectorMiddleware`` utilizará el encabezado ``Accept-Language`` para establecer automáticamente el idioma preferido del usuario. Puedes usar la opción de lista de idiomas para restringir qué idiomas se utilizarán automáticamente. + +Traducir Contenido/Entidades +============================= + +Si deseas traducir contenido/entidades, entonces deberías consultar el :doc:`Comportamiento de Traducción `. .. meta:: - :title lang=es: Internationalization & Localization - :keywords lang=es: internationalization localization,internationalization and localization,language application,gettext,l10n,pot,i18n,translation,languages + :title lang=es: Internacionalización y Localización + :keywords lang=es: internacionalización y localización, internacionalización y localización, aplicación de idioma, gettext, l10n, pot, i18n, traducción, idiomas diff --git a/es/core-libraries/text.rst b/es/core-libraries/text.rst index 1ac31f81cd..327a7eb0f7 100644 --- a/es/core-libraries/text.rst +++ b/es/core-libraries/text.rst @@ -5,17 +5,361 @@ Text .. php:class:: Text +La clase Text incluye métodos de conveniencia para crear y manipular cadenas y normalmente se accede estáticamente. Por ejemplo: ``Text::uuid()``. + +Si necesitas funcionalidades de :php:class:`Cake\\View\\Helper\\TextHelper` fuera de una ``View``, utiliza la clase ``Text``:: + + namespace App\Controller; + + use Cake\Utility\Text; + + class UsersController extends AppController + { + public function initialize(): void + { + parent::initialize(); + $this->loadComponent('Auth') + }; + + public function afterLogin() + { + $message = $this->Users->find('new_message')->first(); + if (!empty($message)) { + // Notifica al usuario de un nuevo mensaje + $this->Flash->success(__( + 'Tienes un nuevo mensaje: {0}', + Text::truncate($message['Message']['body'], 255, ['html' => true]) + )); + } + } + } + +Convertir Cadenas en ASCII +========================== + +.. php:staticmethod:: transliterate($string, $transliteratorId = null) + +Transliterate convierte de forma predeterminada todos los caracteres en la cadena proporcionada en caracteres ASCII equivalentes. +El método espera codificación UTF-8. La conversión de caracteres puede ser controlada utilizando identificadores de transliteración +que puedes pasar utilizando el argumento ``$transliteratorId`` o cambiar la cadena de identificador predeterminada +utilizando ``Text::setTransliteratorId()``. Los identificadores de transliteración de ICU son básicamente +de la forma ``