Creando paquetes para PHP

La creaci贸n de paquetes es una de las mejores formas para volver a aprovechar c贸digo y emplearlo en m谩s de una app o proyecto. Tambi茅n hace que las apps sean m谩s mantenibles. Si arreglamos un bug en nuestro paquete, con composer update actualizamos las apps que lo tengan como dependencia.

Creemos un paquete de ejemplo para revisar el proceso que conlleva la creaci贸n y publicaci贸n de paquetes PHP. Su funcionalidad ser谩 realizar conversiones de unidades de temperatura entre Fahrenheit y Celsius.


Plantilla "package-skeleton-php"

Spatie tiene una plantilla llamada "package-skeleton-php" que nos ofrece un buen punto de inicio. Cuenta con Composer configurado y algunas acciones de Github para ejecutar tests autom谩ticamente y realizar revisiones de estilo en nuestro c贸digo base.

El repositorio Github de "package-skeleton-php" cuenta con un bot贸n para comenzar a utilizar la plantilla.

Utilizar plantilla

En la p谩gina de creaci贸n de repositorio, elegimos el propietario del repositorio, un nombre y una descripci贸n. Finalmente decidimos si la visibilidad del proyecto ser谩 p煤blica o privada. Por el momento elegimos privada.

Crear repositorio

Clonamos el repositorio reci茅n creado con el comando git clone:

1git clone git@github.com:setdeu/fahrenheit-celsius-conversions.git

La plantilla nos ofrece un script de configuraci贸n que realiza los ajustes necesarios para asignar un autor, proveedor y nombre al paquete:

1php ./configure.php

Nos pregunta el nombre, email y usuario de Github del autor. Un nombre y un namespace para el proveedor (vendor). Nombre del paquete, un nombre de la clase para el paquete y descripci贸n.

Configurar plantilla

Revisemos qu茅 archivos tenemos en nuestro repositorio a partir de la plantilla ya configurada.

  • .github/ISSUE_TEMPLATE 鈥 La plantilla que Github emplear谩 cuando alguien cree una issue.
  • .github/workflows/dependabot-auto-merge.yml 鈥 Una acci贸n de Github para hacer un merge autom谩ticamente cuando el bot dependabot encuentre problemas de seguridad en nuestras dependencias npm.
  • .github/workflows/php-cs-fixes.yml 鈥 Una acci贸n de Github para ejecutar una revisi贸n de estilos al c贸digo base.
  • .github/workflows/run-tests.yml 鈥 Una acci贸n de Github para ejecutar la suite de tests.
  • .github/workflows/update-changelog.yml 鈥 Una acci贸n de Github para mantener actualizado nuestro archivo CHANGELOG.md en cada versi贸n lanzada.
  • .github/workflows/dependabot.yml 鈥 Configuraci贸n del bot dependabot.
  • .github/FUNDING.yml 鈥 Configuraci贸n de Github para aceptar donaciones.
  • src/FahrenheitCelsiusConversionsClass.php 鈥 La clase PHP generada por la plantilla al inicializar nuestro paquete.
  • tests/ExampleTest.php 鈥 Un test de ejemplo para la suite de tests.
  • tests/Pest.php 鈥 Configuraci贸n para el framework de tests Pest.
  • .editorconfig 鈥 Configuraci贸n para los editores de texto que abran el proyecto.
  • .gitattributes 鈥 Configuraci贸n de Git para ignorar archivos que genera, por ejemplo, la ejecuci贸n de la suite de tests.
  • .gitignore 鈥 Configuraci贸n de Git para ignorar archivos de sistema comunes.
  • .php-cs-fixer.dist.php 鈥 Configuraci贸n para que haya un est谩ndar de estilo en el c贸digo base.
  • .CHANGELOG.md 鈥 Informaci贸n de los cambios que habr谩 en nuevas versiones del paquete.
  • composer.json 鈥 Configuraci贸n Composer con detalles del paquete.
  • LICENSE.md 鈥 Detalles de la licencia que tiene nuestro paquete. Inicialmente una licencia MIT.
  • phpunit.xml.dist 鈥 Configuraci贸n de PHPUnit.
  • README.md 鈥 Detalles que deber铆an leerse sobre nuestro paquete e instrucciones de uso.

