Ir al contenido principal

Laravel 8 desde cero Parte V - Proyecto Blog VIP

Laravel 8 desde cero Parte V - Proyecto Blog VIP

Introducción

Primero que todo, este proyecto requiere autenticación, por lo cual se debe tener en cuenta contar con el entendimiento del código explicado en la parte I y la parte IV, para la exigencia de la verificación.

Para el Back-End o tabla para las publicaciones

Desde la linea de comandos y en la carpeta respectiva ejecutamos:
php artisan make:model Post -mf
Con el anterior comando se crean los archivos para el modelo:
App\Models\Post.php
En el modelo mencionado antes del cierre de la clase agregamos:
    protected $fillable = [
        'title', 'content',
    ];
    protected $hidden = [
        'id', 'user_id',
    ];
La migración:
Database\Migrations\aaaa_mm_dd_time_create_post_table.php
En la mencionada migración entre id y timestamp agregamos:
    $table->unsignedBigInteger('user_id')->references('id')->on('users');
    $table->string('title');
    $table->text('content');
Y al usar -mf crea la migración y el factory 'Database\Factories\PostFactory', y cuyo uso es similar a los semilleros, pero sin el uso ni la creación del archivo Seeder, lo cual veremos en el siguiente atículo.
A continuación ejecutamos el comando:
    php artisan migrate
Que se encarga de crear la tabla en nuestra base de datos.
Observaciones:
  • Las variables  'title', y 'content' son los únicos datos declarados en este ejemplo, para hacerlo sencillo. Ver en Post.php protected $fillable = ['title', 'content',];
    Las siguientes son variables digamos automáticas.
  • Las variables  'id', 'user_id' son el ordinal de la publicación, y el id del usuario que la creó respectivamente. Ver en  Post.php protected $hidden = ['id', 'user_id',];
  • Las variables de timestamps 'created_at', y 'updated_at' corresponden a la fecha de creación y la de actualización.

Para el controlador (Back-End) y servidor de vistas (Front-End) ejecutamos

php artisan make:controller PostController --resource --model=Post
Que crea el archivo App\Http\Controllers\PostController.php, que se encarga de servir las vistas y controla las funciones store, update y destroy (almacenar, actualizar y eliminar).
En dicho archivo modificamos el servicio de vistas, con el código siguiente:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Auth;
use App\Http\Requests\PostFormRequest;
class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        $posts = Post::paginate(10);
        return view('post\index', compact('posts'));
    }
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('post\create');
    }
    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(PostFormRequest $request)
    {
        $post = new Post();
        $post->title = $request->input('title');
        $post->content = $request->input('content');
        $post->user_id = $request->user()->id;
        $post->save();
        return redirect()
            ->route('posts.show', ['post' => $post])
            ->with('message', 'lblcreado');
    }
    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function show(Post $post)
    {
        return view('post.show', compact("post"));
    }
    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Models\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function edit(Post $post)
    {
        return view('post\edit', compact("post"));
    }
    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function update(PostFormRequest $request, Post $post)
    {
        if (Auth::user()->cant('update', $post)) {
            return redirect()
            ->route('post/my', ['post' => $post])
            ->with('message', 'lblnopermisos');
        }
        $post->title = $request->input('title');
        $post->content = $request->input('content');
        $post->save();
        return redirect()
            ->route('post/my', ['post' => $post])
            ->with('message', 'lblactualizado');
    }
    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function destroy(Post $post)
    {
        if (Auth::user()->cant('delete', $post)) {
            return redirect()
            ->route('post/my', ['post' => $post])
            ->with('message', 'lblnopermisos.');
        }
        $post->delete();
        return redirect()
            ->route('post/my', ['post' => $post])
            ->with('message', 'lblborrado');
    }
    public function my(Request $request)
    {
        $idlcluser = $request->user()->id;
        $allposts = Post::all();
        $posts = $allposts->where('user_id', $idlcluser);
        return view('post/my', compact("posts"));
    }
}
Observaciones:
  • Primero tenemos la vista index, que hace uso de paginate, es por ello que en el layout o diseño (El cual se puede ver más adelante) se declara tailwind.min.css, ya que a partir de Laravel 8, sin dicho estilo declarado no funciona la vista, con el uso de paginate.
  • Create, show y edit, son vistas fijas, igual que my, que muestra las publicaciones del usuario.
  • La función store, se encarga de almacenar la publicación a partir de create, tal como la función update, se encarga de actualizar la información a partir de edit, con la diferencia de que update solo está disponible para el creador de la publicación, al igual que la función destroy, y donde el archivo PostPolicy, es el encargado de evaluar para update y delete, que sea el autor de la publicación quien elimina o edita la publicación.
  • Es importante notar que cuando se usa with('message',etiqueta), etiqueta es el nombre de la label multi-idioma, que mostrará la vista, y que veremos en el código de dichas vistas como lo evalua para mostrar el mensaje multi-idioma.

Para validar los datos y responder con mensajes según el error

Para validar los campos del modelo Post , incluyendo mensajes de error hacemos uso del archivo PostFormRequest, ver en el código anterior resaltado en amarillo, archivo que creamos, ejecutando el siguiente comando:
php artisan make:request PostFormRequest
Creando el archivo App\Http\Requests\PostFormRequest al cual le asignaremos el siguiente código para el ejemplo (pero que podemos modificar según requerimientos):
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PostFormRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|min:5|max:100',
            'content' => 'required|min:15|max:1000',
        ];
    }
    public function messages(){
        return[
           'title.required' => 'El titulo es requerido - Title is required',
            'title.min' => 'El titulo debe tener al menos 5 caracteres - The title must be at least 5 characters',
            'title.max' => 'El titulo no debe tener mas de 100 caracteres - The title must not be more than 100 characters',
            'content.required' => 'El contenido es requerido - Content is required',
            'content.min' => 'El contenido debe tener al menos 15 caracteres - Content must be at least 15 characters',
            'content.max' => 'El contenido no debe tener mas de 1000 caracteres - The content must not be more than 1000 characters',
        ];
    }
}

En este caso uso dos idiomas para los errores.

Restringiendo la eliminación y modificación al creador de la publicación

Vemos en el archivo App\Http\Controllers\PostController.php, el uso de Auth, también resaltado en amarillo y que que utiliza la información del archivo PostPolicy, para verificar la autorización.
Para que solo el autor del post o publicación, pueda eliminar o actualizar la publicación.
Ejecutamos el comando:
php artisan make:policy PostPolicy --model=Post
Creando el archivo App\Policies\PostPolicy.php
Verificar en cabecera:
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
Si se quiere (es opcional) se elimina de cabecera:
use Illuminate\Auth\Access\HandlesAuthorization;
Y se elimina al inicio de la clase:
use HandlesAuthorization;
Y en los métodos delete y update ponemos el sgte. código:
                        return $user->id == $post->user_id;
Para que funcione hay que declararlo en:    App\Providers\AppServiceProvider.php
El cual es una clase extends ServiceProvider, la cual vemos en el archivo PostPolicy en el use de AuthServiceProvider 
Agregamos a la cabecera de App\Providers\AppServiceProvider.php
use App\Policies\PostPolicy;
use App\Post;
Con el siguiente código registramos la clase que creamos, se coloca dentro de la clase,
es el array que contiene el mapa de las políticas de la aplicación:
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

Observaciones:
Para tener en cuenta la autenticación, evaluación y respuestas es importante notar la relación de los archivos:
  • El ultimo archivo 'App\Providers\AppServiceProvider.php' cuya clase extiende la clase ServiceProvider conectándolo, realiza el enlace entre Post y PostPolicy.
  • El archivo 'App\Policies\PostPolicy.php' que conecta el ServiceProvider, con la inclusión de:

  • 'use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;'.

  • Y el archivo PostController que con Auth, y la respuesta de PostPolicy, hace la validación de los métodos delete y update, y que valida los datos y sus respuestas mediante el archivo PostFormRequest .

