What does the composer "provide" do? - php

I'm using Laravel + VueJS to recreate a POS system from work. I needed to install sped-nfe package to work with it on a system for work.
This package requires many other packages in order to function properly, like ext-curl, ext-soap, ext-json.
As per instructions, I added
"nfephp-org/sped-nfe" : "^5.0"
to my composer.json.
When I ran composer install or composer update, the following error ocurred:
Your requirements could not be resolved to an installable set of packages.
Problem 1
- nfephp-org/sped-nfe[v5.0.100, ..., v5.0.122] require ext-soap * -> it is missing from your system. Install or enable PHP's soap extension.
- Root composer.json requires nfephp-org/sped-nfe ^5.0 -> satisfiable by nfephp-org/sped-nfe[v5.0.100, ..., v5.0.122].
To enable extensions, verify that they are enabled in your .ini files:
- C:\laragon\bin\php\php-7.4.19-Win32-vc15-x64\php.ini
You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.
I'm using Windows and I'm not authorized to modify our local server nor the actual server, I was getting frustrated trying to find a solution to my problem - almost every answer would tell me to modify my php.ini or install curl with sudo apt-get - I arbitrarily decided to add the following lines to my composer.json:
"provide": {
"ext-curl":"*",
"ext-soap":"*"
},
Et voilà, composer update and composer install were working smoothly.
What's bothering me is, according to the composer documentation,
provide
Map of packages that are provided by this package. This is mostly
useful for implementations of common interfaces. A package could
depend on some virtual package e.g. psr/logger-implementation, any
library that implements this logger interface would list it in
provide. Implementors can then be found on Packagist.org.
Using provide with the name of an actual package rather than a virtual
one implies that the code of that package is also shipped, in which
case replace is generally a better choice. A common convention for
packages providing an interface and relying on other packages to
provide an implementation (for instance the PSR interfaces) is to use
a -implementation suffix for the name of the virtual package
corresponding to the interface package.
I am not providing this package, I simply wanted to require it but ended up putting it differently. Also, I've tried requiring it, but the error was still there.
Was this a good solution to my problem or should I do it differently?
Is there anything about this that I should worry?
Can someone explain the 'provide' syntax for me?

If you add a package or a PHP extension to the provide section, you tell composer that your package itself or the external system setup "provides" this one. The dependency resolver is fine with this.
This does not check further whether this dependency is actually properly resolved or not. Composer relies on your statement that this is not a lie ;) So, if you only add this to the section without properly providing that package, you cannot be sure that your application works properly.
In your example: the package you want to install requires the SOAP extension. It won't work properly without it. If you cannot install that extension on your server, you should not use this package.

Related

How can I install a single specific package from composer.json, to install developer tools?

I'm using PHP_CodeSniffer in my GitLab CI/CD pipelines to ensure my code is properly formatted. The job looks like follows:
stages:
- test
- build
- deploy
coding_standard:
stage: test
script:
- curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
- php phpcs.phar --extensions=php .
That's working as expected. However, the exact version of the tool is not specified here. So if there's suddenly a new major version of PHP_Codesniffer, the CI/CD job might fail, although my PHP code hasn't changed.
Furthermore, I currently have the tool installed globally on my local machine. In that way, I cannot have a specific version of the tool for every PHP project.
Now I'd like to add the tool as Composer dev-dependency (require-dev).
In the CI/CD job I would then call composer install instead of downloading the tool via curl.
The problem: That will download all packages needlessly, instead of just PHP_Codesniffer and its dependencies. Can I prevent that?
You can't do this with composer. You can't even install "only the dev dependencies". It's all the dependencies, all the non-dev dependencies, and that's all.
And it's generally a bad idea to install this kind of dependency as a project dependency, since very easily you can enter in dependency hell for reasons beyond your actual application needs. Development tools should not bring that level of complexity and danger to your deployment strategy.
To get around this, you could use something like the Composer Bin Plugin to isolate these dependencies and yet install them through composer. Then on CI you'd run composer install on this directory only, and run the tool from this location (or symlink it to bin, which is what the plugin does when it's installed, but you wouldn't have it installed in CI if you are not installing all the dependencies anyway).
Why not download any tagged version from Github through https://github.com/squizlabs/PHP_CodeSniffer/releases, like https://github.com/squizlabs/PHP_CodeSniffer/releases/download/3.6.0/phpcs.phar?
Using a PHAR is better than installing such stuff using Composer, as you might install other incompatible dependencies that way (this is not the case with phpcs, but other tools like phpmd install other dependencies from Symfony)

How to force dev-dependencies from a dependency to also install with composer?

I'm currently working on a package (cms), which has a dev-dependency to a certain package (code-generator) to create code. This package is not needed in production.
However, when creating a website that uses the cms package, dev-dependencies (including the code-generator) are not installed (which is correct composer behavior btw).
But while developing the website, the code-generator is required.
Is there any way to force a certain dev-dependency to also install when the package is installed?
This is not possible. Dependency can either be required for the package to work properly (then it should be in require section and it is always installed), or required only for development of this package (then it should be in require-dev section and is installed only when package repository is root). There is nothing in between. If this code-generator dependency is required by your package to work it clearly fails into first category (require section).
Usually in this case the best solution is to split this package into 2 packages: regular package and dev package with all tools used only during development process. So it should be installed by 2 commands:
composer require myvendor/mypackage
composer require myvendor/mypackage-dev --dev
This will still require everyone to install two packages instead of one, but it should not be a big problem if it is properly documented. Result should is more clear (it should be quite obvious what is the purpose of myvendor/mypackage-dev package) and gives more control to package owner (he can easily add new dependencies for dev package) and end user (he can always skip installing myvendor/mypackage-dev if he don't want to use this code generator).

PHP install package globally: apt-get vs composer

I found there are two options to install PHP package globally in Linux (Ubuntu 16.04):
Using composer:
composer global require symfony/finder
The package will be located at ~/.config/composer/vendor/
Using apt-get:
apt-get install php-symfony-finder
The package will be located at /usr/share/php/
This directory /usr/share/php/ is also in default PHPs include_path (I have PHP 7.2)
There are several questions I have:
Why would I want to install package globally ?
I know it's useful to install php tools globally, like phpunit - It has binary file and it allows you to run tests everywhere, so you don't have to install it in every project.
But what about symfony/finder for example ? What is particular use of this package installed globally ?
What is the difference between 1 and 2 option ?
Does it have any different use cases or different effects ?
Why would I want to install package globally ?
Normally, these are dependencies you want to use in almost every project, because they are available at a system level you can use them without duplicating their dependencies in every application you create.
For example, in my case I have php_md, php_cs for code formatting, phpunit for testing.
What is the difference between 1 and 2 option ?
Both are package managers, they make sure every package installed has the correct dependencies, so their core functionality is similar.
Now, they have several differences:
Their focus in the packages they manage, composer is specific for php based packages but apt-get is for Linux and more system level oriented.
Their package database, composer uses packagist and apt-get uses a selection of repositories and ppas (you can find them in /var/lib/apt/lists/).
The package selection, since composer is specialized in php you can expect a wider variety in anything php related.
In conclusion, you can clearly make it work with both, but I would recommend you to keep everything php related on composer, unifying them under the same manager.
Any other difference or correction I've overlooked is welcome.

composer provide / require not finding implementation

Trying to use composer's provide feature, I added a provide section to my implementation repository ffa-php-mock, in which I say it provides shadiakiki1986/ffa-php-implementation. In my repository consuming this implementation, ffa-php-cli, I replaced the composer require entry requiring ffa-php-mock with an entry requiring ffa-php-implementation. If I try to run a composer update, I get the following
> composer update
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
- The requested package shadiakiki1986/ffa-php-implementation could not be found in any version, there may be a typo in the package name.
Potential causes:
- A typo in the package name
- The package is not available in a stable-enough version according to your minimum-stability setting
see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.
Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.
Can you give me any hints as to how to debug what the problem is?
Edit: simplified example
I tried the provide feature in a dummy project on something that already exists. jackalope/jackalope provides phpcr/phpcr-implementation, which in its turn is already required by DoctrinePHPCRBundle.
If I start a new empty project with composer init and specify that my project depends on phpcr/phpcr-implementation, I get the same error as above. I also tried putting phpcr/phpcr-implementation directly in the composer.json file and running a composer update, but to no avail.
> cat composer.json
{
"require": {
"phpcr/phpcr-implementation": "2.1.0"
}
}
I would think that this is perhaps a bug in composer, but it seems from this issue that it is already in use.
> composer --version
Composer version 1.3.0 2016-12-24 00:47:03
The closest composer github issue I can find is #2811, but that one says that the reporting isn't clear, not that provide doesn't work
So I ended up learning that the specific package with the implementation should still be included in the composer.json file. For packages using the library, that's ok as they get added to the require section. For the library that is directly requiring the implementation, that should be done in the require-dev so that the unit tests can work and so that other projects using the library are not required to use the same implementation

Why does Composer Install a Different Package?

I've been working with the Magento Firegento custom Composer installer, and I ran into this odd bit of Composer behavior I don't understand.
Consider the following dead simple composer.json file
{
"require": {
"magento-hackathon/magento-composer-installer": "*"
}
}
If I run compser.phar install with this composer.json file, I get the following.
$ composer.phar install --no-dev
Loading composer repositories with package information
Installing dependencies
- Installing aoepeople/composer-installers (v0.0.1)
Loading from cache
Writing lock file
Generating autoload files
From my mostly lay-person's understanding composer.phar, I've said
Hey, composer, please install the magento-hackathon/magento-composer-installer package from packagist.org
And composer has said back to me
Sir, yes sir! Here's the aoepeople/composer-installers package
I don't understand why composer installed aoepeople/composer-installers, when I asked for magento-hackathon/magento-composer-installer.
To be clear: I understand the reason magento-hackathon/magento-composer-installer wasn't installed is this is a package that lives in a different composer repository. My original mistake was not including this repository in my composer.json file.
However, it doesn't make sense to me that composer would install a different package than the one I asked for. When I search packagist there's no magento-hackathon/magento-composer-installer extension.
Why does packagist install a different extension? What's happening behind the scenes to make magento-hackathon/magento-composer-installer resolve to aoepeople/composer-installers? How/where in the composer source could I debug this sort of thing myself in the future?
Vinai published a great writeup on Magento and Composer here.
Quoting from there
Composer will use packagist.org to look where to get the libraries. Because Magento modules aren’t listed there, you will have to add the packages.firegento.org repository to the configuration as follows [...]
So you will need the repository
"repositories":[
{
"type":"composer",
"url":"http://packages.firegento.com"
}
],
to get the magento composer installer.
And yes composer offers replacements. On the packaging entry for aoepeople/composer-installers you will notice the replaces section:
And since magento-hackathon/magento-composer-installer is not available on packagist composer will deliver you aoepeople/composer-installers.
OK, so that's a weird behavior :) I'm the author of the aoepeople/composer-installers package and the idea behind this is that this package provides an alternative (very basic and simplified implementation of the composer package-type magento-module - and adds another type magento-source. I only want the installer to put the package in the right place - keeping it simple. That's why I decided to come up with an alternative package.
The reason why the aoepeople/composer-installer replaces the magento-hackathon/magento-composer-installer is because many Magento modules already come with a composer.json that requires the magento-hackathon/magento-composer-installer. In order to be able to seamlessly use the simpler installer that one is "pretending" to be the hackathon-installer. But unless you actively require aoepeople/composer-installers in your project's composer.json you should continue using the original installer as no Magento module's composer.json out there (not even ours) is directly referring to aoepeople/composer-installers.
The fact that packagist tries to be "smart" and returns a package that replaces a package that's not registered is new to me and - to be honest - very disturbing. While this might be intended behavior I share the opinion that this could easily be abused. I start liking Alan's idea to bypass packagist completely using "packagist":"disabled". Especially in the case of Magento modules this seems to happen easily because most of the Magento modules are unknown to packagist while being registered at packages.firegento.com only.
A simple quickfix/workaround to this specific situation could be to register magento-hackathon/magento-composer-installer on packagist.org.
The Packagist package at https://packagist.org/packages/aoepeople/composer-installers has metadata saying that it replaces the magento-hackathon/magento-composer-installer package. Packagist would then install this as it's the package that replaces what you asked for.
Docs here: https://getcomposer.org/doc/04-schema.md#replace

Categories