Creación de paquetes en Laravel

laravel phpunit orchestra testbench

La gran ventaja de desarrollar un paquete es la de poder utilizar el mismo código en muchos proyectos distintos. Para ellos se crean los paquetes los cuales son importados en el proyecto deseado.

En este ejemplo se muestra como crear un paquete básico pero con la estructura y ventajas de Laravel.

Índice
  1. Estructura de paquetes
  2. Iniciar paquete con Composer
  3. Instalar emulador de laravel Orchestra Testbench
  4. Creación de ficheros para estructura en Laravel
  5. Testear paquetes con TTD

Estructura de paquetes

Lo ideal es crear una carpeta general donde se desarrollan los paquetes, y poder ser referenciados.

Destacar que un paquete lleva el nombre del Vendor o desarrollador, seguido de el nombre del paquete.

c:\workspace\packcages

Dentro de la carpeta de packcages crear una carpeta por Vendor

c:\workspace\packcages\vendor1

c:\workspace\packcages\vendor2

Y dentro de cada Vendor crear una carpeta por paquete con el nombre del mismo. Preferible separado por guión medio si son más de una palabra.

c:\workspace\packcages\vendor1\mi-paquete1
c:\workspace\packcages\vendor1\mi-paquete2

c:\workspace\packcages\vendor2\mi-paquete1
c:\workspace\packcages\vendor2\mi-paquete2

Iniciar paquete con Composer

Partiendo de que ya se tiene instalado Composer, ejecutar dentro del paquete el comando composer init para que se cree el fichero composer.json necesario para poder luego importar el paquete en los distintos proyectos

>.\vendor1\mi-paquete1\composer init

Hace varias preguntas para configurar el paquete, pero la principal es indicar el Vendor y nombre de paquete.

vendor <mi-paquete1>

El resto son opcionales porque se pueden añadir posteriormente, como veremos en cada caso.

Un ejemplo del fichero composer.json puede ser el siguiente, donde se indican las propiedades y requerimientos del paquete.

Autocargado de clases

Para cargar las clases de forma dinámica se utiliza la propiedad "autoload" indicando el estándar PSR-4 (Php Standar Recomentations version 4), indispensable para la carga de paquetes. Este estandar se basa en namespaces, por lo que cada fichero debe llevar su namespace, por ejemplo este controlador.

namespace Vendor1\MiPaquete1\Http\Controllers;

Así indicarle al autoload el vendor y paquete, seguido de la carpeta fuente que se autocargará de forma recursiva.


"autoload": {
        "psr-4": {
            "Vendor1\\MiPaquete1\\": "src/"
        }
    },

Un resumen de ejemplo seria el siguiente:

{
    "name": "vendor1/mi-paquete1",
    "description": "descripcion",
    "type": "library",
    "license": "MIT",
    "autoload": {
        "psr-4": {
            "Vendor1\\MiPaquete1\\": "src/"
        }
    },
    "minimum-stability": "dev",
    "require-dev": {
        "orchestra/testbench": "8.x-dev"
    },
    "require": {
        "vendorexterno/paquete-externo": "*"
    },
    "extra": {
        "laravel": {
            "providers": [
                "Vendor1\\MiPaquete1\\Providers\\RouteServiceProvider",
                "Vendor1\\MiPaquete1\\Providers\\MiPaquete1ServiceProvider"

            ],
            "aliases": {
                "ShortLinks": "Vendor1\\MiPaquete1\\Facades\\ReportsExcel"
            }
        }
    }
}

Refrescar cambios en composer.json

Cada vez que haya cambios en el paquete se puede publicar generando el autoload.php con el comando dump

>composer dump
vendor\autoload.php

Y en el proyecto donde se usa el paquete ejecutar el update general o bien de paquete y su versión. Si se desconoce la versión utilizar el asterisco *.

>composer update

>composer update

>composer update vendor1/mi-paquete1:*

Instalar emulador de laravel Orchestra Testbench

