3

Symfony Framework con Inertia y Vue.js

Aplicando lo aprendido en el “Curso de Single Page Applications en Laravel con Inertia y Vue.js”, pero ahora veremos cómo podemos empezar a usar todo esto con Symfony y Vue.

Utilizaremos el adaptador para Symfony de Inertia que se recomienda en la página web de InertiaJS.

Crear proyecto, componentes y dependencias

Puedes ver más detalles de los cambios en https://github.com/AngelFQC/notes-inertia-demo/commit/066877ac3f8d0430fef509dbfb3ddc26503c64e5

Empezamos agregando las dependencias iniciales con composer y yarn.

$ symfony new notes-inertia --full
$ cd notes-inertia/
$ composer require encore
$ composer require rompetomp/inertia-bundle
$ yarn install
$ yarn add vue vue-loader@15.9.5 vue-template-compiler --dev
$ yarn add sass-loader sass --dev
$ yarn add @inertiajs/inertia @inertiajs/inertia-vue

Editamos webpack.config.js para habilitar lo necesario.

const Encore = require('@symfony/webpack-encore');
const path = require('path');

// Manually configure the runtime environment if not already configured yet by the "encore" command.// It's useful when you use tools that rely on webpack.config.js file.if (!Encore.isRuntimeEnvironmentConfigured()) {
    Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}

Encore
    // directory where compiled assets will be stored
    .setOutputPath('public/build/')
    // public path used by the web server to access the output path
    .setPublicPath('/build')

    .enableVueLoader()
    .addAliases({
        vue$: 'vue/dist/vue.runtime.esm.js',
        '@': path.resolve('assets/vue')
    })

    .addEntry('app', './assets/app.js')

    .enableStimulusBridge('./assets/controllers.json')

    .splitEntryChunks()

    .enableSingleRuntimeChunk()

    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())

    .configureBabel((config) => {
        config.plugins.push('@babel/plugin-proposal-class-properties');
    })

    // enables @babel/preset-env polyfills
    .configureBabelPresetEnv((config) => {
        config.useBuiltIns = 'usage';
        config.corejs = 3;
    })

    // enables Sass/SCSS support
    .enableSassLoader()
;

module.exports = Encore.getWebpackConfig();

Editamos el archivo template en templates/base.html.twig

<!DOCTYPE html><html><head><metacharset="UTF-8"><title>{% block title %}Welcome!{% endblock %}</title>{% block stylesheets %}{{ encore_entry_link_tags('app') }}{% endblock %}</head><body>{{ inertia(page) }}{% block javascripts %}{{ encore_entry_script_tags('app') }}{% endblock %}</body></html>

Editamos assets/app.js

import'./styles/app.css';

import Vue from'vue'import { createInertiaApp } from'@inertiajs/inertia-vue'

createInertiaApp({
    resolve: name => require(`./vue/Pages/${name}`),
    setup({ el, app, props }) {
        new Vue({
            render: h => h(app, props),
        }).$mount(el)
    },
})

Configurar una base de datos para el proyecto.

Puedes ver más detalles de los cambios en https://github.com/AngelFQC/notes-inertia-demo/commit/7996c9d571bdc158a767a159cd5cee0cb95802ca

Creamos credenciales de base de datos

GRANT ALL PRIVILEGESON inertianotes.* TO inertianotes@localhost IDENTIFIEDBY'inertianotes';
FLUSHPRIVILEGES;

Editamos .env y agregar la línea de conexión

DATABASE_URL="mysql://inertianotes:[email protected]:3306/inertianotes?serverVersion=mariadb-10.5.10"

Creamos una base de datos con Doctrine

$ php bin/consoledoctrine:database:create

Crear controladores y vistas

Puedes ver más detalles de los cambios en https://github.com/AngelFQC/notes-inertia-demo/commit/98481506854e39ab90ed092b03c706eaf334a0d4

Controladores de Symfony con la opción --no-template para que no nos cree el archhivo template de Twig.

$ php bin/console make:controller IndexController --no-template
$ php bin/console make:controller PageController --no-template
$ php bin/console make:controller NoteController --no-template

Symfony asume que vamos a usar Twig para renderizar nuestros templates.
Pero aquí vamos a necesitar renderizar con Inertia y el inertia-bundle que estamos usando.
Por lo que utilizaremos una clase base para los controladores que se encargue que llamar a Inertia.

Creamos BaseController inicializando una instancia de InertiaInterface que utilizaremos
en los contradoles que hereden de esta clase.

<?phpnamespaceApp\Controller;

useRompetomp\InertiaBundle\Service\InertiaInterface;
useSymfony\Bundle\FrameworkBundle\Controller\AbstractController;

abstractclassBaseControllerextendsAbstractController{
    protected InertiaInterface $inertia;

    publicfunction__construct(InertiaInterface $inertia){
        $this->inertia = $inertia;
    }
}

IndexController y un método index que retorna una respuesta de InertiaInterface::render.

<?phpnamespaceApp\Controller;

useSymfony\Component\HttpFoundation\Response;
useSymfony\Component\Routing\Annotation\Route;

classIndexControllerextendsBaseController{
    #[Route('/', name: 'index')]publicfunctionindex(): Response{
        return $this->inertia->render('Index');
    }
}

Componente Vue para la página Index en assets/vue/Pages/Index.vue

<template><div>Index</div></template><script>exportdefault {
  name: "Index"
}
</script>

Módulo de notas