Para el Front-End

Obsérvese el uso de @lang y el contenido entre corchetes dobles {{ contenido }}.
Creamos un archivo en Resources\Views\bienvenido.blade.php, esto con el objetivo de tener un archivo aparte personalizado.
El código de dicho archivo sería por ejemplo:

Resources\Views\bienvenido.blade.php

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@lang('main.title')</title>
    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">
    <!-- Styles -->
    <link href="{{ asset('css/compulg.css') }}" rel="stylesheet">
    <style>
        html,
        body {
            background-color: #000;
            color: #fff;
            font-family: 'Nunito', sans-serif;
            font-weight: 200;
            height: 100vh;
            margin: 0;
        }
        .full-height {
            height: 100vh;
        }
        .flex-center {
            align-items: center;
            display: flex;
            justify-content: center;
        }
        .position-ref {
            position: relative;
        }
        .top-center{
            position: absolute;
            align-items: center;
            top: 18px;
        }
        .content {
            text-align: center;
        }
        .links>a {
            color: #fff;
            padding: 5px;
            font-size: 13px;
            font-weight: 600;
            letter-spacing: .1rem;
            text-decoration: none;
            text-transform: uppercase;
        }
        section {
            margin-top: 10vh;
            min-height: 70vh;
        }
    </style>
</head>
<body>
    <div class="flex-center position-ref full-height">
        @if (Route::has('login'))
        <div class="top-center links">
            @auth
            <a href="{{ url('/home') }}">@lang('main.lblhome')</a>
            @else
            <a href="{{ route('login') }}">@lang('main.lblin')</a>
            @if (Route::has('register'))
            <a href="{{ route('register') }}">@lang('main.lblreg')</a>
            @endif
            @endauth
        </div>
        @endif
        <section class="d-flex text-center">
            <div class="content">
                <div><div class="conttxtimgslr">
                <div class="ttltxtpimgslr">
                    <img src="https://i.imgur.com/6kfMQFn.jpg" alt="logo de mundo developer" class="imgmdlft">
                    <spam class="spmlgcenter"><b> @lang('main.lblensociedad') </b></spam>
                    <img src="https://i.imgur.com/Ik1TAEA.png" alt="logo de compulg" class="imgclgrgth">
                </div></div></div>
                <h1>@lang('main.lblpresenta')</h1>
                <div class="titlelg">
                    @lang('main.title')
                </div>
                <p>@lang('main.lblpltfrmdscrp')</p>
                <h1>@lang('main.subttl')</h1>
            </div>
        </section>
    </div>
</body>
</html>
Vemos resaltado en verde, las funciones: @if (Route::has('login')) => Si ha ingresado, @auth => Si ha ingresado y esta autorizado muestra el enlace a home, @else => Si no ha ingresado muestra login o ingresar, @if (Route::has('register')) => Si tiene que registrarse (como es el caso al exigir autenticación), muestra Register o Registrarse, luego finaliza el if @endif, el auth @endauth y el if que lo anida @endif. Resumiendo si se ha logueado o ingresado muestra el enlace a inicio = /home, de lo contrario muestra ingresar y registrarse.
En aguamarina vemos las imágenes en un servidor,(es lo que suelo hacer para ahorrar espacio en servidor, y reutilizar las imagenes sin subirlas a varios servidores), sin embargo lo recomendado es descargar dichas imágenes y guardarlas en public, o en una subcarpeta dentro (esto aún no lo realizo, ya que he tenido problemas con las imagenes a nivel local, espero que al subirlas al hosting no presente este problema, de lo contrario, tendré que hacer lo recomendado).
Y en amarillo en el código previo, las etiquetas de idioma @lang, las rutas {{ url('/home') }}, {{ route('login') }}, y {{ route('register') }}. Donde home corresponde al archivo Resources\Views\home.blade.php, loginResources\Views\Auth\login.blade.php, y registerResources\Views\Auth\register.blade.php), y la inclusión para los estilos personalizados del archivo Public\css\compulg.css, el cual tendrá para nuestro ejemplo el siguiente código:

Public\css\compulg.css

body {
    background: rgba(0, 255, 255, 0.3);
    color: black;
}
/* Estilo para el encabezado */
.hdcont {
    left: 0;
    top: 0;
    width: 100vw;
    z-index: 99999;
    background-color: blue;
    color: aliceblue;
}
/* Estilo para contenedor del titulo y logo */
header {
    position: fixed;
    left: 0;
    top: 0;
    width: 100vw;
    z-index: 99999;
    background-color: blue;
    color: aliceblue;
}
/* Estilos para el texto e imagen de la cabecera para pc */
.hdclgltlttl {
    display: none;
}
.hdclgttl {
    font-family: sans-serif;
    font-size: 1.5em;
    padding-left: 10%;
    padding-right: 14%;
    background-color: blue;
    color: aliceblue;
}
.hdclg {
    padding-bottom: 0.8%;
    width: 15%;
    height: auto;
    margin-left: 20%;
}
a {
    background-color: blue;
    color: aliceblue;
}
a:hover {
    background-color: inherit;
    color: cyan;
}
.links a {
    background-color: blue;
    color: aliceblue;
}
.links > a {
    padding: 5px;
}
.links a:hover {
    background-color: blue;
    color: cyan;
}
.custom-toggler.navbar-toggler {
    background-color: aliceblue;
    border-color: blue;
}
.custom-toggler .navbar-toggler-icon {
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0,0, 255, 1)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E");
}
.txtpost {
    height: 100vh;
    padding: 1vw;
    background-color: black;
    color: aliceblue;
}
/* Estilo para alinear junto a card-text en post.show  */
.spmlgcardin {
    padding: 1.7vw;
    text-align: justify-all;
}
/* Estilos para plantilla bienvenido */
.conttxtimgslr {
    display: inline-block;
}
.ttltxtpimgslr {
    display: flex;
    align-content: space-between;
    align-items: center;
}
/* Estilo para texto con imagenes a cada lado */
.imgmdlft {
    height: 15vh;
    width: auto;
    text-align: left;
}
.imgclgrgth {
    width: 25vw;
    height: auto;
    text-align: right;
}
/* Imagen de CompuLG a la izquierda */
.spmlgcenter {
    padding: 1.7vw;
    text-align: center;
    font-size: 3vh;
}
.titlelg{
    font-size: 5vw;
}
@media screen and (max-width: 600px) {
    /* Para el texto responsivo */
    .hdclg {
        padding-bottom: 3%;
        width: 30%;
        height: auto;
    }
    .hdclgttl {
        display: none;
    }
    .hdclgltlttl {
        display: inline;
        margin-right: 15px;
        margin-left: 15px;
        font-family: cursive, sans-serif;
        background-color: blue;
        color: aliceblue;
    }
}
El archivo anterior es donde personalizamos nuestros estilos para las vistas.
Vamos al archivo Routes\web.php y cambiamos welcome, por bienvenido.

Siguiendo el patrón explicado en el artículo anterior, creamos el partial de la navbar, el diseño o layout, ya tenemos el css personalizado, por último las vistas con su respectiva declaración, y su asignación en Routes\web.php (Ver más al final).

Creamos nuestra barra de navegación, para este ejemplo