Ya que el paquete no es propiamente un proyecto de Laravel, podemos simular que tenemos crado uno gracias a la librería orchestra/testbench. Esté módulo facilita el desarrollo de paquetes y avisos de errores en desarrollo.

Instalar ejecutando el comando composer require

>composer require --dev "orchestra/testbench"

Este comando añade al fichero composer.json un require-dev para poder ser utilizado

...
"require-dev": {
        "orchestra/testbench": "8.x-dev"
},
...

Creación de ficheros para estructura en Laravel

Es conveniente seguir la estrucutra de Laravel para hacer totalmente compatible el paquete desarrollado, con el proyecto en el que se va a utilizar el mismo.

Dentro de la carpeta .\src va la estructura de carpetas y ficheros conrrespondiente a la carpeta .\app de un proyecto de Laravel.

Crear Service Providers y Facades

Crear fichero en formato Camel Case, es decir, cada palabra comienza en mayúsculas y todo junto sin guiones. Debe finalizar con la palabra Proveder y extensión php.

src\Providers\MiPaquete1Provider.php

Extender a la clase ServiceProvider de Laravel y crear los metodos boot y registrer.

  • Método boot: para cargar los Services Providers antes de utilizar la aplicación.
  • Método register: para registar binds. Ejemplo: $this->app->bind('mi-paquete1', function(){return new MiClass;});
<?php

namespace Vendor1\MiPaquete1\Providers;

use Illuminate\Support\ServiceProvider;

class MiPaquete1ServiceProvider extends ServiceProvider{

    public function boot(){
        $this->loadViewsFrom(__DIR__ . '/../../resources/views', 'mi-paquete1');
    }

    public function register(){
 
    }
}

Crear el facades

src\Facades\MiPaquete1.php
<?php

namespace Vendor1\MiPaquete1\Facades;

use Illuminate\Support\Facades\Facade;

class ReportsExcel extends Facade{

    protected static function getFacadeAccessor()
    {
        return 'mi-paquete';
    }
}

Registrar el Providers y facades

En el fichero composer.json incluirlos en providers y aliases respectivamente.


"extra": {
        "laravel": {
            "providers": [
                "Vendor1\\MiPaquete1\\Providers\\RouteServiceProvider",
                "Vendor1\\MiPaquete1\\Providers\\MiPaquete1ServiceProvider"

            ],
            "aliases": {
                "ShortLinks": "Vendor1\\MiPaquete1\\Facades\\ReportsExcel"
            }
        }
    }

Crear y agregar rutas

Crear carpeta routes en el raíz y dentro los ficheros de rutas, por ejemplo el web.php

>routes\web.php

Cargar el fichero creado de rutas en el service provider

>src\Providers\RouteServiceProvider.php
<?php

namespace Vendor1\MiPaquete1\Providers;

use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;

class RouteServiceProvider extends ServiceProvider{

    protected $namespace = 'Vendor1\MiPaquete1\Http\Controllers';

    public function map(){
        Route::namespace($this->namespace)->group(__DIR__ . '/../../routes/web.php');
    }
}

Crear y agregar controladores

Crear carpeta controllers siguiendo la estructura del framework Laravel

>src\Http\Controllers\MyController.php

Los controladores se cargan automáticamente porque se han incluido previamente en el src\Providers\RouteServiceProvider.php

Crear y agregar vistas

En el service provider debe ir la carga dinámica de clases, indicado en su método boot.

>src\Providers\MiProyectoServiceProvider.php
public function boot(){
 $this->loadViewsFrom(__DIR__ . '/../../resources/views', 'mi-proyecto1');
}

Indicar que __DIR__ hace referencia al directorio actual donde se llama.
Pasar también el namespace ( 'mi-proyecto1' ) para hacer referencia a la vista del paquete, y no a la del proyecto donde se use. Así se evita duplicidades en los nombres.

Crear la carpeta resources y subcarpeta views para seguir con la estructura de carpetas de Laravel. Cada fichero de vista lleva la extensión blade.php. En este ejemplo se llama vista1 y el contenido puede ser html o bien hacer referncia a algún layout.

