PHP Composer: arregla las dependencias sin dolor

Muchos de ustedes deben haberse encontrado con una situación en la que hay un error o no la funcionalidad necesaria en la biblioteca o el marco que están utilizando. Supongamos que no fue demasiado vago y formó una solicitud de extracción. Pero no lo aceptarán de inmediato, y la próxima versión del producto en general puede ocurrir en un año.


PHP Composer: arregla las dependencias sin dolor


¿Qué hacer si necesita urgentemente extender la corrección al producto? La solución obvia es usar una bifurcación de una biblioteca o marco. Sin embargo, no todo es simple con horquillas. Usar la herencia para anular la funcionalidad que necesita ser cambiada no siempre es posible y a menudo requiere cambios importantes. Los complementos para Composer que pueden parchear dependencias vienen al rescate.


En este artículo hablaré más sobre por qué las bifurcaciones son inconvenientes, y también consideraré dos complementos para Composer para parchear dependencias: cómo difieren, cómo usarlos y cuáles son sus ventajas. Si ha encontrado problemas similares o simplemente se pregunta, bienvenido a cat.


El problema se considera más convenientemente con un ejemplo. Digamos que queremos cambiar algo en la biblioteca de Cobertura de Código PHP , que se usa en el marco de prueba PHPUnit para medir el nivel de cobertura de código por pruebas. Supongamos que queremos arreglar algo como esto en la versión 7.0.8 (archivo myFix.patch ):


 diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 2c92ae2..514171e 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -190,6 +190,7 @@ public function filter(): Filter */ public function getData(bool $raw = false): array { + // for example some changes here if (!$raw && $this->addUncoveredFilesFromWhitelist) { $this->addUncoveredFilesFromWhitelist(); } 

Creemos nuestra biblioteca de ejemplo. Deja que sea php-composer-patches-example . Los detalles aquí no son muy importantes, pero en caso de que decida ver qué es la biblioteca, traigo la salida de la consola bajo el spoiler.


Texto oculto
 $ git clone git@github.com:mougrim/php-composer-patches-example.git   «php-composer-patches-example»… remote: Enumerating objects: 3, done. remote: Counting objects: 100% (3/3), done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0  : 100% (3/3), . $ cd php-composer-patches-example/ $ $ composer.phar init --name=mougrim/php-composer-patches-example --description="It's an example for article with using forks and patches for changing dependencies" --author='Mougrim <rinat@mougrim.ru>' --type=library --require='phpunit/phpunit:^8.4.2' --license=MIT --homepage='https://github.com/mougrim/php-composer-patches-example' Welcome to the Composer config generator This command will guide you through creating your composer.json config. Package name (<vendor>/<name>) [mougrim/php-composer-patches-example]: Description [It's an example for article with using forks and patches for changing dependencies]: Author [Mougrim <rinat@mougrim.ru>, n to skip]: Minimum Stability []: Package Type (eg library, project, metapackage, composer-plugin) [library]: License [MIT]: Define your dependencies. Would you like to define your dev dependencies (require-dev) interactively [yes]? no { "name": "mougrim/php-composer-patches-example", "description": "It's an example for article with using forks and patches for changing dependencies", "type": "library", "homepage": "https://github.com/mougrim/php-composer-patches-example", "require": { "phpunit/phpunit": "^8.4.2" }, "license": "MIT", "authors": [ { "name": "Mougrim", "email": "rinat@mougrim.ru" } ] } Do you confirm generation [yes]? yes Would you like to install dependencies now [yes]? yes Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 29 installs, 0 updates, 0 removals - Installing sebastian/version (2.0.1): Loading from cache - Installing sebastian/type (1.1.3): Loading from cache - Installing sebastian/resource-operations (2.0.1): Loading from cache - Installing sebastian/recursion-context (3.0.0): Loading from cache - Installing sebastian/object-reflector (1.1.1): Loading from cache - Installing sebastian/object-enumerator (3.0.3): Loading from cache - Installing sebastian/global-state (3.0.0): Loading from cache - Installing sebastian/exporter (3.1.2): Loading from cache - Installing sebastian/environment (4.2.2): Loading from cache - Installing sebastian/diff (3.0.2): Loading from cache - Installing sebastian/comparator (3.0.2): Loading from cache - Installing phpunit/php-timer (2.1.2): Loading from cache - Installing phpunit/php-text-template (1.2.1): Loading from cache - Installing phpunit/php-file-iterator (2.0.2): Loading from cache - Installing theseer/tokenizer (1.1.3): Loading from cache - Installing sebastian/code-unit-reverse-lookup (1.0.1): Loading from cache - Installing phpunit/php-token-stream (3.1.1): Loading from cache - Installing phpunit/php-code-coverage (7.0.8): Loading from cache - Installing doctrine/instantiator (1.2.0): Loading from cache - Installing symfony/polyfill-ctype (v1.12.0): Loading from cache - Installing webmozart/assert (1.5.0): Loading from cache - Installing phpdocumentor/reflection-common (2.0.0): Loading from cache - Installing phpdocumentor/type-resolver (1.0.1): Loading from cache - Installing phpdocumentor/reflection-docblock (4.3.2): Loading from cache - Installing phpspec/prophecy (1.9.0): Loading from cache - Installing phar-io/version (2.0.1): Loading from cache - Installing phar-io/manifest (1.0.3): Loading from cache - Installing myclabs/deep-copy (1.9.3): Loading from cache - Installing phpunit/phpunit (8.4.2): Loading from cache sebastian/global-state suggests installing ext-uopz (*) phpunit/phpunit suggests installing phpunit/php-invoker (^2.0.0) phpunit/phpunit suggests installing ext-soap (*) Writing lock file Generating autoload files $ $ echo 'vendor/' > .gitignore $ echo 'composer.lock' >> .gitignore $ git add .gitignore composer.json $ $ git commit --gpg-sign --message='Init composer' [master ce800ae] Init composer 2 files changed, 18 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json $ git push origin master  : 4, . Delta compression using up to 4 threads.  : 100% (3/3), .  : 100% (4/4), 1.21 KiB | 1.21 MiB/s, . Total 4 (delta 0), reused 0 (delta 0) To github.com:mougrim/php-composer-patches-example.git f31c342..ce800ae master -> master 

¿Qué tiene de malo un tenedor de adicción?


Veamos cómo se produce la dependencia de la bifurcación. Intentemos bifurcar la cobertura del código PHP.


  1. Vamos a la página de Cobertura de Código PHP en GitHub .
  2. Presione el botón de la horquilla Botón de horquilla (nota: tendrá su bifurcación, reemplace mougrim con su nombre de usuario).
  3. Clonar el tenedor:
     cd ../ git clone git@github.com:mougrim/php-code-coverage.git cd php-code-coverage 
  4. Vaya a la versión que queremos parchear:
     git checkout 7.0.8 
  5. Cree una rama para corregir:
     git checkout -b 7.0.8-myFix 
  6. Hacemos los cambios necesarios, nos comprometemos, empujamos:
     git apply ../myFix.patch git add src/CodeCoverage.php git commit --gpg-sign --message='My fix' git push -u origin 7.0.8-myFix 
  7. Agregue fork como repositorio en composer.json para nuestra biblioteca (esto es necesario para que cuando se conecte el phpunit/php-code-coverage , no se conecte el paquete original, sino el fork):
     cd ../php-composer-patches-example git checkout -b useFork composer.phar config repositories.phpunit/php-code-coverage vcs https://github.com/mougrim/php-code-coverage.git 
  8. Cambie la versión de dependencia a brunch:
     composer.phar require phpunit/php-code-coverage 'dev-7.0.8-myFix' 

Pero en realidad es aún más complicado: Composer dice que la instalación es imposible, ya que phpunit/phpunit requiere phpunit/php-code-coverage version ^7.0.7 , y para nuestro proyecto requiere dev-7.0.8-myFix :


 $ composer.phar require phpunit/php-code-coverage 'dev-7.0.8-myFix' ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Your requirements could not be resolved to an installable set of packages. Problem 1 - phpunit/phpunit 8.4.2 requires phpunit/php-code-coverage ^7.0.7 -> satisfiable by phpunit/php-code-coverage[7.0.x-dev]. - phpunit/phpunit 8.4.2 requires phpunit/php-code-coverage ^7.0.7 -> satisfiable by phpunit/php-code-coverage[7.0.x-dev]. - phpunit/phpunit 8.4.2 requires phpunit/php-code-coverage ^7.0.7 -> satisfiable by phpunit/php-code-coverage[7.0.x-dev]. - Can only install one of: phpunit/php-code-coverage[7.0.x-dev, dev-7.0.8-myFix]. - Installation request for phpunit/php-code-coverage dev-7.0.8-myFix -> satisfiable by phpunit/php-code-coverage[dev-7.0.8-myFix]. - Installation request for phpunit/phpunit ^8.4.2 -> satisfiable by phpunit/phpunit[8.4.2]. Installation failed, reverting ./composer.json to its original content. 

¿Qué hacer al respecto? Hay cuatro opciones:


  1. Además de la bifurcación de phpunit/php-code-coverage , bifurca PHPUnit y escribe la versión dev-7.0.8-myFix para la dependencia phpunit/php-code-coverage . Esta ruta es bastante complicada en términos de soporte y cuanto más complicada, más bibliotecas dependen de la phpunit/php-code-coverage .
  2. Use alias al conectar phpunit/php-code-coverage . Pero los alias no se extraen de las dependencias, lo que significa que siempre deberán escribirse manualmente.
  3. Realice la phpunit/php-code-coverage en su bifurcación para que la etiqueta 7.0.8 a otra confirmación. Esto al menos no es obvio, pero como máximo, en Git es inconveniente trabajar con etiquetas que hacen referencia a diferentes confirmaciones con el mismo nombre en diferentes repositorios remotos.
  4. En su fork phpunit/php-code-coverage use la etiqueta de lanzamiento alfa, por ejemplo 7.0.8-a+myFix (puede haber colisiones con lanzamientos alfa de la biblioteca fuente).

Todas las opciones tienen sus inconvenientes. También intenté usar una etiqueta como 7.0.8.1 , pero Composer no acepta tales etiquetas.


Las opciones segunda y cuarta parecen ser los males menores. Por el número de acciones, son aproximadamente las mismas, en este artículo consideraremos solo una: la cuarta. Crea una etiqueta de lanzamiento alfa:


 cd ../php-code-coverage git tag 7.0.8-a+myFix git push origin 7.0.8-a+myFix cd ../php-composer-patches-example composer.phar require phpunit/php-code-coverage '7.0.8-a+myFix' git add composer.json git commit --gpg-sign --message='Use fork' git push -u origin useFork 

Digamos que queremos usar nuestra biblioteca mougrim/php-composer-patches-example en un proyecto que depende de phpunit/phpunit . Aquí, uno no puede prescindir del chamanismo, nuevamente tendrá que especificar el repositorio https://github.com/mougrim/php-code-coverage.git para phpunit/php-code-coverage , así como indicar explícitamente la dependencia de phpunit/php-code-coverage versión 7.0.8-a+myFix (de lo contrario, la instalación no tendrá éxito):


 cd ../ mkdir php-project cd php-project/ composer.phar require phpunit/phpunit '^8.4.2' composer.phar config repositories.mougrim/php-composer-patches-example vcs https://github.com/mougrim/php-composer-patches-example.git composer.phar config repositories.phpunit/php-code-coverage vcs https://github.com/mougrim/php-code-coverage.git composer.phar require phpunit/php-code-coverage 7.0.8-a+myFix composer.phar require mougrim/php-composer-patches-example dev-useFork 

Tenga en cuenta que php-composer-patches-example está conectado como un repositorio, ya que este repositorio es solo un ejemplo y, por lo tanto, no se ha agregado a Packagist. En su caso, es más probable que omita este paso.


Para resumir el uso de horquillas.


Las ventajas de este enfoque:


  • No es necesario instalar complementos para Composer.

Contras de este enfoque:


  • si usa roave/security-advisories , no verá información de que la versión de la dependencia que bifurcó y modificó contiene una vulnerabilidad;
  • cuando salga una nueva versión de la dependencia, la historia de la bifurcación tendrá que repetirse nuevamente;
  • si desea corregir una dependencia de dependencia, como en el ejemplo considerado, entonces dev-* no funcionará y debe chamanizar con versiones o bifurcación para dependencias conflictivas;
  • Si hay proyectos que dependen de su biblioteca, no tendrá que instalar la biblioteca en el proyecto de la manera más obvia y conveniente;
  • si hay proyectos que dependen de su biblioteca, para ellos la versión de phpunit/php-code-coverage será estrictamente fija, lo que no siempre es aceptable;
  • Además, si los proyectos de los puntos anteriores ya bifurcaron la Cobertura de Código PHP por alguna otra razón, entonces todo se vuelve aún más complicado.

Creo que ya te diste cuenta de que abandonar la adicción no es una buena idea.


Usando cweagans / composer-parches


Una vez más, experimentando dolor y sufrimiento al usar tenedores, me encontré con cweagans/composer-patches en PHP Digest No. 101 (por cierto, pronskiy tiene un blog útil, recomiendo suscribirse). Este es un complemento para omposer, que le permite aplicar parches a las dependencias. Después de leer la descripción, pensé que esto es exactamente lo que necesitas.


Cómo usar cweagans / composer-parches:


  1. Clonar la cobertura del código PHP:
     cd ../ rm -rf php-code-coverage git clone git@github.com:sebastianbergmann/php-code-coverage.git cd php-code-coverage 
  2. Vaya a la versión que queremos parchear:
     git checkout 7.0.8 
  3. Hacemos los cambios necesarios.
  4. Crea un parche:
     mkdir -p ../php-composer-patches-example/patches/phpunit/php-code-coverage git diff HEAD > ../php-composer-patches-example/patches/phpunit/php-code-coverage/myFix.patch 
  5. En nuestro proyecto conectamos cweagans/composer-patches :
     cd ../php-composer-patches-example git checkout master composer.phar update git checkout -b cweagansComposerPatches composer.phar require cweagans/composer-patches '^1.6.7' 
  6. Para configurar cweagans/composer-patches agregue lo siguiente a composer.json (puede especificar varios parches para un paquete):
     { "config": { "preferred-install": "source" }, "extra": { "patches": { "phpunit/php-code-coverage": { "My fix description": "patches/phpunit/php-code-coverage/myFix.patch" } }, "enable-patching": true } } 
  7. Actualizar dependencias:
     composer.phar update 
  8. Si algo salió mal, esto se puede ver en la salida del comando anterior, pero por si acaso, puede verificar que nuestros cambios se hayan aplicado:
     $ grep example vendor/phpunit/php-code-coverage/src/CodeCoverage.php // for example some changes here 
  9. Comprometerse e impulsar el resultado:
     git add composer.json patches/phpunit/php-code-coverage/myFix.patch git commit --gpg-sign --message='Use cweagans/composer-patches' git push -u origin cweagansComposerPatches 

Nos aseguramos de que al instalar nuestra biblioteca en el proyecto, el parche también se aplique.


Crea un proyecto:


 cd ../ rm -rf php-project mkdir php-project cd php-project composer.phar require phpunit/phpunit '^8.4.2' 

Agregue las siguientes líneas a composer.json :


 { "extra": { "enable-patching": true } } 

Instale mougrim/php-composer-patches-example :


 composer.phar config repositories.mougrim/php-composer-patches-example vcs https://github.com/mougrim/php-composer-patches-example.git composer.phar require mougrim/php-composer-patches-example dev-cweagansComposerPatches 

Parece que al conectar el paquete debería haber habido un intento de aplicar el parche, pero no.
Actualizamos los paquetes para que se aplique el parche, pero esto no sucede:


 $ composer.phar update Removing package phpunit/php-code-coverage so that it can be re-installed and re-patched. - Removing phpunit/php-code-coverage (7.0.8) Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 1 install, 0 updates, 0 removals No patches supplied. Gathering patches for dependencies. This might take a minute. - Installing phpunit/php-code-coverage (7.0.8): Loading from cache - Applying patches for phpunit/php-code-coverage patches/phpunit/php-code-coverage/myFix.patch (My fix description) Could not apply patch! Skipping. The error was: The "patches/phpunit/php-code-coverage/myFix.patch" file could not be downloaded: failed to open stream: No such file or directory Writing lock file Generating autoload files 

Habiendo hurgado en un rastreador de errores, descubrí que los parches basados ​​en archivos no se resuelven en el error de dependencias . Resulta que debe especificar la URL antes del parche (lo que significa descargarlo desde algún lugar) o especificar la ruta del parche manualmente en cada proyecto donde instala una dependencia que requiere parches.


Para resumir el uso de cweagans/composer-patches .


Las ventajas de este enfoque:


  • el complemento tiene una comunidad;
  • roave/security-advisories no dejarán de funcionar;
  • cuando se lanza una nueva versión de la dependencia, si el parche se aplica con éxito, será suficiente para asegurarse de que todo funcione con la nueva versión (para lanzamientos menores, con una alta probabilidad de que funcione solo, para lanzamientos mayores también es probable que no se haga nada);
  • si hay proyectos que dependen de su biblioteca, para ellos la versión de phpunit/php-code-coverage no se fijará estrictamente;
  • Además, en el caso del párrafo anterior, dicho proyecto podrá aplicar sus parches en la Cobertura de Código PHP.

Contras:


  • Este es un complemento para Composer, lo que significa que al actualizar Composer puede romperse;
  • enable-patching=true debe especificarse para que los parches se apliquen desde dependencias;
  • el responsable principal del proyecto no tiene mucho tiempo para tratarlo, por lo tanto, por regla general, acepta solicitudes de extracción, pero no desarrolla particularmente el proyecto (por ejemplo, tenía ideas para la segunda versión en la tarea , pero poco ha cambiado después de tres años);
  • hay parches basados ​​en archivos que no se resuelven en el error de dependencias , lo cual es inconveniente y ha estado colgado en la cartera durante tres años;
  • No puede usar diferentes parches para diferentes versiones de dependencias.

El último punto se ha convertido en una barrera para mí. Primero hice una solicitud de función . El responsable de mantenimiento escribió que no quería agregar esta función al código principal, pero en la segunda versión sería posible escribir un complemento (sí, un complemento para el complemento para Composer). La perspectiva para la segunda versión era vaga, así que decidí buscar alternativas. Entre la pequeña lista no encontré un complemento que sería compatible.


No quería entrar en el código del complemento, así que decidí bifurcar bifurcaciones, seguro, alguien ya había encontrado el problema y lo resolvió.


Usar parches de Vaimo Composer


En la mayoría de las horquillas no hubo diferencias en absoluto con respecto a la original (¿por qué incluso se bifurcan?). Parte de los tenedores se hicieron para solicitudes de extracción, que ya se fusionaron con la biblioteca principal. Sin embargo, había un candidato interesante que estaba resolviendo mi problema: Vaimo Composer Patches . En ese momento todavía estaba enmarcado como una bifurcación, pero su responsable, al parecer, no iba a hacer solicitudes de extracción. Entre otras cosas, por ejemplo, ya cambió el nombre del paquete a vaimo/composer-patches . Pero había un problema: los problemas estaban desactivados, es decir, no había comentarios del autor en absoluto. Además, el complemento no estaba alojado en Packagist .


Un tenedor tan bueno no debe perderse en una pila de otros tenedores inútiles. Por lo tanto, contacté al autor con una solicitud para habilitar problemas y agregar un paquete a Packagist. Después de casi un mes, el autor respondió e hizo todo esto. :)


Usar vaimo/composer-patches no vaimo/composer-patches diferente de usar el complemento anterior, pero puede especificar diferentes parches para diferentes versiones.


  1. cweagans/composer-patches nuestra biblioteca (es necesario eliminar la carpeta del vendor , ya que los complementos cweagans/composer-patches y vaimo/composer-patches no vaimo/composer-patches muy compatibles entre sí):
     cd ../php-composer-patches-example git checkout master rm -rf vendor/ composer.phar update 
  2. Realizamos los puntos 1-4 de la sección anterior.
  3. En nuestro proyecto conectamos vaimo/composer-patches :
     cd ../php-composer-patches-example git checkout -b vaimoComposerPatches composer.phar require vaimo/composer-patches '^4.20.2' 
  4. Para configurar vaimo/composer-patches agregue lo siguiente a composer.json (la documentación se puede ver aquí ):
     { "extra": { "patches": { "phpunit/php-code-coverage": { "My fix description": { "< 7.0.0": "patches/phpunit/php-code-coverage/myFix-leagcy.patch", ">= 7.0.0": "patches/phpunit/php-code-coverage/myFix.patch" } } } } } 
  5. Actualizar dependencias:
     composer.phar update 
  6. Si algo salió mal, esto se puede ver en la salida del comando anterior, pero por si acaso, puede asegurarse de que se apliquen nuestros cambios:
     $ grep example vendor/phpunit/php-code-coverage/src/CodeCoverage.php // for example some changes here 
  7. Comprometerse e impulsar el resultado:
     git add composer.json patches/phpunit/php-code-coverage/myFix.patch git commit --gpg-sign --message='Use vaimo/composer-patches' git push -u origin vaimoComposerPatches 

Nos aseguramos de que al instalar nuestra biblioteca en el proyecto, el parche también se aplique.


Cree un proyecto e instale mougrim/php-composer-patches-example :


 cd ../ rm -rf php-project mkdir php-project cd php-project composer.phar require phpunit/phpunit '^8.4.2' composer.phar config repositories.mougrim/php-composer-patches-example vcs https://github.com/mougrim/php-composer-patches-example.git composer.phar require mougrim/php-composer-patches-example dev-vaimoComposerPatches 

Por si acaso, puede asegurarse de que se hayan aplicado nuestros cambios:


 $ grep example vendor/phpunit/php-code-coverage/src/CodeCoverage.php // for example some changes here 

Para resumir el uso de vaimo/composer-patches .


Las ventajas de este complemento son casi las mismas que las anteriores, pero también incluyen lo siguiente:


  • el mantenedor está desarrollando activamente el complemento y ya ha lanzado la cuarta versión principal;
  • no es necesario prescribir nada adicional para aplicar parches de dependencias;
  • Puede usar diferentes parches para diferentes versiones de dependencias;
  • el complemento tiene muchas configuraciones, por lo que si la funcionalidad descrita en el artículo no es suficiente para usted, consulte la documentación; tal vez la característica que necesita ya esté implementada.

Contras:


  • como el anterior, este es un complemento para Composer, lo que significa que al actualizar Composer puede romperse;
  • a diferencia del complemento anterior, esta comunidad tiene menos.

Conclusiones


Para resumir los resultados generales:


  • el uso de tenedores de paquetes para cualquier reparación menor es inconveniente;
  • cweagans/composer-patches es un buen complemento, pero se desarrolla mal, por lo que no lo recomiendo;
  • Vaimo Composer Patches es un excelente complemento que resuelve bien el problema de arreglar las dependencias, y también tiene un montón de configuraciones;
  • Vaimo Composer Patches tiene una comunidad pequeña, pero espero que este artículo la aumente;
  • Si se requieren muchos cambios en la dependencia, entonces puede ser más fácil recurrir a una bifurcación dura (mantener la bifurcación independiente de la dependencia original).

También hice una conclusión indirecta: si algún tipo de dependencia no proporciona la funcionalidad necesaria, entonces puede haber bifurcaciones que implementaron esta funcionalidad y aún más.


En Badoo, usamos Vaimo Composer Patches en dos casos:


  • en SoftMocks para parchear PHPUnit y cobertura de código PHP;
  • en el repositorio interno para el arreglo Webmozart Assert para la compatibilidad con SoftMocks como un arreglo temporal (mientras que SoftMocks no admite construcciones array_map(array('static', 'valueToString') ).

Rinat Akhmadeev, Sr. Desarrollador PHP


UPD1 : Gracias BoShurik por el enlace a los alias . Se agregó un punto sobre alias al artículo.

Source: https://habr.com/ru/post/473654/


All Articles