Qué es composer y por qué deberías usarlo

Estás desarrollando una aplicación PHP y te enfrentás a un problema que seguro alguien ya resolvió antes.

Para qué re-inventar la rueda, ¿cierto?

Abrís una nueva pestaña, entrás a Google y escribís «Cómo haer X usando php».

La lista es larga, pero hay un factor común en todos los ejemplos: composer.

Ahí está otra vez.

Composer por aquí, composer por allá.

Por todos lados te tiran cosas como:

composer require base/demo-base-code

Como quien dice «Buenos días».

Y, a pesar de no ser precisamente un newbie, te frustra tener que copiar, pegar y rezar.

No, necesitás entender qué es composer de una buena vez para que a la próxima vez que te lo topes, puedas trabajar y proseguir con esos ejemplos, recursos o códigos sin ningún problema.

Así que… ¿de qué se trata el famoso composer?

Qué es Composer

Composer es un gestor de dependencias para PHP (Similar a lo que npm es para JavaScript o pip para Python).

Es una aplicación PHP que ayuda a administrar las librerías desarrolladas por terceros que vas a incorporar a tu proyecto.

De algún modo podrías considerarlo como el heredero de PEAR.

Hoy día se considera el gestor de dependencias de-facto por unas cuantas buenas razones:

  • Es muy simple de operar
  • Cuenta con un repositorio super completo (Packagist)
  • Disminuye significativamente los problemas de cambio de ambiente de ejecución (Mediante su funcionalidad de congelar dependencias)

Si nunca lo usaste, te recomiendo que lo pruebes.

Cómo se instala Composer

La instalación de composer es sumamente simple. Como dice el sitio getcomposer.org en su página de descargas:

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

A partir de ahí tendrás disponible un comando llamado composer (Qué sorpresa ¿no?). Si lo ejecutas sin decir más nada te va a dar una lista de los sub-comandos disponibles:

  about           Shows the short information about Composer.
  archive         Creates an archive of this composer package.
  browse          Opens the package's repository URL or homepage in your browser.
  clear-cache     Clears composer's internal package cache.
  clearcache      Clears composer's internal package cache.
  config          Sets config options.
  create-project  Creates new project from a package into given directory.
  depends         Shows which packages cause the given package to be installed.
  diagnose        Diagnoses the system to identify common errors.
  dump-autoload   Dumps the autoloader.
  dumpautoload    Dumps the autoloader.
  exec            Executes a vendored binary/script.
  global          Allows running commands in the global composer dir ($COMPOSER_HOME).
  help            Displays help for a command
  home            Opens the package's repository URL or homepage in your browser.
  info            Shows information about packages.
  init            Creates a basic composer.json file in current directory.
  install         Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.
  licenses        Shows information about licenses of dependencies.
  list            Lists commands
  outdated        Shows a list of installed packages that have updates available, including their latest version.
  prohibits       Shows which packages prevent the given package from being installed.
  remove          Removes a package from the require or require-dev.
  require         Adds required packages to your composer.json and installs them.
  run-script      Runs the scripts defined in composer.json.
  search          Searches for packages.
  self-update     Updates composer.phar to the latest version.
  selfupdate      Updates composer.phar to the latest version.
  show            Shows information about packages.
  status          Shows a list of locally modified packages.
  suggests        Shows package suggestions.
  update          Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file.
  validate        Validates a composer.json and composer.lock.
  why             Shows which packages cause the given package to be installed.
  why-not         Shows which packages prevent the given package from being installed.

Te voy a mostrar algunas pocas cosas como para que veas el poder que tiene:

Cómo funciona Composer

Composer trabaja con dos archivos de configuración: composer.json y composer.lock.

El segundo es más bien de uso interno así que no hace falta conocer muchos detalles sobre él.

El primero en cambio es el que vas a usar bastante a menudo ya que en él se declaran todas las dependencias que tu sistema tendrá.

Es muy simple de leer (al igual que cualquier json) y permite una gran flexibilidad para definir las dependencias (por ejemplo se puede decir a partir de qué versión se acepta) tanto de librerías escritas en php como del intérprete y sus extensiones.

Típicamente un archivo composer.json se ve más o menos así:

