SPA | VUEjs y Laravel

Proyecto completo SPA en VUEjs y Laravel

Desarrollaremos un pequeño proyecto que resuelva algunas dudas comunes, trabajaremos con VUEjs y Laravel para crear un single-page application (SPA). Es un tema muy solicitado: Aquí crearé dos áreas, un CRUD para crear lecciones y el área Frontend donde tendremos a nuestra SPA hecha en VUEjs.


¿Qué deberías hacer?...

Lo principal, es instalar el componente vue-router en nuestro proyecto. Lo logramos con el comando npm install vue-route --save-dev en la raiz del proyecto. Observa mi archivo package.json.

{
  "private": true,
  "scripts": {
    // ...
  },
  "devDependencies": {
    "axios": "^0.17",
    "bootstrap-sass": "^3.3.7",
    "cross-env": "^5.1",
    "jquery": "^3.2",
    "laravel-mix": "^1.0",
    "lodash": "^4.17.4",
    "vue": "^2.5.7",
    "vue-router": "^3.0.1"
  }
}

Tu archivo no necesariamente tiene que ser el mismo que el mío ... Solo asegúrate de tener en la lista vue-router, vue, jquery, bootstrap, etc.

Luego, de nuevo en la raíz, ejecuta npm para tener todas las bibliotecas necesarias en el sistema, debes ejecutar concretamente el comando npm install.

Lo que vamos a tratar en este post es:

  1. VUEjs 2.5
  2. Laravel 5.5
  3. Vue-enrutador 3.0
  4. Bootstrap 3.7
  5. ... Un poco de SASS y Laravel Mix

La unión de estas tecnologías nos dará como resultado un proyecto interesante, de hecho el repositorio es: https://github.com/rimorsoft/Laravel-Challenge

Realmente espero que este material sea muy útil para ti, Comencemos con el código y las breves explicaciones:

NOTA: vue-router es necesario para enlazar componentes a diferentes URLs, es decir, cada ruta será un componente. De hecho si esto es realmente nuevo para ti, por favor revisa este material https://rimorsoft.com/tutorial-sobre-vue-router

Backend

Creé un sistema para registrar, enumerar, eliminar, ver y editar lecciones. La idea es mostrar claramente cómo desde el proyecto podemos crear registros que luego podremos consumir desde VUEjs.

A continuación, la estructura de carpetas del backend:

  • Para el backend utilizamos el controlador backend/LessonController.php y el modelo Lesson.php
  • También tenemos api/LessonController.php, que será el controlador que usaremos para conectarnos desde VUEjs a nuestros datos
  • Y PageController.php será el archivo que iniciará nuestro SPA, desde allí comenzará a funcionar VUEjs
  • En el CRUD utilizamos dos archivos de requests para validar correctamente la información a guardar o editar

Esto es lo que se necesita para trabajar en el CRUD y para que VUEjs se conecte correctamente y haga uso de los datos. Es simple, quiero transmitir las bases necesarias para tocar cosas avanzadas en proyectos futuros.

Vamos con el código de cada archivo

Archivo Lesson.php

<?php

namespace App;
use Illuminate\Database\Eloquent\Model;

class Lesson extends Model
{
    protected $fillable = [
        'title', 'slug', 'body',
    ];
}

Archivo PageController.php

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;

class PageController extends Controller
{
    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
		 
    public function index()
    {
        return view('home');
    }
    
}

Archivo Backend/LessonController.php

<?php

namespace App\Http\Controllers\backend;
use App\Http\Requests\LessonStoreRequest;
use App\Http\Requests\LessonUpdateRequest;

use App\Http\Controllers\Controller;
use App\Lesson;

class LessonController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $lessons = Lesson::orderBy('id', 'DESC')->paginate(12);
        return view('backend.lessons.index', compact('lessons'));
    }
    
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('backend.lessons.create');
    }
    
    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(LessonStoreRequest $request)
    {
        $lesson = Lesson::create($request->all());
        return redirect()
                ->route('lessons.edit', $lesson->id)
                ->with('info', 'Saved successfully');
    }
    
    /**
     * Display the specified resource.
     *
     * @param  \App\Lesson  $lesson
     * @return \Illuminate\Http\Response
     */
    public function show(Lesson $lesson)
    {
        return view('backend.lessons.show', compact('lesson'));
    }
    
    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Lesson  $lesson
     * @return \Illuminate\Http\Response
     */
    public function edit(Lesson $lesson)
    {
        return view('backend.lessons.edit', compact('lesson'));
    }
    
    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Lesson  $lesson
     * @return \Illuminate\Http\Response
     */
    public function update(LessonUpdateRequest $request, Lesson $lesson)
    {
        $lesson->fill($request->all())->save();
        return redirect()
                ->route('lessons.edit', $lesson->id)
                ->with('info', 'Successfully updated');
    }
    
    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Lesson  $lesson
     * @return \Illuminate\Http\Response
     */
    public function destroy(Lesson $lesson)
    {
        $lesson->delete();
        return back()->with('info', 'Successfully removed');
    }
}