Convirtiendo Celsius a Fahrenheit

El paquete de ejemplo que estamos creando servir谩 para convertir unidades Celsius a Fahrenheit. Comencemos a a帽adir la l贸gica necesaria para la conversi贸n.

Renombrar茅 el archivo src/FahrenheitCelsiusConversionsClass.php a src/Temperature.php para la clase Temperature. El construct de esta clase aceptar谩 $celsius. Tendr谩 un m茅todo toFahrenheit para hacer la conversi贸n a grados Fahrenheit usando la f贸rmula F = (C * (9/5)) + 32. Y un m茅todo est谩tico celsius para aceptar grados celsius e inicializar la clase:

1<?php
2 
3namespace Setdeu\FahrenheitCelsiusConversions;
4 
5class Temperature
6{
7 protected float $celsius;
8 
9 public function __construct(float $celsius)
10 {
11 $this->celsius = $celsius;
12 }
13 
14 public function toFahrenheit(): float
15 {
16 return ($this->celsius * (9/5)) + 32;
17 }
18 
19 public static function celsius(float $celsius): self
20 {
21 return new static($celsius);
22 }
23}

Generamos un test para asegurarnos de que la conversi贸n sea correcta. Renombrar茅 el archivo tests/ExampleTest.php a src/TemperatureTest.php con el siguiente test de conversi贸n de 100 grados celsius a 212 grados Fahrenheit:

1<?php
2 
3use Setdeu\FahrenheitCelsiusConversions\Temperature;
4 
5test('can convert celsius to fahrenheit', function () {
6 $fahrenheit = Temperature::celsius(100)->toFahrenheit();
7 
8 expect($fahrenheit)->toEqual(212);
9});

Ejecutamos el script test en Composer para verificar que pase el test:

1composer test
Ejecuci贸n del script test

Ejecutando tests con Github Actions

Ejecutar autom谩ticamente los tests, cada vez que el repositorio recibe un push de c贸digo o al crear un pull-request, es bastante 煤til para mantener el paquete estable.

De hecho, la plantilla que hemos utilizado incluye un workflow de Github para hacerlo posible. Se encuentra en el archivo .github/workflows/run-tests.yml:

1name: Tests
2
3on: [push, pull_request]
4
5jobs:
6 test:
7 runs-on: ${{ matrix.os }}
8 strategy:
9 fail-fast: true
10 matrix:
11 os: [ubuntu-latest, windows-latest]
12 php: [8.0]
13 stability: [prefer-lowest, prefer-stable]
14
15 name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}
16
17 steps:
18 - name: Checkout code
19 uses: actions/checkout@v3
20
21 - name: Setup PHP
22 uses: shivammathur/setup-php@v2
23 with:
24 php-version: ${{ matrix.php }}
25 extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
26 coverage: none
27
28 - name: Setup problem matchers
29 run: |
30 echo "::add-matcher::${{ runner.tool_cache }}/php.json"
31 echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
32
33 - name: Install dependencies
34 run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction
35
36 - name: Execute tests
37 run: vendor/bin/pest

Para describir en qu茅 momento ejecutar los tests, definimos on: [push, pull_request]. As铆, al hacer un push de c贸digo o al generar un pull-request Github ejecutar谩 el workflow.

En la secci贸n matrix, se define una matriz para ejecutar el workflow mediante una combinaci贸n de variables:

  • os 鈥 Define los sistemas operativos que queremos ejecutar, la 煤ltima versi贸n de Ubuntu y la 煤ltima versi贸n de Windows.
  • php 鈥 Define las versiones de PHP que queremos ejecutar, PHP 8.0 en este caso, pero podemos a帽adir PHP 8.1 con [8.0, 8.1].
  • stability 鈥 Define las versiones de paquetes debe emplear Composer. Con Composer podemos preferir instalar las 煤ltimas versiones disponibles de los paquetes con prefer-stable o la versi贸n m铆nima que se puede instalar con prefer-lowest.

