Una vez que ya tenemos creado el proyecto con Docker, ya podemos empezar a prepara la aplicación de gestión de pedidos ORDERS-APP, en ella crearemos los pedidos y mantendremos actualizado el saldo de los clientes en la apicación ADMIN-APP.

Nos encontramos ahora en el punto 5 de nuestro desarrollo donde programamos parte de backend y frontend de ORDER APP modelo, controlador y vista para la gestión de pedidos, generamos la parte web de visualización de clientes y de entrada de pedidos.

En esta aplicación utilizaremos el modelo Modelo Vista Controlador (MVC) para nuestro desarrollo en la parte de la vista lo haremos de forma sencilla utilizando el sistema de plantillas de Laravel y para la visualización Bootstrap para el sistema de plantillas CSS.

Índice

EL proyecto lo realizaremos en unas 10 publicaciones apróximadamente y otras publicaciones como anexos para ver el uso de Swagger, Testing,… , vamos a ver el apartado 5 en esta publicación.

  1. Explicación del proyecto: Creación de una aplicación de microservicios con Laravel y RabbitMQ
  2. Creación del proyecto backend ADMIN-APP con Docker Compose : Laravel, Nginx y MySQL
  3. Desarrollamos la API REST de ADMIN-APP para empresas y clientes
    1. CRUD controller y API controller para las empresas (cm_enterprise) (30/01/2022)
    2. CRUD controller y API controller para los clientes (cm_customer) (7/02/2022)
  4. Creación del proyecto order app con docker frontend pedidos (12/02/2022)
    1. Creación del proyecto Laravel
    2. Contenedor Docker con la definición del Dockerfile para la máquina principal con PHP
    3. Creación del fichero de configuración para el servidor web con Nginx
    4. Configuración con docker-compose de Laravel / Servidor Web / MySQL
  5. Creación de la aplicación ORDERS APP: MVC WEB de los pedidos
    1. ORDER APP – CLIENTES: CRUD de clientes modelo y controlador (cm_customer) (20/03/2022)
      1. Definimos la migración para la tabla de clientes
      2. Creamos el modelo para la tabla de clientes
      3. Detallamos el recurso para cliente
      4. Añadimos la ruta para la WEB
      5. Definimos el controlador para clientes en WEB
        1. Listado de clientes: index()
        2. Consulta de cliente: show($id)
    2. ORDER APP – PEDIDOS: CRUD de pedidos modelo y controlador (cm_order) (27/03/2022)
  6. RabbitMQ admin y order app , actualización de clientes
  7. RabbitMQ Automatización y colas diferenciadas
  8. RabbitMQ actualizamos el saldo de clientes en ADMIN-APP desde ORDER-APP
  9. EXTRA: Laravel Api Rest documentación de API con Swagger para ADMIN-APP 
  • Nota: durante las publicaciones este índice podrá sufrir alguna modificación para adaptar el contenido.

Software que utilizaremos

En este listado incluimos todo el software utilizado en todo el proyecto, no solo en esta publicación:

  • Linux (Ubuntu 20.04 LTS): sistema operativo Linux con la versión de Ubuntu 20.04 LTS
  • Docker (docker): Docker version 20.10.7, esta guía se ha generado utilizando esta versión de Docker.
  • Docker compose (docker-compose): docker-compose version 1.25.0, , esta guía se ha generado utilizando esta versión
  • Laravel 8: framewok PHP de desarrollo de aplicaciones web, en este caso utilizaremos la versión 8.
  • Mysql 5.7: versión utilizada en la imagen de docker-compose para la base de datos.
  • Nginx: servidor web de aplicaciones
  • PHP 7.4: utilizamos PHP en una versión superior a 7.4 siguiendo las recomendaciones de Laravel, por eso la imagen que utilicemos será de esta versión.
  • Composer 2: gestor de paquetes a partir de archivos json utilizado por Laravel, utilizamos Composer 2 porque lo necesitamos para Laravel 8, en esta publicación indicamos como actualizarlo.
  • Artisan: programa de línea de comandos de Laravel que nos ayuda en la creación de elementos como modelos, controladores, …, y también, nos proporciona otras utilidades como gestión de las migraciones o el listado de las rutas
  • RabbitMQ / CloudAMQP: en este proyecto utlizaremos CloudAMQP (provides managed RabbitMQ servers in the cloud), que nos proporciona un servidor para RabbitMQ en la nube, y así nos facilita el desarrollo en la nube. Otra opción sería instalar una máquina Docker con RabbitMQ pero eso complicaría el objetivo de este tutorial
  • Artisan CLI: Laravel incluye un interfaz de línea de comandos que se conoce como Artisan.