Resources\Views\_navclg.blade.php, con el contenido:
<nav class="navbar navbar-expand-lg hdcont">
    <div class="container">
        <a class="navbar-brand" href="{{ url('/home') }}">
            <span class="hdclgttl">@lang('main.title')</span>
            <span class="hdclgltlttl">@lang('main.title')</span>
        </a>
        <img src="https://i.imgur.com/Ik1TAEA.png" alt="logo de compulg" class="hdclg">
        <button class="navbar-toggler custom-toggler" type="button" data-toggle="collapse" data-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">
            <!-- Right Side Of Navbar -->
            <ul class="navbar-nav ml-auto">
                <!-- Authentication Links -->
                @guest
                <li class="nav-item">
                    <a class="nav-link" href="{{ route('login') }}">@lang('main.lblin')</a>
                </li>
                @if (Route::has('register'))
                <li class="nav-item">
                    <a class="nav-link" href="{{ route('register') }}">@lang('main.lblreg')</a>
                </li>
                @endif
                @else
                <li class="nav-item dropdown">
                    <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                        {{ Auth::user()->name }} <span class="caret"></span>
                    </a>
                    <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                        <a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                            @lang('main.lblout')
                        </a>
                        <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                            @csrf
                        </form>
                        <a class="dropdown-item" href="{{route('post/index')}}">
                            @lang('main.lblposts')
                        </a><br><br>
                        <a class="dropdown-item" href="{{route('post/create')}}">
                            @lang('main.lblcreatepost')
                        </a><br><br>
                        <a class="dropdown-item" href="{{route('post/my')}}">@lang('main.lblbtnmyposts')</a>
                    </div>
                </li>
                @endguest
            </ul>
        </div>
    </div>
</nav>

Observaciones:
  • En amarillo encontramos los estilos o clases declaradas en compulg.css.
  • En verde vemos las rutas, y el nombre del usuario autorizado.
  • Con fondo azul y texto en blanco vemos las etiquetas de idioma (@lang).
  • Con fondo fucsia, tenemos la relación de la navbar colapsable.
  • En aguamarina tenemos las funciones php en Laravel.
  • En rojo con texto amarillo tenemos el log-out.

Nuestro diseño o layout para el ejemplo será

Resources\Views\Layouts\compulg.blade.php, con el siguiente código:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <title>@yield('title')</title>
    <link href="http://www.iconj.com/icon.php?pid=sz8zcy10pc" rel="shortcut icon" />
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <meta http-equiv="content-language" content="es" />
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <meta name="name" content="{{ config('app.name', 'Laravel') }}" />
    <meta itemprop="image" content="https://i.imgur.com/d7dL3sY.jpg" />
    <meta itemprop="name" content="{{ config('app.name', 'Laravel') }}" />
    <meta name="author" content="Luis Gabriel Hernandez Valderrama" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
    <link href="{{ asset('css/compulg.css') }}" rel="stylesheet">
</head>
<body>        
    @include('_navclg')
    <div class="container">
    @section('mainttl')
    <h1>@lang('main.title')</h1>
    @show
    <br>    
    @yield('content')
    </div><br>
</body>

A la vista Resources\Views\home.blade.php, le asignamos el siguiente código:

home.blade.php

@extends('layouts.compulg')
@section('title')
@lang('main.titlepg')
@endsection
@section('mainttl')
@endsection
@section('content')
@if(Session::has('message'))
@if(Session::get('message') == 'lblupdateok')
<div class="container alert alert-success">
    @lang('main.lblupdateok')
</div>
@endif
@endif
<div class="card">
    <div class="card-header">@lang('main.lblindice') @lang('main.title')</div>
    <div class="card-body">
        @if (session('status'))
        <div class="alert alert-success" role="alert">
            {{ session('status') }}
        </div>
        @endif
        <div>
            <a href="{{route('post/index')}}" class="btn btn-primary">
                @lang('main.lblposts')
            </a><br><br>
            <a href="{{route('post/create')}}" class="btn btn-primary">
                @lang('main.lblcreatepost')
            </a><br><br>
            <a href="{{route('post/my')}}" class="btn btn-primary">@lang('main.lblbtnmyposts')</a>
        </div>
    </div>
</div>
@endsection

Las vistas para los posts, las creamos en Resources\Views\Post, con los siguiente nombres y códigos:

index.blade.php

@extends('layouts.compulg')
@section('title')
@lang('main.lblttlpsts')
@endsection
@section('mainttl')
@endsection
@section('content')
@if(Session::has('message'))
<div class="container alert alert-success">
    {{ Session::get('message') }}