En la secci贸n steps, se definen los pasos para ejecutar los tests para cada combinaci贸n de variables en la matriz.

  • Checkout code 鈥 Define la creaci贸n e inicializaci贸n de un contenedor para los tests.
  • Setup PHP 鈥 Instala la versi贸n de PHP definida en la matriz e instala una serie de extensiones comunes para PHP.
  • Setup problem matchers 鈥 Configura Github Actions para proporcionar errores cuando los tests fallen.
  • Install dependencies 鈥 Instala las dependencias con Composer con la estabilidad definida en la matriz.
  • Execute tests 鈥 Comando para iniciar la ejecuci贸n de los tests con Pest.

Al realizar un commit y push, y dirigirmos a la secci贸n Actions del repositorio, observaremos que el workflow de tests se ha ejecutado.

Ejecuci贸n del workflow test

En la p谩gina de detalles, observamos que los tests se han ejecutado cuatro veces. Una vez por cada combinaci贸n posible. Y en el paso Execute tests vemos el detalle de que los tests fueron exitosos.

Ejecuci贸n del test

Si hacemos que nuestro test falle y hacemos un commit. Github Actions mostrar谩 el error de que el test a fallado.

1<?php
2 
3use Setdeu\FahrenheitCelsiusConversions\Temperature;
4 
5test('can convert celsius to fahrenheit', function () {
6- $fahrenheit = Temperature::celsius(100)->toFahrenheit();
7+ $fahrenheit = Temperature::celsius(200)->toFahrenheit();
8 
9 expect($fahrenheit)->toEqual(212);
10});
Test fallido

Revertimos el 煤ltimo cambio para que el test apruebe.

Pull requests

Intentemos la ejecuci贸n de los tests en un pull-request. Creamos una branch nueva, git checkout -b test-pr. Hacemos que el test falle y hacemos un commit.

Al crear un Pull request, Github nos mostrar谩 que los tests est谩n fallando. De esta manera podemos verificar que la suite de tests pasen incluso antes de incorporar el c贸digo al repositorio.

Test fallido en pull-request

Arreglando el estilo de c贸digo con Github Actions

Localmente, podemos arreglar problemas de estilo con el comando composer format, proporcionado por la plantilla que hemos usado. Ejecuta PHP CS Fixer utilizando el archivo de configuraci贸n .php-cs-fixer.dist.php.

composer format

Pero tambi茅n podemos arreglar el estilo de c贸digo autom谩ticamente en cada push al repositorio empleando Github Actions. As铆, aseguramos una consistencia de estilo, incluso al recibir contribuciones de c贸digo.

La plantilla incluye un workflow de Github para hacerlo posible. Se encuentra en el archivo .github/workflows/php-cs-fixer.yml.

1name: Check & fix styling
2
3on: [push]
4
5jobs:
6 php-cs-fixer:
7 runs-on: ubuntu-latest
8
9 steps:
10 - name: Checkout code
11 uses: actions/checkout@v3
12 with:
13 ref: ${{ github.head_ref }}
14
15 - name: Run PHP CS Fixer
16 uses: docker://oskarstark/php-cs-fixer-ga
17 with:
18 args: --config=.php-cs-fixer.dist.php --allow-risky=yes
19
20 - name: Commit changes
21 uses: stefanzweifel/git-auto-commit-action@v4
22 with:
23 commit_message: Fix styling

Inicializa un contenedor para ejecutar las tareas, ejecuta PHP CS Fixer para arreglar el estilo y realiza un commit "Fix styling" de los cambios realizados.