Archivo API/LessonController.php

<?php

namespace App\Http\Controllers\api;
use App\Http\Controllers\Controller;
use App\Lesson;

class LessonController extends Controller
{
    
    public function getLessons()
    {
    	$lessons = Lesson::get();
        return $lessons;
    }
    
    public function getLesson($slug)
    {
    	$lesson = Lesson::where('slug', $slug)->first();
    	return $lesson;
    }
    
}

Archivo LessonStoreRequest.php

<?php

namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;

class LessonStoreRequest 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',
            'slug'  => 'required|unique:lessons,slug',
            'body'  => 'required'
        ];
    }
}

Archivo LessonUpdateRequest.php

<?php

namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;

class LessonUpdateRequest 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',
            'slug'  => 'required|unique:lessons,slug,' . $this->lesson->id,
            'body'  => 'required'
        ];
    }
}

Estos serían los archivos necesarios dentro de app

A nivel de base de datos eliminé la migración de usuarios y contraseñas, tenemos en este momento un solo archivo llamado su_respectiva_fecha_create_lessons_table.php. También tenemos un archivo seed y un archivo factory.

Archivo LessonFactory.php

<?php

use Faker\Generator as Faker;

$factory->define(App\Lesson::class, function (Faker $faker) {
    $title = $faker->sentence(6);
    return [
        'title'  => $title,
        'slug'  => str_slug($title),
        'body'  => $faker->text(500),
    ];
    
});

Archivo su_respectiva_fecha_create_lessons_table.php

<?php

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

class CreateLessonsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('lessons', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title', 128);
            $table->string('slug', 128)->unique();
            $table->text('body');
            $table->timestamps();
        });
    }
    
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('lessons');
    }
}

Archivo DatabaseSeeder.php (solo lo configuramos, como ya sabes este archivo viene con la instalación de Laravel)

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $this->call(LessonsTableSeeder::class);
    }
}

Archivo LessonsTableSeeder.php

<?php

use Illuminate\Database\Seeder;

class LessonsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\Lesson::class, 60)->create();
    }
}

Todos estos archivos fueron creados con el comando php artisan make:model Lesson -a, el parametro -a nos ayuda con el controlador, modelo, migración, factory. Tuve luego que crear el controlador para el API y el archivo Seed

Trabajemos ahora con las vista del CRUD, recuerda... Continuamos con el Backend.

La carpeta backend tiene todo lo necesario para el CRUD, digamos toda la estructura HTML. Solo home.blade.php será el archivo donde funcionará el SPA.

Colocaremos a continuación el código de cada archivo.

Archivo backend/layouts/app.blade.php

<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

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

    <title>SPA - VUEjs & Laravel</title>

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-default navbar-static-top">
            <div class="container">
                <div class="navbar-header">

                    <!-- Collapsed Hamburger -->
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse" aria-expanded="false">
                        <span class="sr-only">Toggle Navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>

                    <!-- Branding Image -->
                    <a class="navbar-brand" href="{{ route('lessons.index') }}">SPA - VUEjs & Laravel</a>
                </div>

                <div class="collapse navbar-collapse" id="app-navbar-collapse">
                    <!-- Left Side Of Navbar -->
                    <ul class="nav navbar-nav">
                         
                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="{{ route('lessons.index') }}">Lessons</a></li>
                        <li><a href="{{ route('home') }}">Go to the SPA</a></li>
                    </ul>
                </div>
            </div>
        </nav>

        @if (session('info'))
            <div class="container">
                <div class="row">
                    <div class="col-md-8 col-md-offset-2">
                        <div class="alert alert-success">
                            {{ session('info') }}
                        </div>
                    </div>
                </div>
            </div>
        @endif

        @if(count($errors))            
            <div class="container">
                <div class="row">
                    <div class="col-md-8 col-md-offset-2">
                        <div class="alert alert-success">
                            <ul>
                                @foreach($errors->all() as $error)
                                <li>{{ $error }}</li>
                                @endforeach
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        @endif

        @yield('content')
    </div>

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

    @yield('scripts')
