Capítulo 4. Plantillas de cuchillas
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Comparado con la mayoría de los lenguajes de backend, PHP funciona relativamente bien como lenguaje de plantillas. Pero tiene sus defectos, y también es feo utilizar <?php
en línea por todas partes, así que puedes esperar que la mayoría de los marcos de trabajo modernos ofrezcan un lenguaje de plantillas.
Laravel ofrece un motor de plantillas personalizado llamado Blade, que está inspirado en el motor Razor de .NET. Presume de una sintaxis concisa, una curva de aprendizaje poco pronunciada, un modelo de herencia potente e intuitivo, y una fácil extensibilidad.
Para echar un vistazo rápido a cómo es escribir Blade en, consulta el Ejemplo 4-1.
Ejemplo 4-1. Muestras de cuchillas
<
h1
>
{{
$group
->
title
}}
</
h1
>
{
!!
$group
->
heroImageHtml
()
!!
}
@
forelse
(
$users
as
$user
)
•
{{
$user
->
first_name
}}
{{
$user
->
last_name
}}
<
br
>
@
empty
No
users
in
this
group
.
@
endforelse
Como puedes ver, Blade utiliza llaves para su "eco" e introduce una convención en la que sus etiquetas personalizadas, llamadas "directivas", llevan como prefijo @
. Utilizarás directivas para todas tus estructuras de control y también para la herencia y cualquier funcionalidad personalizada que quieras añadir.
La sintaxis de Blade es limpia y concisa, por lo que en el fondo es más agradable y ordenado trabajar con él que con las alternativas. Pero en el momento en que necesitas algo de cierta complejidad en tus plantillas -herencia anidada, condicionales complejos o recursividad- Blade empieza a brillar de verdad. Al igual que los mejores componentes de Laravel, toma requisitos complejos de la aplicación y los hace fáciles y accesibles.
Además, como toda la sintaxis de Blade se compila en código PHP normal y luego se almacena en caché, es rápido y te permite utilizar PHP nativo en tus archivos Blade si quieres. Sin embargo, yo recomendaría evitar el uso de PHP si es posible; normalmente, si necesitas hacer algo que no puedes hacer con Blade o con una directiva personalizada de Blade, no debe estar en la plantilla.
Usar Twig con Laravel
A diferencia de muchos otros frameworks basados en Symfony, Laravel no utiliza Twig por defecto. Pero si estás enamorado de Twig, existe un paquete TwigBridge que facilita el uso de Twig en lugar de Blade.
Eco de datos
Como puedes ver en el Ejemplo 4-1, {{
y }}
se utilizan para envolver secciones de PHP de las que te gustaría hacer eco. {{
es similar a $variable
}}<?=
$variable
?>
en PHP plano.
Sin embargo, es diferente en un aspecto, y puede que ya lo hayas adivinado: Blade escapa por defecto de todos los ecos utilizando el htmlentities()
de PHP para proteger a tus usuarios de la inserción de scripts maliciosos. Esto significa que {{
es funcionalmente equivalente a $variable
}}<?=
htmlentities(
$variable
)?>
. Si quieres hacer eco sin el escape, utiliza en su lugar {!!
y !!}
.
Estructuras de control
La mayoría de de las estructuras de control en Blade te resultarán muy familiares. Muchas reproducen directamente el nombre y la estructura de la misma etiqueta en PHP.
Hay algunos ayudantes prácticos, pero en general, las estructuras de control tienen un aspecto más limpio que en PHP.
Condicionales
En primer lugar, echemos un vistazo en a las estructuras de control que permiten la lógica.
@si
Blade's @if ($condition)
se compila en <?php if ($condition): ?>
. @else
, @elseif
y @endif
también compilan exactamente el mismo estilo de sintaxis en PHP. Echa un vistazo al Ejemplo 4-2 para ver algunos ejemplos.
Ejemplo 4-2. @if
, @else
, @elseif
, y @endif
@
if
(
count
(
$talks
)
===
1
)
There
is
one
talk
at
this
time
period
.
@
elseif
(
count
(
$talks
)
===
0
)
There
are
no
talks
at
this
time
period
.
@
else
There
are
{{
count
(
$talks
)
}}
talks
at
this
time
period
.
@
endif
Al igual que con los condicionales nativos de PHP, puedes mezclarlos y combinarlos como quieras. No tienen ninguna lógica especial; se trata literalmente de un analizador sintáctico que busca algo con forma de @if (
y lo sustituye por el código PHP apropiado.$condition
)
@unless y @endunless
@unless
, en por otra parte, es una nueva sintaxis que no tiene un equivalente directo en PHP. Es la inversa directa de @if
. @unless ($condition)
es lo mismo que <?php if (! $condition)
. Puedes verla en uso en el Ejemplo 4-3.
Ejemplo 4-3. @unless
y @endunless
@
unless
(
$user
->
hasPaid
())
You
can
complete
your
payment
by
switching
to
the
payment
tab
.
@
endunless
Bucles
A continuación, echemos un vistazo a los bucles.
@for, @foreach y @while
@for
, @foreach
, y @while
funcionan igual en Blade que en PHP; véanse los Ejemplos 4-4, 4-5 y 4-6.
Ejemplo 4-4. @for
y @endfor
@
for
(
$i
=
0
;
$i
<
$talk
->
slotsCount
();
$i
++
)
The
number
is
{{
$i
}}
<
br
>
@
endfor
Ejemplo 4-5. @foreach
y @endforeach
@
foreach
(
$talks
as
$talk
)
•
{{
$talk
->
title
}}
({{
$talk
->
length
}}
minutes
)
<
br
>
@
endforeach
Ejemplo 4-6. @while
y @endwhile
@
while
(
$item
=
array_pop
(
$items
))
{{
$item
->
orSomething
()
}}
<
br
>
@
endwhile
@forelse y @endforelse
@forelse
es un @foreach
que también te permite programar un fallback si el objeto sobre el que estás iterando está vacío. Lo vimos en acción al principio de este capítulo; el Ejemplo 4-7 muestra otro ejemplo.
Ejemplo 4-7. @forelse
@
forelse
(
$talks
as
$talk
)
•
{{
$talk
->
title
}}
({{
$talk
->
length
}}
minutes
)
<
br
>
@
empty
No
talks
this
day
.
@
endforelse
Herencia de plantillas
Blade proporciona una estructura para la herencia de plantillas que permite a las vistas ampliar, modificar e incluir otras vistas.
Veamos cómo se estructura la herencia con Blade.
Definir secciones con @section/@show y @yield
Vamos a empezar con un diseño Blade de nivel superior, como en el Ejemplo 4-8. Esta es la definición de una envoltura de página genérica en la que más tarde colocaremos contenido específico de la página.
Ejemplo 4-8. Disposición de la hoja
<!-- resources/views/layouts/master.blade.php -->
<
html
>
<
head
>
<
title
>
My Site | @yield('title', 'Home Page')</
title
>
</
head
>
<
body
>
<
div
class
=
"container"
>
@yield('content')</
div
>
@section('footerScripts')<
script
src
=
"app.js"
></
script
>
@show</
body
>
</
html
>
Esto se parece un poco a una página HTML normal, pero puedes ver que hemos cedido en dos lugares (title
y content
) y hemos definido una sección en un tercero (footerScripts
). Aquí tenemos tres directivas Blade: @yield('content')
sola, @yield('title', 'Home Page')
con un predeterminado definido, y @section/@show
con contenido real dentro.
Aunque cada uno tiene un aspecto un poco diferente, los tres funcionan esencialmente igual. Los tres definen que hay una sección con un nombre determinado (el primer parámetro) que puede ampliarse posteriormente, y los tres definen qué hacer si la sección no se amplía. Lo hacen proporcionando una cadena de retorno ('Home Page'
), sin retorno (que simplemente no mostrará nada si no se amplía), o un bloque entero de retorno (en este caso, <script src="app.js"></script>
).
¿Qué es diferente? Bueno, está claro que @yield('content')
no tiene contenido por defecto. Pero además, el contenido por defecto en @yield('title')
sólo se mostrará si nunca se amplía. Si se amplía, sus secciones hijas no tendrán acceso programático al valor por defecto. @section/@show
Por otro lado, @parent
d e fine un valor por defecto y lo hace de tal forma que su contenido por defecto estará disponible para sus hijos, a través de .
Una vez que tengas un diseño padre como éste, puedes ampliarlo en un nuevo archivo de plantilla como en el Ejemplo 4-9.
Ejemplo 4-9. Ampliar un diseño Blade
<!-- resources/views/dashboard.blade.php -->
@extends('layouts.master') @section('title', 'Dashboard') @section('content') Welcome to your application dashboard! @endsection @section('footerScripts') @parent<
script
src
=
"dashboard.js"
></
script
>
@endsection
@muestra Versus @final
Te habrás dado cuenta de que en el Ejemplo 4-8 se utiliza @section/@show
, pero en el Ejemplo 4-9 se utiliza @section/@endsection
. ¿Cuál es la diferencia?
Utiliza @show
cuando estés definiendo el lugar para una sección, en la plantilla padre. Utiliza @endsection
cuando estés definiendo el contenido de una plantilla en una plantilla hija.
Esta vista de hijo nos permite abarcar algunos conceptos nuevos en la herencia de las palas.
@extende
En Ejemplo 4-9, con @extends('layouts.master')
, definimos que esta vista no debe representarse por sí misma, sino que amplía otra vista. Eso significa que su función es definir el contenido de varias secciones, pero no funcionar por sí sola. Se parece más a una serie de cubos de contenido que a una página HTML. Esta línea también define que la vista que amplía se encuentra en resources/views/layouts/master.blade.php.
Cada archivo sólo debe ampliar otro archivo, y la llamada a @extends
debe ser la primera línea del archivo.
@sección y @finalización
Con @section('title', 'Dashboard')
, proporcionamos nuestro contenido para la primera sección, title
. Como el contenido es tan corto, en lugar de utilizar @section
y @endsection
, utilizamos un atajo. Esto nos permite pasar el contenido comosegundo parámetro de @section
y seguir adelante. Si te resulta un poco desconcertante ver @section
sin @endsection
, puedes utilizar simplemente la sintaxis normal.
Con @section('content')
y siguientes, utilizamos la sintaxis normal para definir el contenido de la sección content
. De momento, sólo lanzaremos un pequeño saludo. Ten en cuenta, sin embargo, que cuando utilices @section
en una vista hija, debes terminar con @endsection
(o su alias @stop
), en lugar de @show
, que está reservado para definir secciones en vistas padre.
@padres
Por último, con @section('footerScripts')
y encendido, utilizamos la sintaxis normal para definir el contenido de la sección footerScripts
.
Pero recuerda que, en realidad, ya definimos ese contenido (o, al menos, su "predeterminado") en el diseño maestro. Así que esta vez tenemos dos opciones: sobrescribir el contenido de la vista principal o añadirlo.
Puedes ver que tenemos la opción de incluir el contenido del padre utilizando la directiva @parent
dentro de la sección. Si no lo hiciéramos, el contenido de esta sección sobrescribiría por completo todo lo definido en el padre para esta sección.
Incluyendo Ver parciales
Ahora que hemos establecido los fundamentos de la herencia, hay algunos trucos más que podemos realizar.
@incluir
¿Qué si estamos en una vista y queremos tirar de otra vista? Tal vez tengamos un botón de llamada a la acción "Regístrate" que queramos reutilizar en todo el sitio. Y tal vez queramos personalizar el texto del botón cada vez que lo utilicemos. Echa un vistazo al Ejemplo 4-10.
Ejemplo 4-10. Incluir vistas parciales con @include
<!-- resources/views/home.blade.php -->
<
div
class
=
"content"
data-page-name
=
"{{ $pageName }}"
>
<
p
>
Here's why you should sign up for our app:<
strong
>
It's Great.</
strong
></
p
>
@include('sign-up-button', ['text' => 'See just how great it is'])</
div
>
<!-- resources/views/sign-up-button.blade.php -->
<
a
class
=
"button button--callout"
data-page-name
=
"{{ $pageName }}"
>
<
i
class
=
"exclamation-icon"
></
i
>
{{ $text }}</
a
>
@include
extrae el parcial y, opcionalmente, le pasa datos. Ten en cuenta que no sólo puedes pasar explícitamente datos a un include mediante el segundo parámetro de @include
, sino que también puedes hacer referencia a cualquier variable del archivo incluido que esté disponible para la vista del include ($pageName
, en este ejemplo). Una vez más, puedes hacer lo que quieras, pero te recomendaría que consideraras siempre pasar explícitamente cada variable que pretendas utilizar, sólo por claridad.
También puedes utilizar las directivas @includeIf
, @includeWhen
y @includeFirst
, como se muestra en el Ejemplo 4-11.
Ejemplo 4-11. Incluir vistas condicionalmente
{{-- Include a view if it exists --}} @includeIf('sidebars.admin', ['some' => 'data']) {{-- Include a view if a passed variable is truth-y --}} @includeWhen($user->isAdmin(), 'sidebars.admin', ['some' => 'data']) {{-- Include the first view that exists from a given array of views --}} @includeFirst(['customs.header', 'header'], ['some' => 'data'])
@cada
En probablemente puedas imaginar algunas circunstancias en las que necesitarías hacer un bucle sobre una matriz o colección y @include
un parcial para cada elemento. Existe una directiva para ello: @each
.
Supongamos que tenemos una barra lateral compuesta por módulos, y queremos incluir varios módulos, cada uno con un título diferente. Echa un vistazo al Ejemplo 4-12.
Ejemplo 4-12. Utilizar parciales de vista en un bucle con @each
<!-- resources/views/sidebar.blade.php -->
<
div
class
=
"sidebar"
>
@each('partials.module', $modules, 'module', 'partials.empty-module')</
div
>
<!-- resources/views/partials/module.blade.php -->
<
div
class
=
"sidebar-module"
>
<
h1
>
{{ $module->title }}</
h1
>
</
div
>
<!-- resources/views/partials/empty-module.blade.php -->
<
div
class
=
"sidebar-module"
>
No modules :(</
div
>
Considera la sintaxis de @each
. El primer parámetro es el nombre de la vista parcial. El segundo es la matriz o colección sobre la que se va a iterar. El tercero es el nombre de la variable que cada elemento (en este caso, cada elemento de la matriz $modules
) se pasará a la vista. Y el cuarto parámetro opcional es la vista que se mostrará si la matriz o colección está vacía (u, opcionalmente, puedes pasar aquí una cadena que se utilizará comoplantilla).
Utilizar componentes
Laravel ofrece otro patrón para incluir contenido entre vistas: los componentes. Los componentes tienen más sentido en contextos en los que te encuentras utilizando vistas parciales y pasando grandes trozos de contenido a ellas como variables. Echa un vistazo al Ejemplo 4-13 para ver un ejemplo de modal, o ventana emergente, que podría alertar al usuario en respuesta a un error u otra acción.
Ejemplo 4-13. Un modal como vista parcial incómoda
<!-- resources/views/partials/modal.blade.php -->
<
div
class
=
"modal"
>
<
h2
>
{{ $title }}</
h2
>
<
div
>
{!! $content !!}</
div
>
<
div
class
=
"close button etc"
>
...</
div
>
</
div
>
<!-- in another template -->
@include('partials.modal', [ 'title' => 'Insecure password', 'content' => '<
p
>
The password you have provided is not valid. Here are the rules for valid passwords: [...]</
p
><
p
><
a
href
=
"#"
>
...</
a
></
p
>
' ])
Esto es demasiado para estas pobres variables, y es el ajuste perfecto para un componente.
Los componentes de Laravel son otra forma de estructurar las vistas parciales que se parece mucho más a cómo funcionan los componentes en frameworks frontales como Vue. Puede que resulten más familiares a los desarrolladores frontales, pero también tienen algunas ventajas significativas en comparación con las vistas parciales, como que es mucho más fácil pasarles grandes secciones de código de plantilla.
Echa un vistazo al Ejemplo 4-14 para ver cómo refactorizar el Ejemplo 4-13 con componentes.
Ejemplo 4-14. Un modal como componente más apropiado
<!-- resources/views/components/modal.blade.php -->
<
div
class
=
"modal"
>
<
h2
>
{{ $title }}</
h2
>
<
div
>
{{ $slot }}</
div
>
<
div
class
=
"close button etc"
>
...</
div
>
</
div
>
<!-- in another template -->
<
x-modal
title
=
"Insecure password"
>
<
p
>
The password you have provided is not valid. Here are the rules for valid passwords: [...]</
p
>
<
p
><
a
href
=
"#"
>
...</
a
></
p
>
</
x-modal
>
Como puedes ver en el Ejemplo 4-14, los componentes nos permiten sacar nuestro HTML de una apretada cadena variable y devolverlo al espacio de la plantilla.
Vamos a profundizar más en las características de los componentes, cómo se estructuran y cómo los escribimos.
Crear componentes
Los componentes pueden existir como plantillas Blade puras(componentes anónimos), o como plantillas Blade respaldadas por una clase PHP que inyecta datos y funcionalidad(componentes basados en clases).
Si sólo necesitas una plantilla, puedes generar tu componente con la bandera --view
:
phpartisan
make:component
modal
--view
Si también quieres generar la clase PHP, excluye esa bandera:
phpartisan
make:component
modal
Si quieres agrupar tus componentes en carpetas, puedes utilizar el separador .
:
# To create it:
php
artisan
make
:
component
modals
.
cancellation
// To use it:
<
x
-
modals
.
cancellation
/>
Pasar datos a los componentes
En hay cuatro formas de pasar datos a los componentes: atributos de cadena, atributos PHP, la ranura por defecto y las ranuras con nombre.
Pasar datos a los componentes mediante atributos
Empecemos en con los atributos. Puedes pasar cadenas directamente a los componentes pasando atributos sin prefijo, o puedes pasar variables y expresiones PHP con un prefijo de dos puntos, como puedes ver en el Ejemplo 4-15.
Ejemplo 4-15. Pasar datos a componentes mediante atributos
<!-- Passing the data in -->
<
x-modal
title
=
"Title here yay"
:width
=
"$width"
/>
<!-- Accessing the data in the template -->
<
div
style
=
"width: {{ $width }}"
>
<
h1
>
{{ $title }}</
h1
>
</
div
>
Para los componentes basados en clases, tendrás que definir cada atributo en la clase PHP y establecerlo como propiedad pública de la clase, como en el Ejemplo 4-16.
Ejemplo 4-16. Definir atributos como públicos en clases componentes
class
Modal
extends
Component
{
public
function
__construct
(
public
string
$title
,
public
string
$width
,
)
{}
}
Para los componentes anónimos, tendrás que definir los atributos en una matriz props
en la parte superior de tu plantilla:
@
props
([
'width'
,
'title'
,
])
<
div
style
=
"width: {{
$width
}}"
>
<
h1
>
{{
$title
}}
</
h1
>
</
div
>
Pasar datos a los componentes mediante ranuras
En el Ejemplo 4-14 habrás observado en que se hacía referencia al contenido del modal como una variable, $slot
. ¿Pero de dónde viene esto?
Por defecto, cada componente que tiene una etiqueta de apertura y otra de cierre cuando se hace referencia a él tiene una variable $slot
, y se rellena con todo el HTML que hay entre esas dos etiquetas. En el Ejemplo 4-14, la variable $slot
contiene las dos etiquetas <p>
y todo lo que hay dentro (y entre) ellas.
Pero, ¿y si necesitas dos o más ranuras? Puedes añadir más que la ranura por defecto, dando a cada ranura su propio nombre y variable. Reformulemos el Ejemplo 4-14 suponiendo que queremos definir el título en una ranura; echa un vistazo al Ejemplo 4-17.
Ejemplo 4-17. Definir varias ranuras
<
x-modal
>
<
x-slot:title
>
<
h2
class
=
"uppercase"
>
Password requirements not met</
h2
>
</
x-slot
>
<
p
>
The password you have provided is not valid. Here are the rules for valid passwords: [...]</
p
>
<
p
><
a
href
=
"#"
>
...</
a
></
p
>
</
x-modal
>
El contenido de esta nueva variable $slot
será accesible para la plantilla de componentes como una variable $title
, igual que antes lo era el atributo.
Métodos de los componentes
A veces puede ser útil tener un método auxiliar en un componente que realice alguna lógica. Un patrón común es utilizar estos métodos para comprobaciones lógicas complejas que preferirías mantener fuera de tus plantillas.
Los componentes te permiten llamar a cualquier método público de su clase PHP asociada en la plantilla anteponiendo al nombre del método el prefijo $
, como puedes ver en el Ejemplo 4-18.
Ejemplo 4-18. Definir y llamar a métodos de componentes
// in the component definition
public
function
isPromoted
(
$item
)
{
return
$item
->
promoted_at
!==
null
&&
!
$item
->
promoted_at
->
isPast
();
}
<!-- in the template -->
<
div
>
@if ($isPromoted($item))<!-- show promoted badge -->
@endif<!-- ... -->
</
div
>
Bolsa de atributos
La mayoría de los atributos que pasaremos a nuestros componentes tendrán nombre, serán específicos y similares a pasar parámetros a una función PHP.
Pero a veces sólo hay atributos HTML sueltos que queremos pasar, casi siempre para que se asignen al elemento raíz de nuestra plantilla.
Con los componentes, puedes coger todos esos atributos a la vez, utilizando la variable $attributes
. Esta variable captura todos los atributos no definidos como propiedades y te permite hacer eco de ellos (tratándolos como una cadena) o interactuar con algunos de sus métodos para agarrar o inspeccionar datos.
Echa un vistazo a la documentación para conocer todas las formas en que puedes interactuar con el objeto $attributes
, pero aquí tienes un truco muy útil:
<!-- Merge default classes with passed-in classes -->
<!-- Definition -->
<
div
{{
$
attributes-
>
merge(['class' => 'p-4 m-4']) }}> {{ $message }}</
div
>
<!-- Usage -->
<
x-notice
class
=
"text-blue-200"
>
Message here</
x-notice
>
<!-- Outputs: -->
<
div
class
=
"p-4 m-4 text-blue-200"
>
Message here</
div
>
Utilizar pilas
Un patrón común de que puede ser difícil de gestionar utilizando Blade includes básicos es cuando cada vista de una jerarquía de Blade includes necesita añadir algo a una sección determinada, casi como añadir una entrada a una matriz.
La situación más común para esto es cuando ciertas páginas (y a veces, más ampliamente, ciertas secciones de un sitio web) tienen archivos CSS y JavaScript específicos y únicos que necesitan cargar. Imagina que tienes un archivo CSS "global" para todo el sitio, un archivo CSS para la "sección de empleo" y un archivo CSS para la página "solicitar empleo".
Las pilas de Blade están creadas exactamente para esta situación. En tu plantilla padre, define una pila, que es sólo un marcador de posición. Luego, en cada plantilla hija puedes "empujar" entradas a esa pila con @push/@endpush
, que las añade a la parte inferior de la pila en el renderizado final. También puedes utilizar @prepend/@endprepend
para añadirlas a la parte superior de la pila. El ejemplo 4-19 lo ilustra.
Ejemplo 4-19. Utilizar pilas de cuchillas
<!-- resources/views/layouts/app.blade.php -->
<
html
>
<
head
>
<
link
href
=
"/css/global.css"
>
<!-- the placeholder where stack content will be placed -->
@stack('styles')</
head
>
<
body
>
<!-- // -->
</
body
>
</
html
>
<!-- resources/views/jobs.blade.php -->
@extends('layouts.app') @push('styles')<!-- push something to the bottom of the stack -->
<
link
href
=
"/css/jobs.css"
>
@endpush<!-- resources/views/jobs/apply.blade.php -->
@extends('jobs') @prepend('styles')<!-- push something to the top of the stack -->
<
link
href
=
"/css/jobs--apply.css"
>
@endprepend
Esto genera el siguiente resultado:
<
html
>
<
head
>
<
link
href
=
"/css/global.css"
>
<!-- the placeholder where stack content will be placed -->
<!-- push something to the top of the stack -->
<
link
href
=
"/css/jobs--apply.css"
>
<!-- push something to the bottom of the stack -->
<
link
href
=
"/css/jobs.css"
>
</
head
>
<
body
>
<!-- // -->
</
body
>
</
html
>
Ver Compositores e Inyección de Servicio
Al igual que que cubrimos en el Capítulo 3, es sencillo pasar datos a nuestras vistas desde la definición de la ruta (véase el Ejemplo 4-20).
Ejemplo 4-20. Recordatorio de cómo pasar datos a las vistas
Route
::
get
(
'passing-data-to-views'
,
function
()
{
return
view
(
'dashboard'
)
->
with
(
'key'
,
'value'
);
});
Sin embargo, puede haber ocasiones en las que te encuentres pasando los mismos datos una y otra vez a múltiples vistas. O puede que utilices un encabezado parcial o algo similar que requiera algunos datos; ¿tendrás que pasar esos datos desde cada definición de ruta que cargue ese encabezado parcial?
Vinculación de datos a vistas mediante compositores de vistas
Afortunadamente, existe una forma más sencilla. La solución se llama compositor de vistas, y te permite definir que cada vez que se cargue una vista determinada, se le pasen ciertos datos, sinque la definición de la ruta tenga que pasar esos datos explícitamente.
Supongamos que tienes una barra lateral en cada página, que se define en un parcial llamado partials.sidebar
(resources/views/partials/sidebar.blade.php) y luego se incluye en cada página. Esta barra lateral muestra una lista de las últimas siete entradas publicadas en tu sitio. Si está en todas las páginas, normalmente cada definición de ruta tendría que coger esa lista y pasarla, como en el Ejemplo 4-21.
Ejemplo 4-21. Pasar datos de la barra lateral desde cada ruta
Route
::
get
(
'home'
,
function
()
{
return
view
(
'home'
)
->
with
(
'posts'
,
Post
::
recent
());
});
Route
::
get
(
'about'
,
function
()
{
return
view
(
'about'
)
->
with
(
'posts'
,
Post
::
recent
());
});
Eso podría volverse molesto rápidamente. En lugar de eso, vamos a utilizar compositores de vistas para "compartir" esa variable con un conjunto determinado de vistas. Podemos hacerlo de varias formas, así que empecemos por lo más sencillo y vayamos avanzando.
Compartir una variable globalmente
En primer lugar, la opción más sencilla: simplemente "comparte" globalmente una variable con todas las vistas de tu aplicación, como en el Ejemplo 4-22.
Ejemplo 4-22. Compartir una variable globalmente
// Some service provider
public
function
boot
()
{
...
view
()
->
share
(
'recentPosts'
,
Post
::
recent
());
}
Si quieres utilizar view()->share()
, el mejor lugar sería el método boot()
de un proveedor de servicios para que la vinculación se ejecute en cada carga de página. Puedes crear un ViewComposerServiceProvider
personalizado (consulta "Proveedores de servicios" para más detalles), pero por ahora basta con que lo pongas en App\Providers\AppServiceProvider
en el método boot()
.
Sin embargo, utilizar view()->share()
hace que la variable sea accesible a todas las vistas de toda la aplicación, por lo que puede resultar excesivo.
Compositores de vistas con cierres
La siguiente opción es utilizar un compositor de vistas basado en cierres para compartir variables con una única vista, como en el Ejemplo 4-23.
Ejemplo 4-23. Crear un compositor de vistas basado en cierres
view
()
->
composer
(
'partials.sidebar'
,
function
(
$view
)
{
$view
->
with
(
'recentPosts'
,
Post
::
recent
());
});
Como puedes ver, hemos definido el nombre de la vista con la que queremos que se comparta en el primer parámetro (partials.sidebar
) y luego hemos pasado un cierre al segundo parámetro; en el cierre hemos utilizado $view->with()
para compartir una variable, pero sólo con una vista concreta.
Compositores de vistas con clases
Por último, la opción más flexible, pero también la más compleja, es crear una clase dedicada para tu compositor de vistas.
En primer lugar, vamos a crear la clase compositor de vistas. No hay un lugar formalmente definido para los compositores de vistas, pero los documentos recomiendan App\Http\ViewComposers
. Así que vamos a crear App\Http\ViewComposers\RecentPostsComposer
como en el Ejemplo 4-24.
Ejemplo 4-24. Un compositor de vistas
<?
php
namespace
App\Http\ViewComposers
;
use
App\Post
;
use
Illuminate\Contracts\View\View
;
class
RecentPostsComposer
{
public
function
compose
(
View
$view
)
{
$view
->
with
(
'recentPosts'
,
Post
::
recent
());
}
}
Como puedes ver, cuando se llama a este compositor, se ejecuta el método compose()
, en el que vinculamos la variable recentPosts
al resultado de ejecutar el método recent()
del modelo Post
.
Al igual que los otros métodos para compartir variables, este compositor de vistas necesita tener un enlace en alguna parte. De nuevo, probablemente crearías un ViewComposerServiceProvider
personalizado, pero por ahora, como se ve en el Ejemplo 4-25, lo pondremos en el método boot()
de App\Providers\AppServiceProvider
.
Ejemplo 4-25. Registrar un compositor de vistas en AppServiceProvider
public
function
boot
()
:
void
{
view
()
->
composer
(
'partials.sidebar'
,
\App\Http\ViewComposers\RecentPostsComposer
::
class
);
}
Observa que esta vinculación es la misma que la de un compositor de vistas basado en cierres, pero en lugar de pasar un cierre, pasamos el nombre de la clase de nuestro compositor de vistas. Ahora, cada vez que Blade muestre la vista partials.sidebar
, ejecutará automáticamente nuestro proveedor y pasará a la vista una variable recentPosts
configurada con los resultados del método recent()
de nuestro modelo Post
.
Inyección de servicio de cuchillas
Hay tres tipos principales de datos que es más probable que inyectemos en una vista: colecciones de datos sobre las que iterar, objetos individuales que mostramos en la página y servicios que generan datos o vistas.
Con un servicio, lo más probable es que el patrón sea como en el Ejemplo 4-26, en el que inyectamos una instancia de nuestro servicio de análisis en la definición de la ruta indicándolo en la firma del método de la ruta, y luego lo pasamos a la vista.
Ejemplo 4-26. Inyectar servicios en una vista mediante el constructor de definición de ruta
Route
::
get
(
'backend/sales'
,
function
(
AnalyticsService
$analytics
)
{
return
view
(
'backend.sales-graphs'
)
->
with
(
'analytics'
,
$analytics
);
});
Al igual que con los compositores de vistas, la inyección de servicios de Blade ofrece un cómodo atajo para reducir la duplicación en tus definiciones de rutas. Normalmente, el contenido de una vista que utiliza nuestro servicio de análisis podría parecerse al del Ejemplo 4-27.
Ejemplo 4-27. Utilizar un servicio de navegación inyectado en una vista
<
div
class
=
"finances-display"
>
{{
$analytics
->
getBalance
()
}}
/
{{
$analytics
->
getBudget
()
}}
</
div
>
La inyección de servicios blade facilita la inyección de una instancia de una clase del contenedor directamente en la vista, como en el Ejemplo 4-28.
Ejemplo 4-28. Inyectar un servicio directamente en una vista
@
inject
(
'analytics'
,
'App\Services\Analytics'
)
<
div
class
=
"finances-display"
>
{{
$analytics
->
getBalance
()
}}
/
{{
$analytics
->
getBudget
()
}}
</
div
>
Como puedes ver en, esta directiva @inject
ha puesto a disposición una variable $analytics
, que utilizaremos más adelante en nuestra vista.
El primer parámetro de @inject
es el nombre de la variable que estás inyectando, y el segundo parámetro es la clase o interfaz de la que quieres inyectar una instancia. Esto se resuelve de la misma forma que cuando escribes una dependencia en un constructor en cualquier parte de Laravel; si no estás familiarizado con cómo funciona, consulta el Capítulo 11 para obtener más información.
Al igual que los compositores de vistas, la inyección de servicios Blade facilita que determinados datos o funcionalidades estén disponibles en todas las instancias de una vista, sin tener que inyectarlos cada vez a través de la definición de ruta.
Directivas de hoja personalizadas
Todas las de la sintaxis incorporada de Blade que hemos cubierto hasta ahora -@if
, @unless
, etc.- sedenominan directivas. Cada directiva de Blade es una correspondencia entre un patrón (por ejemplo, @if ($condition)
) y una salida PHP (p.ej, <?php if ($condition): ?>
).
Las directivas no son sólo para el núcleo; puedes crear las tuyas propias. Puedes pensar que las directivas son buenas para hacer pequeños atajos a trozos más grandes de código; por ejemplo, utilizar @button('buttonName')
y hacer que se expanda a un conjunto más grande de HTML de botones. No es una idea terrible, pero para una simple expansión de código como ésta, sería mejor que incluyeras una vista parcial.
Las directivas personalizadas suelen ser más útiles cuando simplifican algún tipo de lógica repetida. Digamos que estamos cansados de tener que envolver nuestro código con @if (auth()
->
guest())
(para comprobar si un usuario ha iniciado sesión o no) y queremos una directiva personalizada @ifGuest
. Al igual que con los compositores de vistas, podría valer la pena tener un proveedor de servicios personalizado para registrarlas, pero por ahora vamos a ponerlo en el método boot()
de App\Providers\AppServiceProvider
. Echa un vistazo al Ejemplo 4-29 para ver cómo será esta vinculación.
Ejemplo 4-29. Vincular una directiva Blade personalizada en un proveedor de servicios
public
function
boot
()
:
void
{
Blade
::
directive
(
'ifGuest'
,
function
()
{
return
"<?php if (auth()->guest()): ?>"
;
});
}
Ahora hemos registrado una directiva personalizada, @ifGuest
, que será sustituida por el código PHP <?php if (auth()->guest()): ?>
.
Esto puede parecer extraño. Estás escribiendo una cadena que será devuelta y luego ejecutada como PHP. Pero lo que esto significa es que ahora puedes tomar los aspectos complejos, feos, poco claros o repetitivos de tu código de plantillas PHP y ocultarlos tras una sintaxis clara, sencilla y expresiva.
Directiva personalizada Almacenamiento en caché de resultados
Podrías tener la tentación de hacer algo de lógica para que tu directiva personalizada sea más rápida realizando una operación en el enlace y luego incrustando el resultado dentro de la cadena devuelta:
Blade
::
directive
(
'ifGuest'
,
function
()
{
// Antipattern! Do not copy.
$ifGuest
=
auth
()
->
guest
();
return
"<?php if (
{
$ifGuest
}
): ?>"
;
});
El problema de esta idea es que asume que esta directiva se volverá a crear en cada carga de página. Sin embargo, Blade almacena en caché de forma agresiva, por lo que te encontrarás en una mala situación si intentas esto.
Parámetros en las directivas de hoja personalizadas
¿Y si quieres aceptar parámetros en tu lógica personalizada? Echa un vistazo al Ejemplo 4-30.
Ejemplo 4-30. Crear una directiva Blade con parámetros
// Binding
Blade
::
directive
(
'newlinesToBr'
,
function
(
$expression
)
{
return
"<?php echo nl2br(
{
$expression
}
); ?>"
;
});
// In use
<
p
>@
newlinesToBr
(
$message
->
body
)
</
p
>
El parámetro $expression
que recibe el cierre representa lo que hay dentro de los paréntesis. Como puedes ver, a continuación generamos un fragmento de código PHP válido y lo devolvemos.
Si te encuentras escribiendo constantemente la misma lógica condicional una y otra vez, deberías considerar una directiva Blade.
Ejemplo: Uso de directivas Blade personalizadas para una aplicación multiusuario
Imaginemos que estamos construyendo una aplicación que admite la multitenencia, lo que significa que los usuarios pueden estar visitando el sitio desde www.myapp.com, cliente1.miapp.com,cliente2.miapp.com, o desde cualquier otro lugar.
Supongamos que hemos escrito una clase para encapsular parte de nuestra lógica de multitenencia y la hemos llamado Context
. Esta clase capturará información y lógica sobre el contexto de la visita actual, como quién es el usuario autenticado y si está visitando el sitio web público o un subdominio cliente.
Probablemente resolveremos con frecuencia esa clase Context
en nuestras vistas y realizaremos condicionales sobre ella, como en el Ejemplo 4-31. app('context')
es un atajo para obtener una instancia de una clase del contenedor, sobre el que aprenderemos más en el Capítulo 11.
Ejemplo 4-31. Condicionales sobre contexto sin directiva Blade personalizada
@
if
(
app
(
'context'
)
->
isPublic
())
&
copy
;
Copyright
MyApp
LLC
@
else
&
copy
;
Copyright
{{
app
(
'context'
)
->
client
->
name
}}
@
endif
¿Y si pudiéramos simplificar @if (app('context')->isPublic())
a sólo @ifPublic
? Hagámoslo. Mira el Ejemplo 4-32.
Ejemplo 4-32. Condicionales sobre contexto con una directiva Blade personalizada
// Binding
Blade
::
directive
(
'ifPublic'
,
function
()
{
return
"<?php if (app('context')->isPublic()): ?>"
;
});
// In use
@
ifPublic
&
copy
;
Copyright
MyApp
LLC
@
else
&
copy
;
Copyright
{{
app
(
'context'
)
->
client
->
name
}}
@
endif
Como esto se resuelve con una simple sentencia if
, podemos seguir confiando en las condicionales nativas @else
y @endif
. Pero si quisiéramos, también podríamos crear una directiva @elseIfClient
personalizada, o una directiva @ifClient
independiente, o realmente lo que quisiéramos.
Directivas personalizadas más sencillas para las sentencias "if
Aunque las directivas "Blade" personalizadas de son potentes, su uso más habitual son las declaraciones if
. Así que hay una forma más sencilla de crear directivas "if" personalizadas: Blade::if()
. El Ejemplo 4-33 muestra cómo podríamos refactorizar el Ejemplo 4-32 utilizando el método Blade::if()
:
Ejemplo 4-33. Definir una directiva Blade "if" personalizada
// Binding
Blade
::
if
(
'ifPublic'
,
function
()
{
return
(
app
(
'context'
))
->
isPublic
();
});
Utilizarás las directivas exactamente igual, pero como puedes ver, definirlas es un poco más sencillo. En lugar de tener que escribir manualmente las llaves PHP, puedes escribir simplemente un cierre que devuelva un booleano.
Prueba
El método más común de probar las vistas es mediante pruebas de aplicación, lo que significa que estás llamando realmente a la ruta que muestra las vistas y asegurándote de que las vistas tienen cierto contenido (ver Ejemplo 4-34). También puedes hacer clic en botones o enviar formularios y asegurarte de que se te redirige a una determinada página o de que ves un determinado error. (Aprenderás más sobre pruebas en el Capítulo 12).
Ejemplo 4-34. Comprobar que una vista muestra un contenido determinado
// EventsTest.php
public
function
test_list_page_shows_all_events
()
{
$event1
=
Event
::
factory
()
->
create
();
$event2
=
Event
::
factory
()
->
create
();
$this
->
get
(
'events'
)
->
assertSee
(
$event1
->
title
)
->
assertSee
(
$event2
->
title
);
}
También puedes comprobar que a una determinada vista se le ha pasado un conjunto concreto de datos, lo cual, si cumple tus objetivos de comprobación, es menos frágil que comprobar cierto texto en la página. El Ejemplo 4-35 muestra este enfoque.
Ejemplo 4-35. Comprobar que a una vista se le ha pasado cierto contenido
// EventsTest.php
public
function
test_list_page_shows_all_events
()
{
$event1
=
Event
::
factory
()
->
create
();
$event2
=
Event
::
factory
()
->
create
();
$response
=
$this
->
get
(
'events'
);
$response
->
assertViewHas
(
'events'
,
Event
::
all
());
$response
->
assertViewHasAll
([
'events'
=>
Event
::
all
(),
'title'
=>
'Events Page'
,
]);
$response
->
assertViewMissing
(
'dogs'
);
}
Con assertViewHas()
podemos pasar un cierre, lo que significa que podemos personalizar cómo queremos comprobar estructuras de datos más complejas. El ejemplo 4-36 ilustra cómo podríamos utilizar esto.
Ejemplo 4-36. Pasar un cierre a assertViewHas()
// EventsTest.php
public
function
test_list_page_shows_all_events
()
{
$event1
=
Event
::
factory
()
->
create
();
$response
=
$this
->
get
(
"events/{
$event1->id
}"
);
$response
->
assertViewHas
(
'event'
,
function
(
$event
)
use
(
$event1
)
{
return
$event
->
id
===
$event1
->
id
;
});
}
TL;DR
Blade es el motor de plantillas de Laravel. Su principal objetivo es una sintaxis clara, concisa y expresiva con una potente herencia y extensibilidad. Sus corchetes de "eco seguro" son {{
y }}
, sus corchetes de eco desprotegido son {!!
y !!}
, y tiene una serie de etiquetas personalizadas llamadas "directivas" que empiezan todas por @
(@if
y @unless
, por ejemplo).
Puedes definir una plantilla padre y dejar "huecos" en ella para el contenido utilizando @yield
y @section
/ @show
. Luego puedes enseñar a sus vistas hijas a ampliar la padre utilizando @extends('parent.view')
y definir sus secciones mediante @section
/@endsection
. Utiliza @parent
para hacer referencia al contenido del bloque padre.
Los compositores de vistas facilitan la definición de que, cada vez que se cargue una vista o subvista determinada, debe disponer de cierta información. Y la inyección de servicios permite que la propia vista solicite datos directamente al contenedor de la aplicación.
Get Laravel: Up & Running, 3ª Edición now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.