5. CREACIÓN DE LA APLICACIÓN ORDERS APP: MVC WEB DE PEDIDOS

Cómo en el desarrollo de ADMIN-APP utilizaremos artisan para crear la estructura de nuestro código e ir programando la aplicación de gestión de pedidos ORDERS-APP, en ella crearemos los pedidos y mantendremos actualizado el saldo de los clientes en la aplicación ADMIN-APP.

Desarrollamos la parte de backend y frontend de ORDER APP modelo y controlador de clientes y pedidos

Código Xules

Empezaremos creando el CRUD de los clientes, ya que permitiremos una actualización de de los datos de los clientes desde ADMIN-APP, así las actualizaciones que se realicen en ADMIN-APP de los clientes se mantendrán sincronizadas con los datos de los clientes en ORDERS-APP.

5.1. ORDER APP – CLIENTES: CRUD de clientes modelo y controlador (cm_customer)

Vamos a crear el controlador para nuestra API de clientes que se corresponderá a la tabla cm_customer, para ello vamos a generar las clases bases del controlador, modelo y migraciones con un único comando:

$ docker-compose exec orders-app php artisan make:model CmCustomer --controller --migration --resource

5.1.1. Definimos la migración para la tabla cm_customer de clientes

La tabla cm_customer la definimos en el diagrama entidad relación con pedidos cm_order, donde un cliente puede tener múltiples pedidos:

Learning Project Mini diagrama ER - ORDER-APP
Learning Project Mini diagrama ER – ORDER-APP

El SQL de la tabla cm_customer que vamos a ver a continuación nos sirve de definición de la tabla en la migración:

CREATE TABLE cm_customer
 (
   idcustomer INT NOT NULL AUTO_INCREMENT,
   identerprise INT,
   customer VARCHAR(150) NOT NULL, 
   contact VARCHAR(250),
   customerstate VARCHAR(30),
   sale DECIMAL(10,3), 
   paymentmethod VARCHAR(50),
   country VARCHAR(100),
   currency VARCHAR(100),
   elanguage VARCHAR(6),
   address VARCHAR(500),
   CONSTRAINT pk_cm_customer PRIMARY KEY (idcustomer),   
   CONSTRAINT un_cm_customer_cb_enterprise UNIQUE (identerprise, customer)
 )
 ENGINE=InnoDB
 COMMENT='Tabla donde se almacenarán los clientes de las diferentes empresas, se entiende cliente como aquel que compra a una empresa.';
 GRANT ALL ON TABLE cm_customer TO xulescode;

No entramos en detalles ya que ya los explicamos en 3.2. DESARROLLAMOS EL API REST DE ADMIN-APP PARA CLIENTES, lo único a tener en cuenta es que en ORDERS APP no incluimos la tabla cm_enterprise, y por eso, tanto en el SQL como en el desarrollo del modelo no incluimos la referencia a la tabla cm_enterprise como clave foránea.

Entonces la definición de la migración para ORDERS APP quedará de la siguiente manera (nota: tuvimos que prescindir del Sintax Highligther ya que nos da problemas cambiando caracteres por codificación HTML):

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCmCustomersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
      /**
       * Tabla donde se almacenarán los clientes de las diferentes empresas,
       * se entiende cliente como aquel que compra a una empresa.
       *  idcustomer INT NOT NULL AUTO_INCREMENT,
       *  identerprise INT,
       *  customer VARCHAR(150) NOT NULL,
       *  contact VARCHAR(250),
       *  customerstate VARCHAR(30),
       *  sale DECIMAL(10,3),
       *  paymentmethod VARCHAR(50),
       *  country VARCHAR(100),
       *  currency VARCHAR(100),
       *  elanguage VARCHAR(6),
       *  address VARCHAR(500),
       *  CONSTRAINT pk_cm_customer PRIMARY KEY (idcustomer),
       *  CONSTRAINT fk_cm_customer_identerprise FOREIGN KEY (identerprise)
       *      REFERENCES cb_enterprise (identerprise) MATCH SIMPLE
       *  ON UPDATE NO ACTION ON DELETE NO ACTION,
       *  CONSTRAINT un_cm_customer_cb_enterprise UNIQUE (identerprise, customer)
       */
      Schema::create('cm_customer', function (Blueprint $table) {
        $table->unsignedInteger('idcustomer')->primary();
        $table->unsignedInteger('identerprise');
        $table->string('customer', 150);
        $table->string('customerstate', 30);
        $table->string('contact', 250);
        $table->string('paymentmethod', 50);
        $table->string('currency', 100);
        $table->string('country', 100);
        $table->string('elanguage', 6);
        $table->string('address', 50);
        $table->engine = 'InnoDB';   // Specify the table storage engine (MySQL).

      });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
      Schema::dropIfExists('cm_customer');
    }
}

Tenemos que tener en cuenta es que en esta aplicación los clientes nos vienen dados de la aplicación ADMIN-APP para conservar la integridad entre aplicaciones vamos a utilizar los ids que se generen en esta aplicación por esto marcamos idcustomer como clave primaria (primary) pero sin ser autoincremental:

 $table->unsignedInteger('idcustomer')->primary();

Una vez completado el código ya podemos ejecutar la migración tal y como comentamos en la publicación anterior:

.../cx-lpm-customerdb-orders-app$ docker-compose exec orders-app php artisan migrate
 Migration table created successfully.
 Migrating: 2014_10_12_000000_create_users_table
 Migrated:  2014_10_12_000000_create_users_table (16.00ms)
 Migrating: 2014_10_12_100000_create_password_resets_table
 Migrated:  2014_10_12_100000_create_password_resets_table (16.62ms)
 Migrating: 2019_08_19_000000_create_failed_jobs_table
 Migrated:  2019_08_19_000000_create_failed_jobs_table (15.25ms)
 Migrating: 2019_12_14_000001_create_personal_access_tokens_table
 Migrated:  2019_12_14_000001_create_personal_access_tokens_table (23.45ms)
 Migrating: 2022_02_03_191224_create_cm_customers_table
 Migrated:  2022_02_03_191224_create_cm_customers_table (9.11ms)

Si queréis comprobar el resultado podéis acceder al contenedor orders-db, consultar la tabla y su descripción dentro de la base de datos ordersminidb.

5.1.2. Creamos el modelo para la tabla de clientes

Creamos el modelo para la tabla cm_customer (clientes), la definición la haremos en CmCustomer dentro de la carpeta de los modelos app/Models, los campos que definimos en la migración los llevamos a la tabla:

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class CmCustomer extends Model
{
  use HasFactory;
  /**
   * Nombre de la tabla de la base de datos.
   * Database table name.
   */
  protected $table = 'cm_customer';
  /**
   * Indicamos si los ids son auto incrementales.
   * Indicates if the IDs are auto-incrementing.
   * @var bool si true id autoincrementeal, si false no.
   */
  public $incrementing = false;
  /**
   * Por defecto Eloquent  asume que existe una clave primaria llamada id,
   * si este no es nuesto caso lo tenemos que indicar en la variable $primaryKey.
   * Eloquent asumes tha a primary key called id exists by default,
   * if it isn't our case we have to write the name in the var class $primaryKey.
   */
  protected $primaryKey = 'idcustomer';

  protected $guarded = [];

  /**
   * Indicamos si el modelo tiene campos de tiempo de creación y actualización.
   * Indicates if the model should be timestamped.
   * @var bool
   */
  public $timestamps = false;

  /**
   * Definimos los campos de la tabla directamente en la variable de tipo array $fillable.
   * We define the fields of the table in the var $fillable directly.
   */
  protected $fillable =  array( 'idcustomer', 'identerprise', 'customer',
                                'contact', 'customerstate',
                                'paymentmethod',
                                'country', 'currency', 'elanguage',
                                'address');
}

Aquí no vamos a entrar en detallles ya que la definición la explicamos anteriormente, lo que si tenemos que tener en cuenta es que en esta aplicación los clientes nos vienen dados de la aplicación ADMIN-APP para conservar la integridad entre aplicaciones vamos a utilizar los ids que se generén en esta aplicación como ya indicamos en la creación de la migración por eso marcamos la opción incrementing = false; indicando que la creación de la clave primaria no es auto incremental.

5.1.3. Detallamos el recurso para clientes (Resource)

