Panel de control

Índice

¿Qué aprenderemos?

  1. A crear un panel de control para nuestras entidades
  2. A usar controladores embebidos
  3. A usar servicios

2.1 Panel de control

Vamos a crear un panel de control para nuestra aplicación.

Para ello usaremos el bundle EasyAdmin de Symfony.

1
composer require easycorp/easyadmin-bundle

Una vez instalado vamos a crear nuestro Dashboard

1
php bin/console make:admin:dashboard

Que creará una nueva ruta admin y un nuevo controlador DashboardController

Si visitamos la ruta http://127.0.0.1:8080/admin verás la página de inicio

image-20221027181654194

Si aparace un error avisando de que no encuentra el controlador, limpia la caché mediante el comando php bin/console cache:clear

2.2 Team

Primero configuramos la conexión en el archivo .env

1
DATABASE_URL=mysql://root:sa@127.0.0.1:3306/tienda

Y creamos la base de datos:

1
php bin/console doctrine:database:create

Vamos a empezar con los miembros del equipo

image-20221027182231943

El primer paso es crear la entidad Team.

Y generar la migración:

1
2
php bin/console make:migration
php bin/console doctrine:migrations:migrate

Y luego generar el controlador CRUD

CRUD son las iniciales de Create, Read, Update y Delete que son las operaciones básicas que se realizan con una entidad

Y por último, modificar DashboardController para que cargue por defecto el panel de control de la entidad Team

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Otros use
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;

#[Route('/admin', name: 'admin')]
public function index(): Response
{
$adminUrlGenerator = $this->container->get(AdminUrlGenerator::class);

return $this->redirect($adminUrlGenerator->setController(TeamCrudController::class)->generateUrl());
}

    public function configureMenuItems(): iterable
    {
        yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
        // Añadimos al menú el enlace al crud de Team
        yield MenuItem::linkToCrud('Team', 'fas fa-list', Team::class);        
    }

image-20221027184941326

Ahora ya podemos añadir miembros al equipo.

image-20221027185039315

Pero claro, EasyAdmin no sabe que el campo Photo debe ser de tipo File

Así que configuramos TeamCrudController para modificar los campos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace App\Controller\Admin;

use App\Entity\Team;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\Field;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;

class TeamCrudController extends AbstractCrudController
{
    public static function getEntityFqcn(): string
    {
        return Team::class;
    }

    public function configureFields(string $pageName): iterable
    {
        return [
            Field::new('name'),
            ImageField::new('photo')->setUploadDir('/public/img')->setBasePath('/img/'),
            Field::new('designation')
        ];
    }
}

Lo estamos configurando para que los campos name y designation sean de tipo Field (campo de texto) y el campo photo sea de tipo ImageField y además configuramos el directorio de subida y cuál es la ruta de la carpeta.

Ahora ya podemos subir una foto:

image-20221027192221148

Ahora vamos a subir todas las imágenes del equipo a la base de datos mediante el siguiente sql:

1
2
3
4
5
6
INSERT INTO `team` (`id`, `name`, `photo`, `designation`) VALUES
(1, 'Team 1', '/team-1.jpg', 'Designation'),
(2, 'Team 2', '/team-2.jpg', 'Designation'),
(3, 'Team 3', '/team-3.jpg', 'Designation'),
(4, 'Team 4', '/team-4.jpg', 'Designation'),
(5, 'Team 5', '/team-5.jpg', 'Designation');

image-20221027192959150

Ya sólo nos queda hacer el partial para mostrar a los miembros del equipo en la ruta index.

Pasamos todo el código html a una plantilla llamada partials/_team.html.twig y en la vista index.html.twig la incluimos.

Ya solo queda conectarla con la base de datos.

Primero modificamos el controlador de la ruta index

1
2
3
4
5
6
7
#[Route('/', name: 'index')]
public function index(ManagerRegistry $doctrine): Response
{
    $repository = $doctrine->getRepository(Team::class);
    $team = $repository->findAll();
    return $this->render('page/index.html.twig', compact('team'));
}