Al hacer un commit y push, y dirigirmos a la secci贸n Actions del repositorio, observaremos que el workflow de Check & fix styling se ha ejecutado.

Ejecuci贸n del workflow Check & fix styling

Lanzamiento del paquete

Lancemos el paquete para que otros usuarios o proyectos puedan instalarlo v铆a Composer. Lo publicaremos en Packagist, que es el principal repositorio p煤blico de paquetes en el ecosistema PHP.

Comencemos con hacer p煤blico el repositorio para que Packagist pueda acceder a 茅l.

En la secci贸n de Settings de la p谩gina de nuestro repositorio en Github hay una secci贸n para cambiar la visibilidad a p煤blico.

Repositorio settings Cambiar visibilidad a p煤blico

Ahora, con una cuenta registrada en Packagist enviamos nuestro paquete especificando la URL del repositorio.

Enviar a Packagist

Nuestro paquete ha sido publicado con una 煤nica versi贸n dev-main. Es decir, Composer instalar谩 el paquete con siempre hasta el 煤ltimo commit de la branch main.

Paquete publicado

Normalmente, etiquetaremos los lanzamientos por versi贸n. Asegur谩ndonos de que la versi贸n etiquetada es estable para utilizarse.

Modifiquemos nuestro archivo CHANGELOG.md para dar detalles de la versi贸n que estamos por lanzar. Este archivo contendr谩 nuestro historial de versiones.

Changelog v1.0.0

En Github creemos una nueva realease. Elegimos o generamos una tag. Colocamos la versi贸n 1.0.0 como t铆tulo para nuestra realease y una descripci贸n.

Crear nueva realease Crear tag Crear realease

Creada nuestra release, Packagist autom谩ticamente detectar谩 la nueva versi贸n y la har谩 disponible p煤blicamente.

Nueva versi贸n en Packagist

Usando el paquete publicado

Probemos instalar nuestro paquete.

Nos creamos un nuevo directorio de trabajo test:

1mkdir test
2cd test

Instalamos el paquete v铆a Composer:

1composer require setdeu/fahrenheit-celsius-conversions

Creamos un archivo index.php para realizar una conversi贸n de 100 grados Celsius a Fahrenheit.

1<?php
2 
3include 'vendor/autoload.php';
4 
5use Setdeu\FahrenheitCelsiusConversions\Temperature;
6 
7echo Temperature::celsius(100)->toFahrenheit() . PHP_EOL;

Ejecutamos php index.php en nuestro terminal.

php index.php

Fant谩stico, hemos empleado nuestro paquete y hecho una conversi贸n de prueba.


Publicando una nueva versi贸n

Agreguemos y publiquemos una nueva funcionalidad al paquete para tambi茅n pueda convertir grados Fahrenheit a celsius.

El construct de Temperature tambi茅n aceptar谩 $fahrenheit. Tendr谩 un m茅todo toCelsius para hacer la conversi贸n a grados Celsius usando la f贸rmula C = (F - 32) * (5 / 9). Y un m茅todo est谩tico fahrenheit para aceptar grados Fahrenheit e inicializar la clase:

1class Temperature
2{
3 protected float $celsius;
4+ protected float $fahrenheit;
5 
6- public function __construct(float $celsius)
7+ public function __construct(float $celsius = 0, float $fahrenheit = 0)
8 {
9 $this->celsius = $celsius;
10 $this->fahrenheit = $fahrenheit;
11 }
12 
13 public function toFahrenheit(): float
14 return ($this->celsius * (9 / 5)) + 32;
15 }
16 
17+ public function toCelsius(): float
18+ {
19+ return ($this->fahrenheit - 32) * (5 / 9);
20+ }
21 
22 public static function celsius(float $celsius): self
23 {
24 return new static($celsius);
25 }
26 
27+ public static function fahrenheit(float $fahrenheit): self
28+ {
29+ return new static(0, $fahrenheit);
30+ }
31}