{
  "name": "leeway/scrapper",
  "description": "Un pequeño web scrapper",
  "minimum-stability": "stable",
  "license": "proprietary",
  "authors": [
    {
      "name": "Mauro Chojrin",
      "email": "mauro.chojrin@leewayweb.com"
    }
  ],
  "require": {
    "fabpot/goutte": "v3.2.0"
  }
}

Donde la parte más importante es lo que está dentro de require (Las dependencias específicas del proyecto).

Se puede crear el archivo «manualmente» o, mejor, se puede usar el comando composer init que lo hace por nosotros (y aparte permite definir algunas propiedades en forma interactiva).

Pero claramente, de poco sirve la declaración de las dependencias si no tenemos las librerías instaladas…

Cómo instalar librerías usando Composer

Para instalar librerías composer dispone del comando install, el cual se encarga de bajarlas, armar el autoloading y demás… ¡magia!.

Lo único que te queda por hacer es require_once 'vendor/autoload.php'; donde vayas a usar las librerías instaladas y voilà.

Hasta acá todo muy lindo, se pueden instalar y usar dependencias con muy poco esfuerzo pero… ¿Te pasó alguna vez que desarrollaste un sistema, en tu computadora andaba 10 puntos pero al subirlo a producción daba algún error raro?

¿Te pasó estar horas dando vueltas sin entender nada hasta que te diste cuenta de que la versión 3.4.1 de la librería de envío de mails no era compatible con la 3.4.0 (Instalada en producción)?

Justamente para eso composer tiene el archivo composer.lock. Si bien es un archivo de texto, ¡no lo toques!.

Te muestro el que corresponde al composer.json que veías:

