7

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.

mae
  • 14,947
  • 8
  • 32
  • 47
  • http://stackoverflow.com/a/15186162/897075?? Sorry command line bit didn't register until second read - may I ask why? – Alex.Barylski Mar 05 '17 at 04:06
  • @Alex.Barylski This needs to happen within the application without relying on commandline for various reasons unrelated to this question. – mae Mar 05 '17 at 04:07
  • Sorry my bad - it's late here - read it quickly and replied just as fast – Alex.Barylski Mar 05 '17 at 04:10
  • 2
    A quick look into the vendor/composer has a file "installed.json" - might be of interest? I was thinking about this very problem only a week ago - so this is a brain dump. – Alex.Barylski Mar 05 '17 at 04:13
  • No worries! I think that would be an OK solution, but it introduces a new problem: auto-locating the `installed.json` file. – mae Mar 05 '17 at 04:16
  • Yea...I think the default is always "vendor" - relative to your composer.json file - unless your override it in composer.json using "config.vendor-dir" – Alex.Barylski Mar 05 '17 at 04:18
  • When you say "rely on command line exec calls" - do you mean that `shell_exec()` is out of the question? – FatBoyXPC Mar 05 '17 at 04:20
  • @Alex.Barylski Indeed, which means we first need to auto-locate `composer.json` itself. @FatBoyXPC: Correct. – mae Mar 05 '17 at 04:22
  • You should be able to assume composer.json location as it's a requirement in your project if your using composer, no? Sorry I don't have an answer for you - just batting ideas around :) – Alex.Barylski Mar 05 '17 at 04:24
  • @Alex.Barylski That's right, but it won't be a universal solution that works in anyone's project. I guess that's an acceptable & perhaps necessary trade-off, but maybe someone will come up with a way around it. – mae Mar 05 '17 at 04:29
  • Look for it in default spot - if not found use symfony/finder to look for it? You could maybe further determine the correct composer.json by looking at it's header details, version, etc. As a user, I can tell you I would prefer just clear documentation as to what to expect. – Alex.Barylski Mar 05 '17 at 04:36
  • I don't think you'll find a "universal" way of doing this, including using `shell_exec`. Why can't you use `shell_exec`, though? – FatBoyXPC Mar 05 '17 at 04:49
  • 4
    You could use [class_exists](http://php.net/class_exists), then it wouldn't matter if the package has been installed via composer or by any other means. This gives you some flexibility, but does wander outside the lines of "composer", though. – FatBoyXPC Mar 05 '17 at 04:58
  • @FatBoyXPC That's probably the next best solution for now. It's just a shame that it doesn't tell you about a package as a whole. What we want here basically is to make sure that a *set of classes* is available. We could do it with `class_exists`, it would just be a lot more convenient if we could look for the actual composer package by passing only its name as a parameter. – mae Jul 15 '17 at 07:41
  • You shouldn't use composer package names either, as those dependencies can also change. Just like how you can get a list of messages when installing like this: `Package swiftmailer/swiftmailer is abandoned, you should avoid using it. Use symfony/mailer instead.` – mbomb007 Jul 13 '22 at 16:39
  • The only way if you don't want to rely on shell_exec() which covers the environment, so it's not only `composer.json#/config/vendor-dir` but also overriding `COMPOSER_VENDOR_DIR`, is you have a dev-requirement of a package yours own so that you know from within where it is located as well as your own utility class-name that is being autoloaded. That is also independent to the composer version. Dependencies however don't change if you use a lock file and their names don't change if you use a composer.json file (also /cc @mbomb007). Don't overcomplicate things. – hakre Jan 28 '23 at 10:13

2 Answers2

10

Composer 2 (Oct 2020) now supports looking for package installation status! 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.

The following usage example is from Installed versions - Runtime Composer utilities:

\Composer\InstalledVersions::isInstalled('vendor/package'); // returns bool

@user1132363 using shell_exec() to run something like composer show is the only way to know for sure (Composer below 2), 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?

hakre
  • 193,403
  • 52
  • 435
  • 836
FatBoyXPC
  • 861
  • 7
  • 15
  • I want to have a modular structure in my application and since every module is ultimately managed by composer that's the best way to check for their presence. Relying on `class_exists` is not sufficient because there's no guarantee that class names will remain the same or even exist as the modules evolve. – mae Aug 28 '20 at 02:14
  • Your application code shouldn't be checking what dependencies composer has, though. You should specify your dependencies then use them as necessary in your application code. – FatBoyXPC Aug 28 '20 at 18:04
  • Not possible in a modular structure. Modules are not dependencies. They are optional components which may or may not be present. – mae Aug 29 '20 at 08:28
  • I'm honestly not tracking what you mean by this. I apologize I cannot be of further assistance. – FatBoyXPC Sep 03 '20 at 22:11
  • 2
    I've updated my answer to reflect that Composer 2 now supports this directly. – FatBoyXPC Oct 26 '20 at 14:42
  • Sorry but class names almost never change, or at least I've never seen them. `shell_exec` is very dangerous to use in your code. – Amir Hassan Azimi Jan 26 '23 at 13:44
  • @AmirHassanAzimi: True, depending on who does it, but also in general, there is some risk w/ `shell_exec`, but there is also no need to do it as the answer outlines. However, the installation might be with a Composer version below 2 and you then need to look for the installed packages directly (and Composer might not even be available and therefore `shell_exec` wouldn't make any sense at all). If there is such a need, perhaps the following answer may have additional pointers: https://stackoverflow.com/a/75267235/367456 – hakre Jan 28 '23 at 11:39
0

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. E.g. 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 (e.g. 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, e.g. 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. E.g. 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, e.g. 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).

hakre
  • 193,403
  • 52
  • 435
  • 836
  • This is not an answer you’ve just written an essay for how file system works with no example code. – Amir Hassan Azimi Jan 29 '23 at 12:07
  • Missing code should not be a problem. But checking the filesystem sounds like a good alternative to the one from the accepted answer, as checking for the folder with the package name is way more lightweight, but still fulfills the requirement to check this **by package name** and not by any other metric like a namespace or a randomly picked class name – Nico Haase Jan 30 '23 at 05:40