Creación de paquetes en Laravel
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.
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.
- En la app de Laravel que utiliza el paquete
resources/views/vendor/mi-paquete1
- 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
- Descargar el fichero recomendado php_xdebug-3.2.0-8.1-vs16-x86_64.dll
- Copiarlo a C:\xampp\php\ext, y renombrarlo a php_xdebug.dll
- 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
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