Vamos a definir la respuesta de los servicios de nuestra API para clientes, así organizamos estructuralmente la devolución de registros, esto lo hacemos en la clase CmCustomerResource dentro de app/HTTP/Resources

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class CmCustomerResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
      return parent::toArray($request);
    }
}

5.1.4. Añadimos las rutas para la WEB

Para definir las rutas que utilizaremos en la WEB dentro de la carpeta routes, en el fichero web.php de definición de rutas para la WEB, y completamos con el acceso a todas las rutas CRUD:

// Añadimos la ruta al controlador que utilizaremos para la ruta
use App\Http\Controllers\CmCustomerController;

// Añadimos el acceso a todas las rutas estándar CRUD
Route::resource('customers', CmCustomerController::class);

5.1.5. Definimos el controlador para clientes en WEB

En este caso solo vamos a definir los métodos de visualización, ya que la creación y el mantenimiento de los clientes lo haremos a través de los trabajos que definiremos para los microservicios en la siguiente publicación, así en los métodos para las consultas web en el controlador App\Http\Controllers\CmCustomerController que implementaremos para su visualización son:

  • index(): listado de todos clientes
  • show(CmCustomer $customer): consulta de datos de un cliente

Veámoslos paso a paso, donde iremos creando la estructura de la aplicación y finalmente la visualización de clientes y la gestión de los pedidos:

5.1.5.1 Listado de clientes index()

Vamos a realizar un carga directa de todos los clientes y que presentaremos en una vista con una tabla y el listado de clientes:

  /**
   * Muestra una lista de las clientes.
   * Display a list with all the customers.
   * @return \Illuminate\Http\Response
   *               devuelve las vista cargaada con los clientes.
   *               returns the view loading with the customers.
   */
  public function index()
  {
    $customers = CmCustomer::all();
    return view('customers.index')->with('customers', $customers);
  }

Para la creación de la vista creamos una carpeta customer dentro de resources/views, y ahí creamos la vista index.blade.php, para este desarrollo vamos a utilizar directamente el sistema de plantillas blade de Laravel con Bootstrap, esto lo vamos a ve después del controlador de definir los controladores .

5.1.5.2 Consulta de datos de un cliente show(CmCustomer $customer)

En la consulta de clientes utilizamos el método show(CmCustomer $customer) donde simplemente se recogen los datos del cliente que se pasa como parámetro y se inyectan a la vista correspondiente: customers.show:

  /**
  * Display the specified resource.
  * Show the language selected by id.
  * @param  \App\Models\CmCustomer  $CmCustomer
  * @return \Illuminate\Http\Response
  */
  public function show(CmCustomer $customer)
  {
    return view('customers.show', compact('customer'));
  }

5.1.5. Definimos la vista para clientes en WEB

Blade es el sistema de plantillas por defecto que incorpora Laravel , nos permite combinar código HTML con inyección de código PHP, tal y como veremos, como no es el objetivo de este tutorial vamos a ir desarrollándolo explicando lo básico.

5.1.5.1. Instalamos Bootstrap

Como framework CSS vamos a utilizar Bootstrap, para ello vamos a utilizar el paquete que nos proporciona Laravel y que nos genera un estructura básica CSS, JS y HTML para un sistema de login con menú básico y es laravel/ui, que nos proporciona este paquete:

  • Generación de uns scaffolding (estructura base) para Bootstrap y un sistema de login.
  • Generación de uns scaffolding (estructura base) para Vue o React y un sistema de login.
  • Para la compilación de nuestro sistema de plantillas utilizaremos Sass con Laravel Mix.

Cómo alternatica a laravel/ui tienes laravel/breeze que te proporcionará lo mismo con el framework CSS Tailwind (esta es la versión que encontrarás como ejemplo en la documentación de Laravel a partir de la versión 8)

5.1.5.2. Creamos la estructura base

Pues nada maś, instalamos vía composer:

$ docker-compose exec orders-app composer require laravel/ui
Info from https://repo.packagist.org: #StandWithUkraine
Using version ^3.4 for laravel/ui
./composer.json has been updated
Running composer update laravel/ui
Loading composer repositories with package information
Info from https://repo.packagist.org: #StandWithUkraine
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking laravel/ui (v3.4.2)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Downloading laravel/ui (v3.4.2)
  - Installing laravel/ui (v3.4.2): Extracting archive
