简体   繁体   中英

Composer/PHP: How to check if composer package is installed?

What is the cleanest way to check whether a package (of any version) is installed/present, using PHP within our application?

Basically, inside our application we want to call a function with the following signature:

bool function hasComposerPackage(string $packageName)

What would this function have to contain so that we can do something like:

if (hasComposerPackage('phpunit/phpunit')) {
    echo 'PHPUnit is installed!';
}

Ideally this needs to happen without any command-line exec calls and it should not autoload any unnecessary files in the process.

Edit: Composer 2 now supports this! https://blog.packagist.com/composer-2-0-is-now-available/

There is a new class, Composer\\InstalledVersions, which is autoloaded in every project and is available at runtime. It allows you to check which packages/versions are present at runtime of your own project.

@user1132363 using shell_exec() to run something like composer show is the only way to know for sure, but you seem to refuse to want to go this route. I'm not sure why you refuse, this is the solution to your problem. There is no other reliable means. Using class_exists is also unreliable as class names can change in packages.

That said, I think there's a bigger question here that you aren't asking: What problem are you actually trying to solve? As in, why do you need to check to see if a package is installed?

This is an old question and I think the accepted answer is fine, but just for those who would like to have this portable across Composer versions, perhaps even on a system deployed that does not have the composer user command and also to understand the underlying protocol with the file-system.

In general you can easily obtain the vendor folder from within your own package after it has been installed.

hakre/xmlreaderiterator

(exemplary "own" package relative path in the composer vendor folder, refs )

Additionally you can similarly easily obtain the installation status of other composer configured packages after they have been installed.

composer/semver

(exemplary another, Composer Semver, package relative path in the composer vendor folder, refs )

As usual, we may "only" need to understand how Composer itself works with our (file-)system, which I elaborate in my answer here to a greater extend to make it prominent you neither need to depend on a specific Composer runtime version nor shell_exec() as we're already executing PHP code, a) this is not necessary, b) we may not have composer(1) (the user-command named composer ) and c) we may not have the location of composer.json nor of the vendor folder.

Take it with a grain of salt, YMMV. Eg if I have composer(1) at hand during build time, I would not stress myself with the details of the file-system. However I may already benefit from knowing about the installed.json file.

Let's see what the vendor folder actually is, where it is located and how packages are placed inside. Then get any packages installation status just by their package name:

  1. Composer installs (by default and unless re-written) all dependencies (packages) into the vendor folder . This is a central configuration option that by default is the relative path vendor . It is relative to the working directory from which by default composer loads the project configuration from the relative path composer.json (for the composer update command) and composer.lock (for the composer install command if the lock file has not been disabled).
  2. These settings can be changed, the working directory most prominently perhaps, composer.json (and .lock ) by the COMPOSER environment parameter and the vendor directory by the COMPOSER_VENDOR_DIR parameter.
  3. As it has been previously discussed with the question, these environment parameters also override configuration settings within a project composer.json configuration file: #/config/vendor-dir mainly, but also the behaviour of having or not having the lock file #/config/lock (all JSON Pointers after # marked relative to the JSON Text merge production of $COMPOSER_HOME/config.json and overriding $COMPOSER ).
  4. It is therefore that if you don't want to rely on the runtime (eg no shell_exec() ) you have to figure out the vendor directory yourself.
  5. The vendor directory can only be figured out after composer has installed, as written in 1.), it will create the vendor folder .
  6. Having a package your own that is installed by composer will allow you to locate the vendor folder as it is installed therein. In PHP you acquire the path to the file itself with the __DIR__ magic constant .
  7. The default location a package is installed within the vendor folder is the package name . This is also the reason why it should be all lower-case and only contain characters from a reduced filename character set: This is more portable and helps you to locate the packages easily within the vendor folder .
  8. The default location of a package within the vendor folder is the package-vendor-name/package-name-name directory, this is composer.json#/name for each package, eg from the root of the vendor folder */*/composer.json#/name .
  9. Given from within your own code that you have configured to be installed with composer and you have installed it with composer, the code then can obtain the absolute path to the vendor folder from it's own files' __DIR__ magic constant by obtaining the grandparent directory in the hierarchy. Eg given your php file is in the root of your project, dirname(__DIR__, 2) (or dirname(dirname(dirname(__FILE__))) for PHP versions below 7.0/5.3 if you require such compatibility - which is supported by Composers' default autoloader code as well btw., it is compatible with PHP 5.2, so while at this level of detail, you may want to support it).
  10. Now after establishing the absolute path of the vendor folder you can either look-up other packages by their folder (the reverse of how you did with your package and the grand-parent directory lookup) just by known the name, -or-, by locating composer/installed.json which is composers sentinel file for the composer install command invocations' vendor folder that also installed your package. Its format did change in history but not much. And while we're here at this end, this should not be the show-stopper.

Non-default locations in the vendor folder at install (build) time:

  • the vendor folder is just a directory and after composer install, eg either by a hook script or just because you can, files there-in can be re-arranged at free will.
  • the benefit of the composer/installed.json file is that Composer itself keeps its protocol of what has been installed in the vendor folder. So despite things get moved away from default locations, as long as this file has not been moved, you can normally rely on it. So does Composer.
  • You may not always see changed timestamps on the files composer has in use for installing (creating and populating the vendor folder) because it relies on that composer/installed.json file what the contents of the vendor folder is and would not update if it deems the vendor folder production already complete.
  • You can easily prevent running scripts with composer commands with the --no-scripts and --no-plugins flags when you want to have a clean vendor folder production, perhaps with --no-dev for a production only vendor.
  • You can easily create your own vendor folder sentinel based on composer/installed.json by making a forced copy of which will also give you an updated timestamp.
  • You can further on create sentinels out of composer.json and composer.lock (if in use) by making copies of those inside the vendor folder and setting COMPOSER environment parameter to them. This comes with the benefit that invalidating the vendor folder is still possible for all these and you get additional book-keeping.

As usual every project is different, therefore you may only want to cherry pick a single detail from this answer or even the inverse, you may not want to make use of that after you've learned about it. There is quite some benefit to rely on the command-line interface of composer only and have your projects dependencies and the autoloader afterwards.

You known which packages are installed by default, by specifying your required ones in composer.json . Composer then can either install them (you know those packages are there), or it fails installing them (you know those packages are not there).

You can actually use PHP's class_exists() function. You only have to know the namespace and I suggest using it as a string rather than ClassName::class . Here is how:

<?php

class_exists('romanzipp\QueueMonitor\Services\QueueMonitor');

This will return true or false if you are using composer autoloading.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM