Curso de Laravel
Montando un proyecto
Instalación del entorno necesario
1. Instalamos composer
2. Creamos el proyecto de Laravel:
composer create-project laravel/laravel .
3. Configuramos el fichero .env para que funcione con mysql:
DB_CONNECTION=mysql
DB_HOST=db # Este dato debe coincidir con nuestra configuración de docker
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=root
3. Levantamos la aplicación de docker con la configuración de este enlace.
4. Ejecutamos el siguiente comando para crear las tablas en la base de datos:
docker compose run --rm artisan migrate
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
./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 firebase
Debemos instalar el módulo correspondiente utilizando JWT:
composer update -W or composer require -W kreait/firebase-php
/app/Http/Controllers/UserController.php
class UserController extends Controller
{
public function index(Request $request)
{
$token = $request->post('token');
$user = EnsureTokenIsValid::getUserFromFirebase($token);
return response()->json($user);
}
}
/app/Http/Middleware/EnsureTokenIsValid.php
public static function getUserFromFirebase($token)
{
// Tras el login con firebase que hacemos en el front, enviamos el token y si es válido, en el caso de que el usuario no exista, lo añadimos a la base de datos
if ($token != null) {
$auth = app('firebase.auth');
try {
$verifiedIdToken = $auth->verifyIdToken($token);
$userEmail = $verifiedIdToken->claims()->get('email');
// Buscar el usuario en la base de datos local
$user = User::where('email', $userEmail)->first();
if ($user) {
return $user;
} else {
// Si el usuario no existe, crear uno nuevo
$name = $verifiedIdToken->claims()->get('displayName');
$user = User::create(['name' => $name,'email' => $userEmail,]);
return $user;
}
} catch (\Kreait\Firebase\Exception\Auth\FailedToVerifyToken $e) {
Log::error('Token verification failed: ' . $e->getMessage());
return response()->json(['error' => 'Unauthorized'], 401);
}
}
return null;
}
/storage/app/firebase-credentials.json
{
"type": "service_account",
"project_id": "*****",
"client_email": "****@gmail.com",
}
/app/Providers/AppServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Kreait\Firebase\Factory;
use Illuminate\Support\Facades\Log;
use Kreait\Firebase\Exception\FirebaseException;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
try {
$factory = (new Factory)
->withServiceAccount(storage_path('app/firebase-credentials.json'));
} catch (FirebaseException $e) {
Log::error('Firebase initialization error: ' . $e->getMessage());
} catch (\Exception $e) {
Log::error('General error: ' . $e->getMessage());
}
}
public function boot(): void
{
//
}
}
/config/firebase.php
<?php
return [
'credentials' => env('FIREBASE_CREDENTIALS'),
];
/.env
FIREBASE_DATABASE_URL=https://secondmama-e5862.firebaseio.com
FIREBASE_CREDENTIALS=storage/app/firebase-credentials.json
/routes/api.php
Route::post('/auth/sign-in', [UserController::class, 'index']);
Route::get('/tasks/{type}', [TaskController::class, 'index'])->middleware(EnsureTokenIsValid::class);
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