Package swiftmailer/swiftmailer is abandoned, you should avoid using it. Use symfony/mailer instead.
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fruitcake/laravel-cors
Discovered Package: laravel/sail
Discovered Package: laravel/sanctum
Discovered Package: laravel/tinker
Discovered Package: laravel/ui
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Discovered Package: vladimir-yuldashev/laravel-queue-rabbitmq
Package manifest generated successfully.
78 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan vendor:publish --tag=laravel-assets --ansi --force
No publishable resources for tag [laravel-assets].
Publishing complete.

Generamos el scaffolding de autentificación:

$ docker-compose exec orders-app php artisan ui bootstrap --auth
Bootstrap scaffolding installed successfully.
Please run "npm install && npm run dev" to compile your fresh scaffolding.
Authentication scaffolding generated successfully.

Lanzamos la ejecucion de mix con npm:

$ docker-compose exec orders-app npm install && npm run dev
npm WARN deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.

added 772 packages, and audited 773 packages in 1m

84 packages are looking for funding
  run `npm fund` for details
...........

> dev
> npm run development


> development
> mix

● Mix █████████████████████████ emitting (98%)  
 after emit

● Mix █████████████████████████ done (99%) plugins 
 WebpackBar:done
✔ Mix
  Compiled successfully in 4.59s
                         
   Laravel Mix v6.0.43   
                    

✔ Compiled Successfully in 4545ms
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬──────────┐
│                                                                                                                                                      File │ Size     │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼──────────┤
│                                                                                                                                                /js/app.js │ 2.23 MiB │
│                                                                                                                                               css/app.css │ 202 KiB  │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────┘
webpack compiled successfully
 

Después de la primera instalación ya solo utilizaremos npm run dev para la compilación de nuestro código.

5.1.5.3. Explicamos la estructura generada

Una vez hecho esto, si vamos a nuestro proyecto Laravel veremos que se han creado los siguientes ficheros:

  • resources
    • resources/css
      • resources/css/app.css: fichero base para la creación del código CSS de nuestra aplicacíon.
    • resources/js
      • resources/js/app.js: fichero para nuestros scripts js, este fichero solo incluye inicialmente: require(‘./bootstrap’);
      • resources/js/bootstrap.js: carga de bootstrap en nuestra aplicación.
  • resources/sass
    • resources/sass/_variables.scss: variables CSS que nos genera por defecto
    • resources/sass/app.scss: definición de los ficheros a incluir en la compilación Sass, entre ellos Bootstrap.
  • resources/views
    • resources/views/auth
      • resources/views/auth
        • resources/views/auth/passwords: estructura HTML / Blade para el reset de passwords
        • resources/views/auth/login.blade.php: estructura HTML / Blade de una página de login básica con el sistema de usuarios básicos que trae Laravel.
        • resources/views/auth/register.blade.php: estructura HTML / Blade de un registro de usuario
        • resources/views/auth/verify.blade.php: estructura HTML / Blade para la verificación.
    • resources/views/layouts
      • resources/views/layouts/app.blade.php: con el sistema de plantillas blade este fichero será nuestra estructura base, como veremos a continuación, inyectaremos el código HTML directamente utilizando el sistema de plantillas que nos proporciona Blade.

Al mismo tiempo también se crea la parte del controladoar para la creación de nuevos usuarios (vía register) y para el control de login, no entraremos en detalle, te recomiendo que lo veas tu mismo.

Nuestro fichero app.blade.php tiene el siguiente contenido:

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav me-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ms-auto">
                        <!-- Authentication Links -->
                        @guest
                            @if (Route::has('login'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                </li>
                            @endif

                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item"><a class="nav-link" href="#">Ayuda</a></li>
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }}
                                </a>

                                <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                                        @csrf
                                    </form>
                                </div>
                            </li>

                        @endguest

                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            <div class="container">
                <!-- Content here -->
                @yield('content')
            </div>
        </main>
    </div>
</body>
</html>

Hemos marcado en negrita donde se inyectará el código que haga referencia a este plantilla para eso utilizamos @yield al que haremos referencia utilizando @extends dentro de las páginas que creemos, lo único que hemos añadido aquí es un div sobre este campo para indicar que es un contendor utilizando Bootstrap esto lo hacemos con: <div class=»container»>, en este fichero será donde añadiremos las entradas de menú a clientes y pedidos.

