Laravel

Montando un proyecto

Instalación del entorno necesario

1. Instalamos Laragon Full.

2. Instalamos composer

3. Instalamos globalmente el creador de proyectos de Laravel ejecutando en la terminal el siguiente comando:

composer global require laravel/installer

3. Para probar, podemos ir a localhost.

Creación de un proyecto en Laravel

Tenemos dos opciones:

Utilizando Laragon

Utilizando Laragon. Esta opción se asegura de manetener la compatibilidad entre la versión de Laravel que instalaremos y la versión del servidor que tenemos instalado, lo que se traduce en menos posibilidades de error:
Aplicación de Laragon → Menu Quick App Laravel

Desde la consola

1. Ejecutamos el siguiente comando dentro de c:/laragon/www para crear nuestro proyecto de Laravel:

laravel new example-app

2. Desde el panel de administración de Laragon, recargamos el apache para que detecte el proyecto que acabamos de crear.

3. Ingresamos en: example-app.test

Configuración inicial

  • Debemos renombrar el fichero .env.example a .env e introducir los datos de conexión a la base de datos.
  • Debemos introducir la clave de cifrado de nuestra App en el fichero .env. Esto se hará automáticamente ejecutrando los siguientes comandos:
php artisan key:generate # genera la key y la guarda automáticamente en el fichero .env
php artisan config:cache # borra la caché para que el cambio surta efecto

Tras ejecutra estos comandos habrá que reiniciar el servidor.

  • Al insertar un usuario en la base de datos, nos dará este error:
Laravel Unknown Column 'updated_at'

Para evitarlo, podemos añadir esta propiedad al modelo de User:

/app/Models/User.php

public $timestamps = false;

Error en el driver

Es posible que al ejecutar el comando

composer install

o al ejecutar algunos comandos de php, puede dar el siguiente error:


could not find driver (Connection: mysql, SQL: select table_name as `name`, (data_length + index
_length) as `size`, table_comment as `comment`, engine as `engine`, table_collation as `collation`
 from information_schema.tables where table_schema = '...' and table_type in ('BASE TABLE', 
'SYSTEM VERSIONED') order by table_name)

Esto puede ser debido a que nos falta el driver de php_pdo_mysql. Aunque lo tengamos instalado en Laragon, puede ser que no lo tengamos instalado en el php de nuestro sistema operativo. Para que esté instalado en el PHP de nuestro sistema operativo, debemos encontrar la ubicación del fichero php.ini de nuestro sistema operativo ejecutando el comando:

php --ini

En este fichero borraremos el ; de esta línea:

;extension=php_pdo_mysql.dll

.

Ficheros y carpetas fundamentales de una aplicación Laravel

  • app. Aquí estan los ficheros que nosotros estamos desarrollando. Aquí pasaremos la mayor parte del tiempo.
    • Http/Controllers: Esta es la capa que procesa las solicitudes del usuario.
    • Http/Middleware: Nos permiten atrapar una petición y hacer alguna validación adicional, inyectar código, etc.
  • models. Se comunican con tablas de la base de datos para gestionarlas.
  • routes: Aquí están las rutas correspondientes a las peticiones del cliente.
  • vendor: Aquí está todo el código que necesita Laravel para poder funcionar, es decir, el propio framework y sus dependencias.

Componentes fundamentales de un proyecto con Laravel

  • model → representan la estructura y la interacción con la base de datos. Proporcionan métodos para realizar operaciones de base de datos, como consultas, inserciones, actualizaciones y eliminaciones.
php artisan make:model Book
  • controller → manejan la lógica de la aplicación y actúan como intermediarios entre las rutas y los modelos. Reciben solicitudes HTTP, interactúan con los modelos y devuelven una respuesta al cliente.
php artisan make:controller BookController
  • seeder → permiten poblar la base de datos con datos de prueba o iniciales.
php artisan make:seeder BookSeeder
  • migration → son documentos escritos en PHP que serán traducidos en tablas de la base de datos.
php artisan make:migration create_books_table
  • factory → se utilizan para generar datos de prueba de manera rápida y automatizada. Son útiles para crear registros falsos en la base de datos durante las pruebas o para llenar la base de datos con datos de prueba.

La siguiente instrucción creará todos los componentes anteriores ficheros (aunque Book esta en singular, la tabla de la base de datos será creada en plural):

php artisan make:model Book -a

La siguiente instrucción creará un modelo, una migración y un seeder:

php artisan make:model Book -ms

Enrutamiento

Controller

Para crear las rutas editaremos el fichero routes/api.php:

use App\Http\Controllers\Api\BookController
...
// Es SUPER IMPORTANTE que la siguiente url no termine en barra
Route::get('/book', [BookController::class, 'index']);
Route::get('/book/{id}', [BookController::class, 'getById']);

Si con el navegador accedemos a la ruta http://laraapp.test/api/brand, deberíamos ver una página en blanco.

BookController.php

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Book;
use Illuminate\Http\Request;

class BookController extends Controller
{
    public function index(Request $request)
    {
        // Recogemos los query params:
        // i.get('/books', {params: { id}});
        $id = $request->query('id');
        // ... o los parámetros enviados en la url
        //  i.get(`/books/${id}`)
        $id = $request->route('id');

        if ($id!= null) {
             $books = Book::where('categoryId', $categoryId)
                ->get();
            return response()->json($books);
        }

        $books = Book::get();
        // $books = Book::paginate(10);
        return response()->json($books);
    }

    public function create(Request $request)
    {
            $title = $request->route('title');
            $newBook = new Book;
            $newBook->title= $title;
            $newBook->save();
            return response()->json(['message' => 'book created'], 200);
    }

    public function update(Request $request)
    {
            $title= $request->route('title');
            $newBook = new Book();
            $newBook->title= $title;
            $newBook->save();
            return response()->json(['message' => 'book updated'], 200);
    }

    public function destroy(Request $request, $digitalActionId)
    {
         $id = $request->route('id');
         $res = Book::where('bookId','=', $id)->delete();
         return response()->json(['message' => 'book deleted'], 200);
    }
}

Componentes relacionados con la base de datos

Definiremos los datos de conexión el el fichero ./env

Migration

Creando la estructura de nuestras tablas

Siempre nombraremos nuestras tablas en plural. Esto es imprescindible para que todo funcione correctamente en Laravel. Las propiedades de las tablas podemos nombrarlas en singular.

Dentro de una migración, la función up() sirve para crear las columnas y la función down() para eliminar las columnas creadas. Este último paso nos permite revertir las migraciones.

Definiendo los tipos de dato
$table->string("title", 255);
$table->text("content");
$table->enum("posted", ["yes", "not"]);
$table->boolean('email_verified');
$table->unsignedBiginteger('editorial_id');
$table->unsignedInteger('price');
Relación one to many
$table->foreign("author_id")->references('id')->on('books')->onDelete("cascade");
Relación many to many

Vamos a crear una relación muchos a muchos entre la tabla students y la tabla teachers. Para ello, ambas tablas deberán tener su correspondiente fichero de modelo y migración.

Necesitaremos una tabla auxiliar o de pivote. Para crear el modelo y su correspondiente fichero de migración de la tabla de pivote, podemos usar:

php artisan make:model StudentTeacher -m

Las instrucciones para crear el modelo y el fichero de migraciones por separado serían:

php artisan make:model StudentTeacher 
php artisan make:migration create_student_teachers_table

En el fichero de migración de de student_teachers:

$table->unsignedBiginteger('student_id');
$table->unsignedBiginteger('teacher_id');
$table->foreign('student_id')->references('id')->on('students')->onDelete('cascade');
$table->foreign('teacher_id')->references('id')->on('teachers')->onDelete('cascade');

Para montar la base de datos borrando los datos anteriores y cargar los nuevos…

Seeder

Ver código.

./database/seeders/BrandSeeder.php

class BrandSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        Book::create(
            [
                'title' => 'Viaje al centro de la tierra',
                'description' => 'bla bla bla...'
            ]
        );
    }
}

./database/seeders/DatabaseSeeder.php

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call(BrandSeeder::class);
    }
}

Para cargar los datos:

php artisan db:seed

Montar base de datos borrando datos

php artisan migrate:fresh --seed
  • El modificador seed carga la base de datos con los datos especificados en los seeders.
  • Es posible que en el gestor de la base de datos (heidi) no refresque los datos automáticamente y haya que cerrar la conexión y volver a abrirla para que se vean actualizados.

Consultas

Cuando necesito los datos de dos o más tablas puede pasar que:

  • El where se aplica a todas las tablas que intervienen en la consulta → Usaremos join
Book::where('id', $bookId)               
->join('autors', 'autors.bookId', '=', 'books.id')
  • El where no se aplica a todas las tablas que intervienen en la consulta → Usaremos dos consultas