</body>
</html>

Archivo backend/lessons/create.blade.php

@extends('backend.layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Create lesson</div>

                <div class="panel-body">                    
                    
                    <form action="{{ route('lessons.store') }}" method="POST">
                        
                        {{ csrf_field() }}
                        
                        <div class="form-group">
                            <label for="title">Name *</label>
                            <input type="text" name="title" class="form-control" value="{{ old('title') }}">
                        </div>
                        <div class="form-group">
                            <label for="slug">Slug *</label>
                            <input type="text" name="slug" class="form-control" value="{{ old('slug') }}">
                        </div>
                        <div class="form-group">
                            <label for="body">Body *</label>
                            <textarea name="body" class="form-control">{{ old('body') }}</textarea>
                        </div>
                        <div class="form-group">
                            <input type="submit" value="Save" class="btn btn-sm btn-primary">
                        </div>
                    
                    </form>

                </div>
            </div>
        </div>
    </div>
</div>
@endsection

@section('scripts')
<script src="//cdn.ckeditor.com/4.9.2/basic/ckeditor.js"></script>
<script>
    $(document).ready(function(){       
        CKEDITOR.config.width  = 'auto';
        CKEDITOR.replace('body');
    });
</script>
@endsection

Archivo backend/lessons/edit.blade.php

@extends('backend.layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Edit lesson</div>

                <div class="panel-body">                    
                    
                    <form action="{{ route('lessons.update', $lesson->id) }}" method="POST">
                        
                        {{ csrf_field() }}
                        {{ method_field('PATCH') }}

                        <div class="form-group">
                            <label for="title">Name *</label>
                            <input type="text" name="title" class="form-control" value="{{ old('title', $lesson->title) }}">
                        </div>
                        <div class="form-group">
                            <label for="slug">Slug *</label>
                            <input type="text" name="slug" class="form-control" value="{{ old('slug', $lesson->slug) }}">
                        </div>
                        <div class="form-group">
                            <label for="body">Body *</label>
                            <textarea name="body" class="form-control">{{ old('body', $lesson->body) }}</textarea>
                        </div>
                        <div class="form-group">
                            <input type="submit" value="Update" class="btn btn-sm btn-primary">
                        </div>
                    
                    </form>

                </div>
            </div>
        </div>
    </div>
</div>
@endsection

@section('scripts')
<script src="//cdn.ckeditor.com/4.9.2/basic/ckeditor.js"></script>
<script>
    $(document).ready(function(){       
        CKEDITOR.config.width  = 'auto';
        CKEDITOR.replace('body');
    });
</script>
@endsection

Archivo backend/lessons/index.blade.php

@extends('backend.layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">
                    List of lessons 
                    <a href="{{ route('lessons.create') }}" class="pull-right btn btn-sm btn-primary">
                        Create lesson
                    </a>
                </div>

                <div class="panel-body">

                    <table class="table table-striped table-hover">
                        <thead>
                            <tr>
                                <th width="10px">ID</th>
                                <th>Name</th>
                                <th colspan="3"> </th>
                            </tr>
                        </thead>
                        <tbody>
                            @foreach($lessons as $lesson)
                            <tr>
                                <td>{{ $lesson->id }}</td>
                                <td>{{ $lesson->title }}</td>
                                <td width="10px">
                                    <a href="{{ route('lessons.show', $lesson->id) }}" class="btn btn-sm btn-default">See</a>
                                </td>
                                <td width="10px">
                                    <a href="{{ route('lessons.edit', $lesson->id) }}" class="btn btn-sm btn-default">Edit</a>
                                </td>
                                <td width="10px">
                                    <form action="{{ route('lessons.destroy', $lesson->id) }}" method="POST">
                        
                                        {{ csrf_field() }}
                                        {{ method_field('DELETE') }}

                                        <button class="btn btn-sm btn-danger delete">
                                            Remove
                                        </button> 
                                    
                                    </form>
                                </td>
                            </tr>
                            @endforeach
                        </tbody>   
                    </table>        

                    {{ $lessons->render() }}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

@section('scripts')
<script>
    $(document).ready(function(){       
        $(".delete").on("click", function(e) { 
            e.preventDefault(); 
            if (!confirm("Are you sure to delete?")) { 
                return false; 
            } 
            form = $(this).parent(); 
            $(form).submit();
        });
    });
</script>
@endsection

Archivo backend/lessons/show.blade.php

@extends('backend.layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">
                    Lesson
                    <a href="{{ url()->previous() }}" class="pull-right btn btn-sm btn-primary">Back</a>
                </div>

                <div class="panel-body">                    
                    <p><strong>Name</strong> {{ $lesson->title }}</p>
                    <p><strong>Slug</strong> {{ $lesson->slug }}</p>
                    <p><strong>Body</strong> {!! $lesson->body !!}</p>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Archivo home.blade.php

<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

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

    <title>SPA - VUEjs & Laravel</title>

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
	<div id="app">

		<nav-bar></nav-bar>
	
		<transition name="slide-fade" mode="out-in">
			<router-view :key="$route.fullPath"></router-view>
		</transition>

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

</body>
</html>

Todo esto no tiene mucha explicación porque entiendo que tienes una buena relación con laravel y solo buscas pequeños trucos para perfeccionar tu código. Hasta ahora ha sido carpintería, controlador (web y API), modelo, validaciones, archivos de base de datos y vistas ... Fácil, ¿verdad?

Archivo de rutas routes/web.php

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

// Backend
Route::resource('lessons', 'backend\LessonController');

// SPA
Route::get('/{any?}', 'PageController@index')->name('home');

Archivo de rutas routes/api.php

<?php

Route::get('lessons', 'api\LessonController@getLessons');
Route::get('lesson/{slug}', 'api\LessonController@getLesson');

Ten en cuenta que al tener rutas en el archivo api.php, el acceso a los datos debe ser incluyendo el prefijo api, por ejemplo: api/lessons si nuestra configuración es Route::get('lessons', 'api\LessonController@getLessons'); En otras palabras, necesitamos el prefijo api.

El gran Frontend con VUEjs

Esta es la parte interesante de este tutorial...

  • Para este proyecto, necesitamos un componente y dos vistas... (El componente del menú y las páginas lesson y 404).
  • Mis archivos son: routes.js, NavBar.vue, Lesson.vue, 404.vue y _custom.scss ... Se entiende claramente para qué sirve cada uno.

Vamos con el código... La verdad es muy simple, solo quiero transmitir las bases necesarias y este proyecto lo logra hacer bastante bien.

Archivo assets/js/app.js

require('./bootstrap');

window.Vue = require('vue');

import router from './routes'

Vue.component('nav-bar', require('./components/NavBar'));

const app = new Vue({
    el: '#app',
    router
});

Archivo assets/js/routes.js

import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

export default new Router({
    routes: [
        {
            path: ':slug',
            name: 'lesson',
            component: require('./views/Lesson'),
            props: true
        },
        {
            path: '*',
            component: require('./views/404')
        }
    ],
    linkExactActiveClass: 'active',
    mode: 'history',
    scrollBehavior(){
        return {x:0, y:0}
    }
});

Aquí está la magia, es nuestro archivo de rutas de frontend.

  • linkExactActiveClass: Aquí definimos la clase que se asigna cuando el botón está activo.
  • mode: Cuyo valor es la history, con esto eliminamos el famoso #! (Shebang, hash-bang o sharpbang) de nuestra URL
  • scrollBehavior: Cuando el contenido es muy extenso, necesitamos que al hacer clic en un botón, la página suba para leer el contenido de forma natural (de arriba hacia abajo)

Avancemos...

Archivo assets/js/components/NavBar.vue

<template>
    <aside class="layout-sidebar">
        <div class="topbar">
            <a href="#">Laravel Challenge</a>
        </div>
        <ul>
            <li v-for="lesson in lessons">
            	<router-link :to="{name: 'lesson', params: {slug: lesson.slug}}" v-text="lesson.title"></router-link>
            </li>
        </ul>
    </aside>
</template>

<script>
    export default {
        data(){
            return {
                lessons: [],
            }
        },
        mounted(){
            axios.get('api/lessons')
                .then(response => {
                    this.lessons = response.data
                })
        }
    }
</script>

Desde aquí, nos conectamos a la API para mostrar nuestro menú dinámicamente.

Archivo assets/js/views/404.vue

<template>
    <section class="layout-content">
        <div>
            <div class="col-xs-12 text-center">
                <h1>Study lessons</h1>
                <hr>
                <img src="img/php.png">
            </div>
        </div>
    </section>
</template>

Archivo assets/js/views/Lesson.vue

<template>
    <section class="layout-content">
        <div>
            <div class="col-xs-12">
                <h1 v-text="lesson.title"></h1>
                <hr>
                <div v-html="lesson.body"></div>
            </div>
        </div>
    </section>
</template>

<script>
    export default {
        props: ['slug'],
        data(){
            return {
                lesson: {}
            }
        },
        mounted(){
            axios.get(`api/lesson/${this.slug}`)
                .then(response => {
                    this.lesson = response.data;
                })
        }
    }
</script>

Diseño, Archivos SASS

Archivo assets/sass/_custom.scss

body{
    color: #29393d;
}

.layout-sidebar{
    width: 25%;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    overflow-y: scroll;
    background-color: #171a1c;
	ul {
	    list-style: none;
	    margin: 0;
	    padding: 0;
		li {
			a {
			    color: rgba(255, 255, 255, 0.6);
			    padding: 10px 15px 10px 25px;
			    display: block;
			    text-decoration: none;
			    font-weight: bold;
			}
			a.active {
			    color: #fff;
			    background-color: #3b4045;
			}
		}
	}
	.topbar{
	    background-color: #00aced;
	    padding-left: 25px;
	    margin-bottom: 15px;
	    height: 60px;
	    align-items: center;
	    display: flex;
		a{
		    font-weight: bold;
		    color: #fff;
		    font-size: 22px;
		    text-decoration: none;
		}
	}
}

.layout-content{
    padding-left: 25%;
}

.slide-fade-enter-active {
  transition: all .2s ease;
}
.slide-fade-leave-active {
  transition: all .2s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, 
.slide-fade-leave-to {
  transform: translateX(10px);
  opacity: 0;
}

Este proyecto no es responsivo en la parte frontend... Pero no creo que esto retrase o endurezca tu aprendizaje.

Archivo assets/sass/app.scss

// Fonts
@import url("https://fonts.googleapis.com/css?family=Raleway:300,400,600");

// Variables
@import "variables";
@import "custom";

// Bootstrap
@import "~bootstrap-sass/assets/stylesheets/bootstrap";

Con esto termino, si algo no entiendes por favor ve al repositorio y tendrás el código original.

Has visto cómo tener un CRUD y al mismo tiempo un SPA con VUEjs que consume datos sin problemas de Laravel.

Adquiere en preventa y aprovecha un gran descuento TDD en Laravel

Comparte en

Creado por: Venezuela Italo Morales

Profesor de #Laravel y #PHP en Rimorsoft Online

Más información


Valor antiguo y Valor Nuevo en la opción Seleccionar

quiero crear un form general para un registro de paciente de alli crear y editar pero en los select me sale que la varible no esta definidad. colocando

<div class="col-sm-6">
                            <label>Tipo de Documento</label>
                            <select class="form-control" name="PTipoDocumento" required>
                                <option>--...

Soy un programador y también un humano

Esto es una reflexión, la escribo porque es muy probable que te sirva y la consideres importante, quiero que este escrito te de confianza y confies de verdad en ti.

Constantes en PHP

Es muy común usar constantes en nuestro sistema, recuerdo que uso mucho la constante NUM_LATEST para que sirva como parámetro al momento de usar el método ->take(). Básicamente, trabajo con algo así ->take(NUM_LATEST) al consultar usuarios, artículos, comentarios, etc.

Una constante es una "variable" que no está pensada para cambiar, por eso no es variable, su valor no varía es constante. Es exacta su definición ¿cierto?.

Spatie Laravel Permissions con Vuejs

Buenas noches compañeros, quiero mostrar el rol que tiene cada usuario ,pero aun no logro hacerlo. estoy trabajando con Spatie Laravel Permissions, este paquete ya lo he trabajo anteriormente con Laravel. Ahora quiero implementarlo con Vuejs y Laravel. Agradezco la orientación que me puedan brindar.

Implementacion con Laravel
@foreach($user->roles as $role)
	<td>
	...