{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
        "This file is @generated automatically"
    ],
    "content-hash": "cc70bd9c59b4fdbb7e8e632402e15bcc",
    "packages": [
        {
            "name": "fabpot/goutte",
            "version": "v3.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/FriendsOfPHP/Goutte.git",
                "reference": "8cc89de5e71daf84051859616891d3320d88a9e8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/8cc89de5e71daf84051859616891d3320d88a9e8",
                "reference": "8cc89de5e71daf84051859616891d3320d88a9e8",
                "shasum": ""
            },
            "require": {
                "guzzlehttp/guzzle": "^6.0",
                "php": ">=5.5.0",
                "symfony/browser-kit": "~2.1|~3.0",
                "symfony/css-selector": "~2.1|~3.0",
                "symfony/dom-crawler": "~2.1|~3.0"
            },
            "type": "application",
            "extra": {
                "branch-alias": {
                    "dev-master": "3.2-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Goutte\\": "Goutte"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                }
            ],
            "description": "A simple PHP Web Scraper",
            "homepage": "https://github.com/FriendsOfPHP/Goutte",
            "keywords": [
                "scraper"
            ],
            "time": "2016-11-15T16:27:29+00:00"
        },
        {
            "name": "guzzlehttp/guzzle",
            "version": "6.3.0",
            "source": {
                "type": "git",
                "url": "https://github.com/guzzle/guzzle.git",
                "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
                "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
                "shasum": ""
            },
            "require": {
                "guzzlehttp/promises": "^1.0",
                "guzzlehttp/psr7": "^1.4",
                "php": ">=5.5"
            },
            "require-dev": {
                "ext-curl": "*",
                "phpunit/phpunit": "^4.0 || ^5.0",
                "psr/log": "^1.0"
            },
            "suggest": {
                "psr/log": "Required for using the Log middleware"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "6.2-dev"
                }
            },
            "autoload": {
                "files": [
                    "src/functions_include.php"
                ],
                "psr-4": {
                    "GuzzleHttp\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Michael Dowling",
                    "email": "mtdowling@gmail.com",
                    "homepage": "https://github.com/mtdowling"
                }
            ],
            "description": "Guzzle is a PHP HTTP client library",
            "homepage": "http://guzzlephp.org/",
            "keywords": [
                "client",
                "curl",
                "framework",
                "http",
                "http client",
                "rest",
                "web service"
            ],
            "time": "2017-06-22T18:50:49+00:00"
        },
        {
            "name": "guzzlehttp/promises",
            "version": "v1.3.1",
            "source": {
                "type": "git",
                "url": "https://github.com/guzzle/promises.git",
                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
                "shasum": ""
            },
            "require": {
                "php": ">=5.5.0"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "GuzzleHttp\\Promise\\": "src/"
                },
                "files": [
                    "src/functions_include.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Michael Dowling",
                    "email": "mtdowling@gmail.com",
                    "homepage": "https://github.com/mtdowling"
                }
            ],
            "description": "Guzzle promises library",
            "keywords": [
                "promise"
            ],
            "time": "2016-12-20T10:07:11+00:00"
        },
        {
            "name": "guzzlehttp/psr7",
            "version": "1.4.2",
            "source": {
                "type": "git",
                "url": "https://github.com/guzzle/psr7.git",
                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
                "shasum": ""
            },
            "require": {
                "php": ">=5.4.0",
                "psr/http-message": "~1.0"
            },
            "provide": {
                "psr/http-message-implementation": "1.0"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "GuzzleHttp\\Psr7\\": "src/"
                },
                "files": [
                    "src/functions_include.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Michael Dowling",
                    "email": "mtdowling@gmail.com",
                    "homepage": "https://github.com/mtdowling"
                },
                {
                    "name": "Tobias Schultze",
                    "homepage": "https://github.com/Tobion"
                }
            ],
            "description": "PSR-7 message implementation that also provides common utility methods",
            "keywords": [
                "http",
                "message",
                "request",
                "response",
                "stream",
                "uri",
                "url"
            ],
            "time": "2017-03-20T17:10:46+00:00"
        },
        {
            "name": "psr/http-message",
            "version": "1.0.1",
            "source": {
                "type": "git",
                "url": "https://github.com/php-fig/http-message.git",
                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Psr\\Http\\Message\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "PHP-FIG",
                    "homepage": "http://www.php-fig.org/"
                }
            ],
            "description": "Common interface for HTTP messages",
            "homepage": "https://github.com/php-fig/http-message",
            "keywords": [
                "http",
                "http-message",
                "psr",
                "psr-7",
                "request",
                "response"
            ],
            "time": "2016-08-06T14:39:51+00:00"
        },
        {
            "name": "symfony/browser-kit",
            "version": "v3.3.10",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/browser-kit.git",
                "reference": "317d5bdf0127f06db7ea294186132b4f5b036839"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/317d5bdf0127f06db7ea294186132b4f5b036839",
                "reference": "317d5bdf0127f06db7ea294186132b4f5b036839",
                "shasum": ""
            },
            "require": {
                "php": "^5.5.9|>=7.0.8",
                "symfony/dom-crawler": "~2.8|~3.0"
            },
            "require-dev": {
                "symfony/css-selector": "~2.8|~3.0",
                "symfony/process": "~2.8|~3.0"
            },
            "suggest": {
                "symfony/process": ""
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "3.3-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\BrowserKit\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony BrowserKit Component",
            "homepage": "https://symfony.com",
            "time": "2017-10-02T06:42:24+00:00"
        },
        {
            "name": "symfony/css-selector",
            "version": "v3.3.10",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/css-selector.git",
                "reference": "07447650225ca9223bd5c97180fe7c8267f7d332"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/css-selector/zipball/07447650225ca9223bd5c97180fe7c8267f7d332",
                "reference": "07447650225ca9223bd5c97180fe7c8267f7d332",
                "shasum": ""
            },
            "require": {
                "php": "^5.5.9|>=7.0.8"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "3.3-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\CssSelector\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Jean-François Simon",
                    "email": "jeanfrancois.simon@sensiolabs.com"
                },
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony CssSelector Component",
            "homepage": "https://symfony.com",
            "time": "2017-10-02T06:42:24+00:00"
        },
        {
            "name": "symfony/dom-crawler",
            "version": "v3.3.10",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/dom-crawler.git",
                "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/40dafd42d5dad7fe5ad4e958413d92a207522ac1",
                "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1",
                "shasum": ""
            },
            "require": {
                "php": "^5.5.9|>=7.0.8",
                "symfony/polyfill-mbstring": "~1.0"
            },
            "require-dev": {
                "symfony/css-selector": "~2.8|~3.0"
            },
            "suggest": {
                "symfony/css-selector": ""
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "3.3-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\DomCrawler\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony DomCrawler Component",
            "homepage": "https://symfony.com",
            "time": "2017-10-02T06:42:24+00:00"
        },
        {
            "name": "symfony/polyfill-mbstring",
            "version": "v1.6.0",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/polyfill-mbstring.git",
                "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
                "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "suggest": {
                "ext-mbstring": "For best performance"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Polyfill\\Mbstring\\": ""
                },
                "files": [
                    "bootstrap.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Nicolas Grekas",
                    "email": "p@tchwork.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony polyfill for the Mbstring extension",
            "homepage": "https://symfony.com",
            "keywords": [
                "compatibility",
                "mbstring",
                "polyfill",
                "portable",
                "shim"
            ],
            "time": "2017-10-11T12:05:26+00:00"
        }
    ],
    "packages-dev": [],
    "aliases": [],
    "minimum-stability": "stable",
    "stability-flags": [],
    "prefer-stable": false,
    "prefer-lowest": false,
    "platform": [],
    "platform-dev": []
}