$books = $books1->concat($books2);

Añadiendo un alias.

->select('books.*', 'autors.name as autorName')

Recibiendo…

Un resultado:

 $brand = Book::where('id', '=', $bookId)
->first();

Varios resultados:

$brands = Book::where('editorialId', '=', $editorialId)
->get();

Desplegar la aplicación en un servidor compartido

Navegaremos a la carpeta public de nuestro proyecto.

Logs (trazas)

Debemos añadir la siguiente variable de entorno al fichero .env:

APP_DEBUG=true

Los logs aparecerán en storage/logs/laravel.logs.

Para generar una traza podemos utilizar el siguiente código:

Log:info(print_r($obj), true); // El parámetro true hace que el objeto se muestre como un texto

Autentificación con Auth0

Configuración en Auth0

Applications → Create Application → Machine to Machine → Asignamos todos los permisos

Configuración en el código de Laravel

Instalaremos el módulo de auth0. Para ello, ejecutaremos el siguiente comando en la terminal:

composer require auth0/auth0-php

Añadiremos las siguientes variables de entorno al fichero .env:

# The URL of our Auth0 Tenant Domain.
# If we're using a Custom Domain, be sure to set this to that value instead.
AUTH0_DOMAIN='https://{yourDomain}'

# Our Auth0 application's Client ID.
AUTH0_CLIENT_ID='{yourClientId}'

# Our Auth0 application's Client Secret.
AUTH0_CLIENT_SECRET='{yourClientSecret}'

# Our Auth0 API's Identifier.
AUTH0_AUDIENCE='YOUR_API_IDENTIFIER'

Código de Laravel:

class UserController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(Request $request)
    {
        $token = $request->post('token');

        $auth0 = new Auth0([
            'domain' => env('AUTH0_DOMAIN'),
            'clientId' => env('AUTH0_CLIENT_ID'),
            'clientSecret' => env('AUTH0_CLIENT_SECRET'),
            'audience' => [env('AUTH0_AUDIENCE')],
            'cookieSecret' => env('AUTH0_COOKIE_SECRET'),
        ]);

        $userInfo = $auth0->decode($token);

        var_dump($userInfo);
    }

La aplicación de React:

import { useAuth0 } from "@auth0/auth0-react";

const { loginWithRedirect, isAuthenticated, getIdTokenClaims } = useAuth0();

useEffect(() => {
  if (isAuthenticated) {
    getIdTokenClaims().then(({ __raw: idToken }) => login(idToken));
  }
}, [isAuthenticated]);

Posible error al ejecutar una petición:

cookieSecret` must be configured

Uso del Middleware para gestionar la autentificación

php artisan make:middleware EnsureTokenIsValid

/app/Http/Middleware/EnsureTokenIsValid.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Auth0\SDK\Auth0;
use App\Models\User;
use Illuminate\Support\Facades\Log;

class EnsureTokenIsValid
{

    public static function getUserFromAuth0($token)
    {
        $auth0 = new Auth0([
            'domain' => env('AUTH0_DOMAIN'),
            'clientId' => env('AUTH0_CLIENT_ID'),
            'clientSecret' => env('AUTH0_CLIENT_SECRET'),
            'audience' => [env('AUTH0_AUDIENCE')],
            'cookieSecret' => env('AUTH0_COOKIE_SECRET'),
        ]);

        if ($token != null) {
            $userInfo = $auth0->decode($token);
            $userData = $userInfo->toArray();
            $user = User::where('email', $userData['email'])->first();

            if ($user) {
                if ($user->email_verified != $userData['email_verified']) {
                    $user->email_verified = $userData['email_verified'];
                    $user->save();
                }
                return $user;
            } else {
                $user = new User();
                $user->email = $userData['email'];
                $user->email_verified = $userData['email_verified'];
                $user->save();
                return $user;
            }
        }
        return null;
    }

    public function handle(Request $request, Closure $next): Response
    {
        $token = $request->header('Authorization');
        $user = self::getUserFromAuth0($token);
        if ($user == null) {
            return response('Unauthenticated.', 401);
        }
        $request->request->add(['user' => $user]);
        return $next($request, $user);
    }
}

Para vincular el middleware a una petición:

/routes/api.php

Route::get('/tasks/{type}', [TaskController::class, 'index'])->middleware(EnsureTokenIsValid::class);;

Para acceder al valor seteado en el middleware desde el controlador, usaremos:

$user = $request->get('user'); // Esto coge el dato que puse en el middleware