</div>
@endif
<div>
    <div>
        <a href="{{route('post/create')}}" class="btn btn-primary">@lang('main.lblbtncrear')</a>
        <a href="{{route('post/my')}}" class="btn btn-primary">@lang('main.lblbtnmyposts')</a>
    </div><br>
    {{ $posts->links() }}
    <div>
        @foreach($posts as $post)
        <div class="card">
            <div class="card-header">{{ $post->title }}</div>
            <textarea class="form-control" name="content" cols="30" rows="10" readonly>{{ $post->content }}</textarea>
        </div><br>
        @endforeach
    </div>
    {{ $posts->links() }}
</div>
@endsection

create.blade.php

@extends('layouts.compulg')
@section('title','Crear Publicacion')
@section('mainttl')
@endsection
@section('content')
    @if(Session::has('message'))
        <div class="container alert alert-success">
            {{ Session::get('message') }}
        </div>
    @endif
@if($errors->any())
<div class="alert alert-danger">
    <ul>
        @foreach($errors->all() as $error)
        <li>{{ $error }}</li>
        @endforeach
    </ul>
</div>
@endif
<form action="{{ route('posts.store') }}" method="post">
    @csrf
    <div>
        <div>
            <div class="form-group">
                <label for="title">@lang('main.lblttlpost')</label>
                <input type="text" class="form-control" name="title" id="title" placeholder="Titulo" value="{{ old('title') }}">
            </div>
            <div class="form-group">
                <label for="contentlbl">@lang('main.lblcontpost')</label>
                <textarea class="form-control" name="content" cols="30" rows="10">{{ old('content') }}</textarea>
            </div>
        </div>
        <div>
            <button class="btn btn-primary btn-block" type="submit">@lang('main.lblbtnsnd')</button>
        </div>
    </div>
</form>
@endsection

edit.blade.php

@extends('layouts.compulg')
@section('title')
    @lang('main.lblbtnedit')
@endsection
@section('mainttl')
@endsection
@section('content')
@if(Session::has('message'))
@if(Session::get('message') == 'lblactualizado')
<div class="container alert alert-success">
    @lang('main.lblactualizado')
</div>
@endif
@endif
    @if($errors->any())
        <div class="alert alert-danger">
            <ul>
                @foreach($errors->all() as $error)
                <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif
    <form action="{{ route('posts.update',['post' => $post]) }}" method="post">
        @method('PUT')
        @csrf
        <div>
            <div>
                <div class="form-group">
                    <label for="title">@lang('main.lblttlpost')</label>
                    <input type="text" class="form-control" name="title" id="title" placeholder="Titulo" value="{{ $post->title }}">
                </div>
                <div class="form-group">
                    <label for="contentlbl">@lang('main.lblcontpost')</label>
                    <textarea class="txtpost form-control" name="content" cols="30" rows="10">{{ $post->content }}</textarea>
                </div>
            </div>
            <div>
                <button class="btn btn-primary btn-block" type="submit">@lang('main.lblbtnsnd')</button>
            </div>
        </div>
    </form>
@endsection

show.blade.php

@extends('layouts.compulg')
@section('title')
@lang('main.lblshwttlpst')
@endsection
@section('mainttl')
@endsection
@section('content')
@if(Session::has('message'))
@if (Session::get('message') == 'lblcreado')
<div class="container alert alert-success">
        @lang('main.lblcreado')
</div>
@elseif (Session::get('message') == 'lblactualizado')
<div class="container alert alert-success">
        @lang('main.lblactualizado')
</div>
@else
<div class="container alert alert-success">
     {{ Session::get('message') }}
</div>
@endif
@endif
<div>
    <div class="card">
        <div class="card-header">{{ $post->title }}</div>
        <textarea class="spmlgcardin" cols="30" rows="10" readonly>{{ $post->content }}</textarea>
    </div><br>
</div>
@endsection

my.blade.php

@extends('layouts.compulg')
@section('title')
    @lang('main.lblbtnmyposts')
@endsection
@section('mainttl')
@endsection
@section('content')
@if(Session::has('message'))
@if (Session::get('message') == 'lblcreado')
<div class="container alert alert-success">
        @lang('main.lblcreado')