Puedes ver más detalles de los cambios en https://github.com/AngelFQC/notes-inertia-demo/commit/4522b9046f3c487daa8093fe3495ca7a3ed88e52

Creamos una entidad Note que almacenará las notas.
Esta entidad tendrá un contenido (content) y un extracto (excerpt).

$ php bin/consolemake:entity Note
$ php bin/console make:migration
$ php bin/console doctrine:migrations:migrate

Lista de notas

Modificamos el controlador NoteController para poder listar las notas.

 namespace App\Controller;
 
 use App\Entity\Note;
+use App\Repository\NoteRepository;+use Rompetomp\InertiaBundle\Service\InertiaInterface;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Annotation\Route;
 
 #[Route('/note')]
 class NoteController extends BaseController
 {
+    private NoteRepository $noteRepository;++    public function __construct(InertiaInterface $inertia, NoteRepository $noteRepository)+    {+        parent::__construct($inertia);++        $this->noteRepository = $noteRepository;+    }+
     #[Route('/', name: 'note')]
     public function index(): Response
     {
-        $noteRepo = $this->getDoctrine()->getRepository(Note::class);--        $notes = $noteRepo->findAll();+        $notes = $this->noteRepository->findAll();
 
         return $this->inertia->render(

Modificamos el componente Vue en Note/Index para permitir renderizar la lista de notas.

-  <div>Note/Index</div>+  <app-layout>+    <template #header>+      <h1>Módulo de notas</h1>+    </template>++    <h3>Listado de notas</h3>++    <p>Toma el registro correcto y ejecuta cualquier función (ver, editar o eliminar)</p>++    <table>+      <thead>+      <tr>+        <th>Extracto</th>+      </tr>+      </thead>+      <tbody>+      <tr v-for="note in notes">+        <td>{{ note.excerpt }}</td>+      </tr>+      </tbody>+    </table>+  </app-layout>
 </template>
 
 <script>
+import AppLayout from "../../Layout/AppLayout";+
 export default {
-  name: "Index"+  name: "Index",+  components: {AppLayout},+  props: {+    notes: Array,+  }
 }

Y con esto podemos entrar a la ruta https://localhost:8000/note
y ver la página con el componente Vue renderizado.

$ yarn encore dev
$ symfony server:start -d
Lista de notas

Utilizando rutas

Puedes ver más detalles de los cambios en https://github.com/AngelFQC/notes-inertia-demo/commit/502c4b166e92d2946234341786e4a7a5ed72bd89

Necesitaremos una forma para tener en JS las rutas definidas en los controladores con PHP.
Entonces agregamos FOSJsRoutingBundle como dependencia con Composer
y seguir las instrucciones de instalación según documentación en
https://symfony.com/doc/current/bundles/FOSJsRoutingBundle/installation.html.

$ composer require friendsofsymfony/jsrouting-bundle

Editamos controllers para exponerlos al router:

IndexController

     public function index(): Response
     {
-        return $this->inertia->render('Index');+        return $this->redirectToRoute('dashboard');
     }
 }

NoteController

 use Symfony\Component\Routing\Annotation\Route;
 
-#[Route('/note')]+#[Route('/note', options: ['expose' => true])]
 class NoteController extends BaseController
 {
     private NoteRepository $noteRepository;

PageController

 class PageController extends BaseController
 {
-    #[Route('/dashboard', name: 'dashboard')]+    #[Route('/dashboard', name: 'dashboard', options: ['expose' => true])]
     public function dashboard(): Response
     {
         return $this->inertia->render('Dashboard');

Luego, generamos el archivo fos_js_router.json que contendrá con la configuración con las rutas pero en JS.
Por defecto lo genera en una carpeta /web pero ahora lo necesitamos dentro de /public/js.

$ php bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json

También necesitaremos que se cargue el router y las rutas de fosjsrouting.
Modificamos el template base.html.twig.

         {% endblock %}
 
+        <script src="{{ asset('bundles/fosjsrouting/js/router.min.js') }}"></script>+        <script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"></script>+        <script>+            // the world's tiniest adapter to handle tighten/ziggy's 'route' function calls with FOSJSRoutingBundle+            window.route = function(name, params) {+                if (typeof(params) === 'number') {+                    params = { 'id': params }+                }+                return Routing.generate(name, params)+            }+        </script>
     </head>

Aquí, la función route trata de imitar un poco a la misma función que se usa en Laravel para generar rutas con Vue.
Entonces para que podamos usarla dentro de componentes Vue, la agregar como mixin.

 import { createInertiaApp } from '@inertiajs/inertia-vue'
 
+Vue.mixin({ methods: { route: window.route } })+
 createInertiaApp({

Por último, necesitamos agregar los enlaces a modo de menú de navegación para que se carguen con Inertia.
Utilizaremos <inertia-link>.

AppLayout.vue

   <div>
+    <nav>+      <ul>+        <li>+          <inertia-link :href="route('dashboard')">Dashboard</inertia-link>+        </li>+        <li>+          <inertia-link :href="route('note')">Notas</inertia-link>+        </li>+      </ul>+    </nav>
     <header>

Dashboard.vue

 <template>
-  <div>-    Dashboard-  </div>+  <app-layout>+    <template #header>+      Dashboard+    </template>+++  </app-layout>
 </template>
 
 <script>
+import AppLayout from "../Layout/AppLayout";+
 export default {
-  name: "Dashboard"+  name: "Dashboard",+  components: {AppLayout}
 }

Resultado final

Pantalla final
Escribe tu comentario
+ 2