Es un json por dentro (Bastante más extenso y complejo que el original) pero su objetivo es el de congelar las dependencias que efectivamente se están usando en tu código.

Si mirás de nuevo el composer.json vas a notar que la versión de Goutte está expresada en forma explícita (v3.2.0) pero, si mirás un poco más profundamente (por ejemplo en el archivo vendor/fabpot/goutte/composer.json) verás que hay otras que no son tan estrictas:

{
    "name": "fabpot/goutte",
    "type": "application",
    "description": "A simple PHP Web Scraper",
    "keywords": ["scraper"],
    "homepage": "https://github.com/FriendsOfPHP/Goutte",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        }
    ],
    "require": {
        "php": ">=5.5.0",
        "symfony/browser-kit": "~2.1|~3.0",
        "symfony/css-selector": "~2.1|~3.0",
        "symfony/dom-crawler": "~2.1|~3.0",
        "guzzlehttp/guzzle": "^6.0"
    },
    "autoload": {
        "psr-4": { "Goutte\\": "Goutte" }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "3.2-dev"
        }
    }
}

Aquí dice que se necesita una versión de Guzzle al menos 6.0.

Si ves en composer.lock notarás que la instalación de Guzzle es clara:

...
        {
            "name": "guzzlehttp/guzzle",
            "version": "6.3.0",
...
        }

Y es que 6.3.0 es al menos 6.0 pero, al momento de distribuir el código necesitamos saber exactamente qué versión estamos usando.

De modo que el composer.lock se genera:

  1. Recorriendo el árbol de dependencias completo
  2. Especificando las versiones concretas que están instaladas actualmente

De esta forma, si el código se lleva a un entorno nuevo (o hay que reinstalarlo por alguna razón), la información en composer.lock asegura que se esté usando exactamente el mismo set de dependencias que se usó en testing (Si el testing no fue suficiente es otro tema…).

Actualización de dependencias

Por otro lado, si querés probar lo último disponible podés (¡en desarrollo!) ejecutar el comando composer update y composer se encargará nuevamente de descargar las nuevas versiones de tus dependencias (siempre respetando tus especificaciones) para que puedas probarlas y generar un nuevo composer.lock (Que obviamente vas a tener que committear).

Remoción de dependencias

Esto puede ser algo más tricky (Siempre es más fácil meter cosas que no se usarán que sacarlas y arriesgarse pero… si sabés lo que hacés…). El comando que se usa para esto es composer remove.

¡A usarlo con juicio!

Y si necesitas ayuda…

Siempre tenés la posibilidad de ejecutar el comando composer help <comando>.

Conclusión

Ahora ya sabés qué es Composer y cómo te puede simplificar la vida.

La próxima vez que vayas a leer un ejemplo de código comprenderás los pasos de la instalación y podrás leer el archivo composer.json para ver qué otras dependencias vienen incluidas.

¿Cómo? ¿Que todavía no descargaste composer? ¡Hacelo ahora mismo! Acá te dejo el link otra vez, ya vas a tener tiempo de volver acá a agradecerme 🙂

mchojrin

Por mchojrin

Ayudo a desarrolladores PHP a acceder mercados y clientes más sofisticados y exigentes