Generamos un test para asegurarnos de que la conversi贸n sea correcta. En src/TemperatureTest.php a帽adimos el siguiente test de conversi贸n de 212 grados Fahrenheit a 100 grados Celsius:

1// ...
2 
3test('can convert fahrenheit to celsius', function () {
4 $celsius = Temperature::fahrenheit(212)->toCelsius();
5 
6 expect($celsius)->toEqual(100);
7});

Ejecutando nuestros tests verificamos que todo est茅 correcto.

1composer test
composer test

Realizamos un commit y hacemos push al repositorio. Tambi茅n podemos comprobar en los Actions de Github est茅n pasando los tests.

Github actions

Para publicar una nueva version, comenzamos con actualizar el archivo CHANGELOG.md para dar detalles de la versi贸n que estamos por publicar.

Changelog para 1.1.0

La versi贸n ser谩 1.1.0, esto es porque seguiremos las convenciones del versionado Semantic Versioning. Donde incrementos del 煤ltimo d铆gito 1.0.x representan arreglo de bugs. Incrementos del segundo d铆gito 1.x.0 representan nuevas funcionalidades a帽adidas sin introducir cambios de ruptura o breaking changes. Incrementos del primer d铆gito x.0.0 representan versiones que incluyen cambios de ruptura.

Creamos una nueva realease en Github:

Creando realease 1.1.0 Crear realease 1.1.0

Con esto, Packagist recoger谩 nuestra 煤ltima versi贸n publicada y la har谩 disponible p煤blicamente.

Realease 1.1.0 en Packagist

Probemos nuestra nueva funcionalidad en nuestro proyecto de prueba.

Ejecutamos composer update para actualizar nuestras dependencias y obtener las 煤ltimas versiones.

composer update

En nuestro archivo index.php realizamos una conversi贸n de 212 grados Fahrenheit a Celsius.

1// ...
2 
3echo Temperature::fahrenheit(212)->toCelsius() . PHP_EOL;

Y ejecutamos php index.php en nuestro terminal.

composer update

Utilizando las issues y discussions de GitHub

Las funcionalidades de issues y discussions de GitHub son una excelente forma de estar abierto a discusiones y contribuciones. En nuestro repositorio contamos con un archivo .github/ISSUE_TEMPLATE/config.yml que configura c贸mo debe abrirse una issue en Github.

1blank_issues_enabled: false
2contact_links:
3 - name: Ask a question
4 url: https://github.com/setdeu/fahrenheit-celsius-conversions/discussions/new?category=q-a
5 about: Ask the community for help
6 - name: Request a feature
7 url: https://github.com/setdeu/fahrenheit-celsius-conversions/discussions/new?category=ideas
8 about: Share ideas for new features
9 - name: Report a security issue
10 url: https://github.com/setdeu/fahrenheit-celsius-conversions/security/policy
11 about: Learn how to notify us for sensitive bugs
12 - name: Report a bug
13 url: https://github.com/setdeu/fahrenheit-celsius-conversions/issues/new
14 about: Report a reproducable bug

Con la opci贸n de blank_issues_enabled se deshabilita la creaci贸n de una issue en blanco. En su lugar, se mostrar谩n enlaces. Los primeros cuatro enlaces son para abrir un tema de discusi贸n. La 煤ltima, para crear una issue si se est谩 reportando un bug.

Plantilla issue
  • Hacer una pregunta. La gente puede hacernos cualquier pregunta abriendo una nueva discusi贸n.
  • Solicitar una funcionalidad. La gente puede sugerirnos a帽adir una nueva funcionalidad abriendo una nueva discusi贸n.
  • Reportar un problema de seguridad. Esto lleva a la p谩gina de seguridad donde se pide a la gente que env铆e un email en vez de hacer p煤blica alg煤n problema potencial de seguridad.
  • Reportar un bug. Crear una issue donde la gente puede hacernos saber de alg煤n bug con informaci贸n de c贸mo reproducirlo.

En desarrollo...