Cómo se ve nuestra web, sin haber hecho nada aún, pues ya tenemos las páginas de login y registro funcionando:

Desde la página inicial ya nos aparece la página de Login y de Registro completamente funcionales, no entraremos en esta publicación en detalles, simplemente te recomiendo que lo explores registres un usuario y veas el funcionamiento externo e interno:

ORDERS APP - Página de login autogenerada
ORDERS APP – Página de login autogenerada
ORDERS APP - Página de registro autogenerada
ORDERS APP – Página de registro autogenerada
ORDERS APP - Página home autogenerada
ORDERS APP – Página home autogenerada

Una vez logeados accedemos a nuestra home con un menú con nuestro usuario, desde ya podemos hacer logout, en este menú añadiremos las entradas a clientes y pedidos.

Nota: no entraremos en detalle de como securizar las páginas que vayamos creando, esto lo veremos más adelante con un anexo.

5.1.5.4. Creamos las plantilla para el listado de clientes: index

Si recordamos nuestro controlador podemos ver que hacer referencia a la plantilla index y que le pasa el objeto customers que serán los clientes que cargaremos para visualizar en la web: view(‘customers.index’)->with(‘customers’, $customers);

Lo que hacemos es crear el fichero index dentro de resources/customers carpeta nueva, y le añadiremos la referencia a la plantilla app.blade.php que se ha generado indicando donde en que sección se inyectará nuestro código con @sectionm haciendo referencia al valor content que definimos con la instrucción @yield en app.blade.php.

El siguiente paso será utilizar @foreach que como ya indica la palabra nos permitirá hacer un bucle sobre customers:

@extends('layouts.app')
@section('content')
    <div class="row">
        <div class="col">
            <h1 class="text-secondary">Lista de Clientes</h1>
            @if ($message = Session::get('customer-success'))
                <div class="alert alert-success">
                    <p>{{ $message }}</p>
                </div>
            @endif
            <table class="table">
                <thead>
                    <tr>
                        <th scope="col"># Id Cliente</th>
                        <th scope="col">Cliente</th>
                        <th scope="col" class="text-center">Contacto</th>
                        <th scope="col" class="text-center">Estado</th>
                        <th scope="col" class="text-center">País</th>
                        <th scope="col" class="text-center">Acciones</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach ($customers as $customer)
                        <tr>
                            <td scope="row">{{ $customer->idcustomer }}</td>
                            <td>{{ $customer->customer }}</td>
                            <td class="text-center">{{ $customer->contacto }}</td>
                            <td class="text-center">{{ $customer->customerstate }}</td>
                            <td class="text-center">{{ $customer->country }}</td>
                            <td class="text-center">
                                <a href="{{ route('customers.show', $customer->idcustomer) }}"
                                    class="btn btn-info">Ver</a>
                            </td>
                        </tr>
                    @endforeach
                </tbody>
                <tfoot>
                    <th scope="col"># Id Cliente</th>
                    <th scope="col">Cliente</th>
                    <th scope="col" class="text-center">Contacto</th>
                    <th scope="col" class="text-center">Estado</th>
                    <th scope="col" class="text-center">País</th>
                    <th scope="col" class="text-center">Acciones</th>
                </tfoot>
            </table>
        </div>
    </div>
@stop
  • @extends(‘app’): con esta introducción hacemos referencia a la plantilla principal que utilizaremos, aún no la hemos creado, lo veremos a continuación.
  • @section(‘content’): en esta plantilla definiremos una sección para el contenido que será donde se inyecte nuestro código de sección content que se finaliza con el comando @stop
  • @foreach($customers as $customer): incluimos código HTML para la creación de una tabla, y para las filas incluimos un bucle foreach para recorrer los clientes que pasamos en la variable customers desde el controlador que hemos definido en la línea: return view(‘customers.index’)->with(‘customers’, $customers);

El objetivo de este tutorial no es el diseño del Frontend, así que en esa parte solo introduciremos lo básico utilizando Bootstrap.

Ya hemos incluido el enlace a la visualización de cada registro de cliente con

<a href=»{{ route(‘customers.show’, $customer->idcustomer) }}» class=»btn btn-info»>Ver</a>

A continuación vamos a crear este página y ver los datos de un cliente en concreto.

Este es el resultado:

ORDERS APP - Listado de cliente
ORDERS APP – Listado de cliente

Como podéis ver ya hemos añadido unas entradas en el menú para: Clientes, Pedidos y Ayuda, las vamos a añadir para que se muestren solo cuando el cliente está logeado, esto lo hacemos en nuestra página de layout.app:

!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ms-auto">
                        <!-- Authentication Links -->
                        @guest
                            @if (Route::has('login'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                </li>
                            @endif

                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item"><a class="nav-link" href="{{url('/customers')}}">Clientes</a></li>
                            <li class="nav-item"><a class="nav-link" href="{{url('/customers')}}">Pedidos</a></li>
                            <li class="nav-item"><a class="nav-link" href="#">Ayuda</a></li>
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }}
                                </a>

                                <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                                        @csrf
                                    </form>
                                </div>
                            </li>

                        @endguest

                    </ul>

En negrita marcamos el código añadido, en este caso inicialmente creamos todos los enlaces apuntando a la página de customers esto lo cambiaremos al añadir las nuevas entradas.

En proyectos grandes tenemos que ver la utilidad del uso de las plantillas blade para crear páginas para la inyección de estos menús por ejemplo, permitiendo una carga dinámica del contenido del mismo.

5.1.5.5. Creamos las plantilla para el listado de clientes: show

Para mostrar los clientes añadimos una plantilla sencilla para mostrar los datos del cliente, con unos botones de recarga y vuelta al índice:

@extends('layouts.app')
@section('content')

    <h1 class="text-primary">Cliente seleccionado {{ $customer->customer }}</h1>

    <form action="{{ route('customers.show', $customer->idcustomer) }}" method="GET" class="row g-3">
        @method('POST')

        @csrf
        <div class="col-md-3">
            <label for="idcustomer">Cliente ID</label>
            <input id="idcustomer" name="idcustomer" class="form-control" type="text" disabled
                value="{{ isset($customer) ? $customer->idcustomer : '' }}" aria-describedby="idcustomerHelp"
                placeholder="Introduce el Id">
            <small id="idcustomerlHelp" class="form-text text-muted">Identificador único del cliente.</small>
        </div>
        <div class="col-md-9">
            <label for="customer">Cliente</label>
            <input id="customer" class="form-control" name="customer" type="text" disabled
                value="{{ isset($customer) ? $customer->customer : '' }}" aria-describedby="customerHelp"
                placeholder="Nombre del cliente">
        </div>
        <div class="col-md-12">
            <label for="identerprise">Empresa</label>
            <input id="customer" class="form-control" name="customer" type="text" disabled
                value="{{ $customer->identerprise }}" aria-describedby="customerHelp" placeholder="Nombre del cliente">
        </div>

        <div class="col-md-6">
            <label for="contact"">Nombre cliente</label>
                        <input  id=" contact" class="form-control" name="contact" type="text" disabled
                value="{{ isset($customer) ? $customer->contact : '' }}">
        </div>
        <div class="col-md-6">
            <label for="customeralias">Alias</label>
            <input id="customeralias" class="form-control" name="customeralias" type="text" disabled
                value="{{ isset($customer) ? $customer->customeralias : '' }}">
        </div>
        <div class="col-md-3">
            <label for="customerstate">Estado</label>
            <input id="idcustomerstate" class="form-control" name="customerstate" type="text" disabled
                value="{{ isset($customer) ? $customer->customerstate : '' }}">
        </div>
        <div class="form-group col-md-2">
            <label for="sale">Saldo</label>
            <input id="idsale" class="form-control" name="sale" type="text" disabled
                value="{{ isset($customer) ? $customer->sale : '' }}">
        </div>
    </form>
    <hr>
    <a class="btn btn-secondary" role="button" href="{{ route('customers.index') }}">Volver al índice</a>
    @if (isset($customer))
        @if ($customer->idcustomer)
            <a class="btn btn-secondary" role="button"
                href="{{ route('customers.show', $customer->idcustomer) }}">Recargar</a>
        @endif
    @endif
@stop

Este es el resultado:

ORDERS APP - Consulta de un cliente
ORDERS APP – Consulta de un cliente

Con esta parte de introducción a las parte web con clientes pasamos al segundo paso que será la gestión de pedidos y que veremos en la próxima publicación 5.2. ORDER APP – PEDIDOS: CRUD de pedidos modelo y controlador (cm_order).