</div>
@elseif (Session::get('message') == 'lblborrado')
<div class="container alert alert-success">
        @lang('main.lblborrado')
</div>
@elseif (Session::get('message') == 'lblactualizado')
<div class="container alert alert-success">
        @lang('main.lblactualizado')
</div>
@elseif (Session::get('message') == 'lblnopermisos')
<div class="container alert alert-success">
        @lang('main.lblnopermisos')
</div>
@else
<div class="container alert alert-success">
     {{ Session::get('message') }}
</div>
@endif
@endif
<div>
    <div>
        <div>
            @foreach($posts as $post)
            <div class="card">
                <div class="card-header">{{ $post->title }}</div>
                <textarea class="spmlgcardin" cols="30" rows="10" readonly>{{ $post->content }}</textarea>
                <div class="card-footer"><spam class="d-flex justify-content-end ml-2">
                    <a href="{{ route('posts.show',['post' => $post]) }}" class="btn btn-primary">@lang('main.lblbtnver')</a>
                    <a href="{{ route('posts.edit',['post' => $post]) }}" class="btn btn-primary  ml-2">@lang('main.lblbtnedit')</a>
                    <form action=" {{ route('posts.destroy',['post' => $post]) }} " method="post">
                        @method('DELETE')
                        @csrf
                        <button class="btn btn-danger ml-2 mr-2">@lang('main.lblbtndel')</button>
                    </form>
                </spam></div>
            </div><br>
            @endforeach
        </div>
    </div>
</div>
@endsection

Configurando las rutas, en el archivo 'Routes\web.php'

Route::get('/', function () {
    return view('bienvenido');
});
Route::resource('posts', PostController::class);
Auth::routes(['verify' => true]);

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

Route::group(['middleware' => 'verified'], function(){
    Route::get('/post/index', [App\Http\Controllers\PostController::class, 'index'])->name('post/index');
    Route::get('/post/create', [App\Http\Controllers\PostController::class, 'create'])->name('post/create');
    Route::get('/post/show', [App\Http\Controllers\PostController::class, 'show'])->name('post/show');
    Route::get('/post/edit', [App\Http\Controllers\PostController::class, 'edit'])->name('post/edit');
    Route::get('/post/my', [App\Http\Controllers\PostController::class, 'my'])->name('post/my');
});

Observaciones:
  • Tal como está, la ruta 'http://127.0.0.1:8000/posts', nos permite ver las publicaciones, sin ingresar, ni registrarse, por medio del código resaltado en amarillo.
  • Si colocamos la linea de código 'Route::resource('posts', PostController::class);', dentro de 'Route::group(['middleware' => 'verified'], function(){}' , no es visible sin ingreso, y por ende sin registro y verificación.
  • Este es el medio por el cual, podemos hacer visibles las publicaciones a cualquier visitante, o restringirlas a usuarios registrados. Donde si la linea resaltada en amarillo, la colocamos fuera del código  resaltado en verde, permite el aceso a 'http://127.0.0.1:8000/posts', a cualquira. Y si colocamos la linea resaltada en amarillo dentro del código  resaltado en verde exige el registro y autenticación del usuario (autenticación que se realiza al verificar el correo).
  • Si queremos que el acceso a las publicaciones sea libre, dejamos el conrtolador 'Route::resource('posts', PostController::class);', tal como está, de lo contrario lo incluimos al inicio del código verde.
  • Para el acceso a las publicaciones de manera libre, en la vista bienvenido, y después de @endauth, colocamos el siguiente código <a href="/posts">@lang('main.lblposts')</a>, código que refiere al link de las publicaciones. Con el mismo propósito vamos a las plantillas de lenguaje y modificamos en español: 'subttl' => 'Por favor ingrese, registrese, o accede a las publicaciones según el caso.', y en inglés: 'subttl' => 'Please enter, register, or access the publications as appropriate.', con lo cual concluimos las modificaciones necesarias para  dar acceso libre a las publicaciones.
En el próximo articulo veremos la creación de semilleros o seeders.

Comentarios