resources\views\vista1.blade.php

Para hacer referencia a la vista, por ejemplo desde el controlador, se pone el namespace seguido de dos puntos y el nombre de la vista, mi-proyecto1::vista1.

class MiController extends Controller{
    
    public function export(){
        return view('mi-proyecto1::vista1');
    }

}

Sobreescribir vistas

No se debe sobreescribir el fichero proporcionado en la carpeta vendor, ya que con cualquier actualización se perderían los cambios.

Para ello hay que permitir publicar las vistas con el método publishes dentro de la función boot.

resource_path indica la carpeta resources.
'mi-paquete1-views' indica el alias de las vistas. Se usará como preferencia amigable para publicarlas.

public function boot(){

 $this->publishes([
  __DIR__ . '/../../resources/views' => resource_path('views/vendor/reports-excel'),
], 'mi-paquete1-views);

En realidad Laravel utiliza dos rutas para buscar las vistas.

  1. En la app de Laravel que utiliza el paquete
    resources/views/vendor/mi-paquete1
  2. En el paquete donde se ha indicado la ruta en el service provider
    $this->loadViewsFrom(DIR . '/../../resources/views', 'mi-paquete1');

Si se siguen las convenciones, Laravel podrá encontrarlas sin problemas.

Comandos para publicar vistas

Listar comandos que se pueden utilizar vendor:pubish con el parámetro opcional --force para sobreescribir los ficheros publicados.

>php artisan vendor:publish --force

Del listado tomar nota del número que se desea publicar

...
Provider: Vieweesp\ReportsExcel\Providers\MiPaquete1ServiceProvider ……. 13
...
Tag: report-excel-views ..................................................... 34 
...

y teclearlo después. Esto crea la vista en el proyecto para poder sobreescribirla.

Crear y agregar ficheros de configuración

Crear la carpeta config en el raíz y el fichero, que normalente se llama igual que el paquete

config\mi-paquete1.php

y contenido que devuelve como array

<?php

return[
    'route' => 'hello-route'
];

Cargarlo en el provider, en el método register, para que esté disponible a la hora de cargar los paquetes (metodo boot)

public function register(){

 $this->mergeConfigFrom(__DIR__ . '/../../config/mi-paquete1.php', 'reports-excel')
}

Se puede llamar a esta configuración llamando con el método config(), y como parámetro el paquete, un punto, y el nombre del campo de la configuración.

Route::get(config('mi-paquete1.route'),[MyController::class, 'index']);

Publicar fichero de configuración

Publicar también utilizando base_path, que indica el raíz del proyecto, y un alias

public function boot(){
 ...

$this->publishes([
  __DIR__ . '/../../config/mi-paquete1.php' => base_path('config/mi-paquete1.php'),
],'mi-paquete1-config');
}

Listar comandos que se pueden utilizar, y elegir el deseado.

>php artisan vendor:publish
...
Tag: mi-paquete1-config .................................................... 34  
...

Para optimizar código se puede reemplazar el __DIR__ por un método que devuelva el path, pasando por parámetro la ruta a montar.

/** craer path agregando la base */
protected function basePath($path = ''){
 return __DIR__ . '/../../' . $path;
}

Crear y agregar migraciones

Crear la carpeta database y dentro otra subcarpeta migrations.

>database\migrations

Crear fichero de migración con las convenciones, que son nombre de fichero con la fecha y palabras reservadas y separadas por guiones bajos.

  • Año: 2023
  • Mes: 01
  • Día: 12
  • Hora: 00
  • Minuto: 24
  • Segundo: 53
  • Prefijo: create
  • Nombre tabla: mitabla
  • Sufijo: table
  • Extensión: .php

>2023_01_12_002453_create_mitabla_table.php

Cargarlos en el método boot del service provider del paquete

$this->loadMigrationsFrom(
 $this->basePath('database/migrations')
);

Publicar migraciones

Agregar al service provider y con database_path

$this->publishes([
 $this->basePath('database/migrations') => database_path('migrations'),
],'report-excel-migrations');

Crear y agregar modelos

Crear la carpeta database y dentro otra subcarpeta migrations.

Crear fichero con convenciones en CamelCase y singular.

>app\Models\Mitabla.php

Con la migración y el modelo creado y configurado, ya se puede ejecutar en el proyecto la migración.

>php artisan migrate

Crear y agregar traducciones

Crear la carpeta lang dentro de resources, y dentro otras subcarpetas para cada idioma

>lang\es
>lang\en

En el caso de crear traducciones con json

>lang\json\es.json
>lang\json\en.json

Cargar y publicar traducciones

Incluir en el service provider la carga y la publicación en vendor, para diferenciar el paquete del proyecto.

$this->loadTranslationsFrom(
  $this->basePath('lang'),
  'reports-excel'
);

$this->publishes([
  $this->basePath('lang') => $this->basePath('lang/vendor/reports-excel'),
],'report-excel-translations');

$this->loadJsonTranslationsFrom(
  $this->basePath('lang/json')
);

En la vista se utiliza para traducción convenciona el método trans(), con el namespace de proyecto seguido de doble puntos, el fichero de traducción y la propiedad a traducir.

{{ trans('mi-paquete1::message.welcome')}}
Traducciones JSON

Si se trata de JSON, utilizar los guiones bajos y entre paréntesis la propiedad a traducir

{{ __('welcome') }}

Además, hay que copiar manualmente el contenido al fichero json del proyecto, bien al final para tenerlo controlado. De lo contrario no tomará las traducciones.

Crear y agregar assests css y jscript

Assets son los ficheros css y javascript ya compilados, y el objetivo es que se puedan utilizar en la parte publica, es decir, dentro de la carpeta public de Laravel.

Por una mejor estructura, se crean los ficheros dentro de la carpeta resources y se publican en la carpeta public.

Por tanto crear las distintas carpetas y ficheros dentro de cada una de ellas

resources\static\css\mi-paquete1.css
resources\static\js\mi-paquete1.js

Publicar assets

En el service provider utilizar origen el basePath y destino el public_path

$this->publishes([
 $this->basePath('resources/static') => public_path('vendor/mi-paquete1'),
], 'mi-paquete1-static-assets');

Detallar proceso para utilizar los assets

En la vista donde se muestra o en su layout, poner el link de los assets para que funcione

<link rel="stylesheet" href="/vendor/mi-paquete1/css/mi-paquete1.css">

<script src="/vendor/mi-paquete1/js/mi-paquete1.js">

Testear paquetes con TTD

El significado de TDD es Test Driven Development o Desarrollo Guiado por Pruebas.

Se suele usar para probar el código en un entorno de desarrollo y sin entorno gráfico

Estructura para Tests

Crear una carpeta en el raíz llamada tests y utilizar namespaces en los ficheros para evitar colisiones de nombres.

Indicar que se va a cargar la carpeta test pero solamente para desarrollo. Para ello crear la llave llamada autoload y de esta forma se diferencia lo necesario en producción y que no.

>mkdir .\tests
"autoload-dev": {
        "psr-4": {
            "Vendor1\\MiPaquete1\\Tests\\": "tests/"
        }
    },

"require-dev": {
        "orchestra/testbench": "8.x-dev"
    },

Así en producción no hace falta instalar estas dependencia indicando el parámetro --no-dev

>composer install --no-dev

Generar entonces el autoloader con dump

>composer dump

Crear un test

Crear un fichero dentro de la carpeta tests, y jerarquizada conforme a nuestro paquete. Todos los ficheros se leerán dinamicamente, y finalizar con la palabra Test para que phpunit lo detecte.

Para utilizar el test es muy aconsejable craear un TestCase configurado para hacer uso de él en cualquier test.

tests\TestCase.php

Esta clase extenderá de \Orchestra\Testbench\TestCase. Cargar los providers y facaces con aliases.

<?php

namespace Vendor1\\MiPaquete1\Tests;

use Vendor1\\MiPaquete1\Providers\MiPaquete1ServiceProvider;
use Vendor1\\MiPaquete1\Providers\RouteServiceProvider;
use Vendor1\\MiPaquete1\Facades\MiPaquete1;


class TestCase extends \Orchestra\Testbench\TestCase {

        //cargar providers
    protected function getPackageProviders($app){
        return [
            MiPaquete1ServiceProvider::class,
            RouteServiceProvider::class
        ];
    }

    //cargar facades
    protected function getPackageAliases($app){
        return [
            'MiPaquete1' => MiPaquete1::class
        ];
    }

}

Phpunit para ejecutar los tests

Copiar el fichero phpunit.xml de un proyecto de Laravel al raíz del paquete.

>phpunit.xml

Modificar el sufijo donde pone app por src.

<coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./src</directory>
        </include>
    </coverage>

Crear directorios de estructura según indica el phpunit.xml

>tests\Unit
>tests\Feature
<testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
<testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>

Crear fichero para test de prueba

En la carpeta Features se puede ir creando ficheros de test, en los que irán las distintas fucniones a testear.

Por ejemplo un test para ver si exite la ruta

tests\Feature\CanGetUrlTest.php

Indicar que es un testo poniendo encima de la función la identificación /** @test */

<?php

namespace Vendor1\\MiPaquete1\Tests\Feature;

use Vendor1\\MiPaquete1\Tests\TestCase;

class CanGetUrlTest extends Testcase{

    /** @test */
    function can_get_url_prueba(){

        $this->withoutExceptionHandling();

        $this->get('prueba')->assertSuccessful();
    }

}

Deshabilitar el manejo de excepciones

Poner el siguiente método dentro de la función que realiza el test.

$this->withoutExceptionHandling();

Ejecutar el test con phpunit

en línea de comandos llamar al phpunit seguido del fichero del test.

>.\vendor\bin\phpunit .\tests\Feature\CanGetUrlTest.php

Code Coverage o cobertura de código

El code coverage es básicamente conocer el porcentaje de código que está cubierto o supervisado con tests.

Habilitar XDebug

Para utilizarlo hay que instalar la extensión XDebug en la configuración de php. Ejecutando el comando php -v muestra si se tiene instalado.

Si no aparece se puede instalar desde la página web xdebug.org siguiendo las instrucciones de instalación, que básicamente son dos tareas:

Conociendo previamente lo necesario en la propia página, pegando el código que se obtiene de
>php -i > phpinfo.txt

Conocer lo que hay que descargar de Wizard page

Ejemplo para Windows y xamp conphp 8.1.2

  1. Descargar el fichero recomendado php_xdebug-3.2.0-8.1-vs16-x86_64.dll
  2. Copiarlo a C:\xampp\php\ext, y renombrarlo a php_xdebug.dll
  3. Actualizar el C:\xampp\php\php.ini añadiendo la línea donde esté el ;zend_extension
    zend_extension = xdebug

Reiniciar el servicio de xamp y comprobar que está instalado

>php -i

xdebug installed php8

Si da errores de instalación, hay que seguir lo pasos que indique la consola de información, pero suele ser fácil solucionarlo.

Conocer porcentaje cubierto

Ejecutar el comando phpunit con parametro -h para conocer la ayuda

.\vendor\bin\phpunit -h

De la lista, ejecutar el parámetro --coverage-text

.\vendor\bin\phpunit --coverage-text

Generar un reporte html, indicar el fichero la ruta donde guardar la salida del informe

.\vendor\bin\phpunit --coverage-html tests\coverage

Configurar phpunit para reporte automático

Incluir en el fichero de configuración phpunit.xml las indicaciones para tipo de informe y el fichero para guardar el informe

<logging>
 <log type="coverage-text" target="tests/coverage.txt"/>
</logging>

Además si se tiene repositorio, no olvidar ignorar la carpeta coverage

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir