jeremy jeremy - 3 months ago 21
PHP Question

Automatically including file in package when included as a dependency in Composer

I have a package that must automatically load a non-namespaced PHP file when the package is included in an application.

Below is my general directory structure

packages/
+-- PackageA/
+-- Entities/
+-- Mappers/
+-- Services/
+-- composer.json
+-- constants.php

apps/appA/
+-- vendors/
+-- autoload/
+-- composer.json

apps/appB/
+-- vendors/
+-- composer.json


I've followed the directions here to use a path repository in making
PackageA
a dependency for
appA/
. This part works smoothly.

Some files within
PackageA
require access to constants, mostly filepaths. This is what "constants.php" is for, and these values are defined procedurally:

<?php

define('XML_REPO_PATH', __DIR__ . '/../blah/xml/');

// --etc--


I originally thought to use the 'files' autoloading mechanism in "packages/PackageA/composer.json" with:

{
...

"autoload": {
"psr-4": { ... }
"files": ["constants.php"]
}
}


However, this is not requiring constants.php when
PackageA
is included as a dependency in
appA
. To fix this, instead of putting
"files": [...]
in "packages/PackageA/composer.json", I put the following in the autoload section of "app/appsA/composer.json":

"files": ["vendors/packages/PackageA/constants.php"]


This isn't very desirable because every application using
PackageA
would need this. I would think that the nature of composer would allow me to make sure that files within
PackageA
have access to (i.e., are meant to include) certain procedural code, like in the case of config constants. Is there a way to do this?

Answer

Don't use Composer's files autoloading to include configuration files or files with constants. Please think about the performance impact for all other libraries. A file in the files section is loaded on every invocation of your script, regardless, if you are using PackageA or not. Also think about possible clashes of constant names, due to non namespaced constant usage. files autoloading is only! meant to be used for legacy code that cannot otherwise be made working. You should avoid using it.

because in php < 5.6 i can't concatenate class constants with other constants like __DIR__

The main problem is not the concatenation, but that the constants file isn't a class. Autoloading won't work here, because Composer's Autolader loads classes only.

So, one solution could be to introduce an empty class for Constants, but add the side-effects on top. Then namespace it under your vendor\PackageA umbrella. This enables you to add use vendor\PackageA\Constants; in other classes, in order to trigger the autoloading, right?

You are including an empty class, but when the file is autoloaded, the defines are happening as side-effects. A good IDE will place an error flag on this file, because it causes side-effects. It's still ugly, because other developers don't know where the defines come from, when they simply include a class - but better than using autoloading files section.

composer.json

 "autoload": {
     "psr-4": { "\Vendor\PackageA\\" : "./src/packages/PackageA/" }
 }

constants.php

<?php

namespace Vendor\PackageA;

class Constants
{ 
    // @todo PHP 5.6 namespaced class constants
}

// global side effect: constant definition
define('XML_REPO_PATH', __DIR__ . '/../blah/xml/');

// etc..

The best-practice is probably to use a Configuration class with a constructor that accepts a configuration object or array for the configuration of your package. This decouple package and application from hard-coded global configurations. Basically, configuration injection (App environment into Package, Package configures itself based on that context).

Comments