Creamos el partial para el miembro del equipo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="team-item">
    <div class="position-relative overflow-hidden">
        <img class="img-fluid w-100" src="{{ asset('img/' ~ teamMember.photo) }}" alt="">
        <div class="team-overlay">
            <div class="d-flex align-items-center justify-content-start">
                <a class="btn btn-light btn-square mx-1" href="#"><i class="bi bi-twitter"></i></a>
                <a class="btn btn-light btn-square mx-1" href="#"><i class="bi bi-facebook"></i></a>
                <a class="btn btn-light btn-square mx-1" href="#"><i class="bi bi-linkedin"></i></a>
            </div>
        </div>
    </div>
    <div class="bg-light text-center p-4">
        <h5 class="text-uppercase">{{ teamMember.name }}</h5>
        <p class="m-0">{{ teamMember.designation }}</p>
    </div>
</div>

Y ahora modificamos _team.html.twig para recorrer los miembros del equipo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    <!-- Team Start -->
<div class="container-fluid py-5">
    <div class="container">
        <div class="border-start border-5 border-primary ps-5 mb-5" style="max-width: 600px;">
            <h6 class="text-primary text-uppercase">Team Members</h6>
            <h1 class="display-5 text-uppercase mb-0">Qualified Pets Care Professionals</h1>
        </div>
        <div class="owl-carousel team-carousel position-relative" style="padding-right: 25px;">
         {% for teamMember in team %}
            {{include('partials/_teamMember.html.twig', {'teamMember': teamMember})}}
         {% endfor %}
        </div>
    </div>
</div>
<!-- Team End -->

Reto

Haz lo mismo en la página about

2.3 Product

Reto

Crea el panel de control para administrar los productos. La entidad Product debe tener los atributos name, photo y price, este último de tipo float

Una vez creada la entidad, carga el siguiente SQL con los productos:

1
2
3
4
5
6
INSERT INTO `product` (`id`, `name`, `photo`, `price`) VALUES
(1, 'Producto 1', 'product-1.png', 12.45),
(2, 'Producto 2', 'product-2.png', 100),
(3, 'Producto 3', 'product-3.png', 20),
(4, 'Producto 4', 'product-4.png', 50),
(5, 'Producto 5', 'product-3.png', 34);

Y ahora crea el partial para los productos que incluirás en la ruta product e index

image-20221101195632637

En este caso vemos hemos de cargar los productos en dos rutas (index y product). Y un pricipio básico de la programación es DRY (Don’t Repeat Yourself)

Para no repetir código, vamos a crear un servicio:

Your application is full of useful objects: a “Mailer” object might help you send emails while another object might help you save things to the database. Almost everything that your app “does” is actually done by one of these objects. And each time you install a new bundle, you get access to even more!

In Symfony, these useful objects are called services and each service lives inside a very special object called the service container. The container allows you to centralize the way objects are constructed. It makes your life easier, promotes a strong architecture and is super fast!

Un servicio es una forma de compartir código entre varias partes de la aplicación. Para usarlo sólo hay que hacer type-hinting y dejar que Symfony nos lo inyecte (Dependency Injection Principle). Vamos a verlo:

  1. Creamos el servicio en App\Service\ProductsService

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    <?php
    namespace App\Service;
       
    use App\Entity\Product;
    use Doctrine\Persistence\ManagerRegistry;
       
    class ProductsService{
        private $doctrine;
       
        public function __construct(ManagerRegistry $doctrine)
        {
            //Como hace falta acceder a ManagerRegistry lo inyectamos en el constructor
            $this->doctrine = $doctrine;
        }
        public function getProducts(): ?array{
            $repository = $this->doctrine->getRepository(Product::class);
            return $repository->findAll();
        }
    }
    
  2. Y ahora lo inyectamos en todos aquellos métodos en que queramos usarlo.

    1
    2
    3
    4
    5
    6
    
    #[Route('/product', name: 'product')]
    public function product(ProductsService $productsService): Response
    {
        $products = $productsService->getProducts();
        return $this->render('product/product.html.twig', compact('products'));
    }
    

Reto

Haz lo mismo para la ruta /