Skip to content

Commit 1d7e39a

Browse files
committed
First version
1 parent 686950e commit 1d7e39a

File tree

3 files changed

+299
-2
lines changed

3 files changed

+299
-2
lines changed

README.md

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,103 @@
1-
# Autoload Your Files Before Vendor Files
1+
# Composer Preload Files Plugin
2+
3+
[![GitHub release](https://img.shields.io/github/release/codezero-be/composer-preload-files.svg?style=flat-square)](https://github.com/codezero-be/composer-preload-files/releases)
4+
[![License](https://img.shields.io/packagist/l/codezero/composer-preload-files.svg?style=flat-square)](LICENSE.md)
5+
[![Total Downloads](https://img.shields.io/packagist/dt/codezero/composer-preload-files.svg?style=flat-square)](https://packagist.org/packages/codezero/composer-preload-files)
6+
7+
## Autoload Your Files Before Vendor Files
8+
9+
This Composer plugin enables you to autoload files that you specify before any vendor files.
10+
11+
This package is based on the original [funkjedi/composer-include-files](https://github.com/funkjedi/composer-include-files) by [@funkjedi](https://github.com/funkjedi) and its fork [hopeseekr-contribs/composer-include-files](https://github.com/hopeseekr-contribs/composer-include-files) by [@hopeseekr](https://github.com/hopeseekr).
12+
Because maintenance of these packages appears to be stalled, I decided to attempt and remake the package from scratch and fix any reported bugs in the process.
13+
14+
## ✅ Requirements
15+
16+
- PHP >= 7.0
17+
- Composer ^2.0
18+
19+
## 📦 Install
20+
21+
Install this package with Composer:
22+
23+
```bash
24+
composer require codezero/composer-preload-files
25+
```
26+
27+
## 📘 Usage
28+
29+
Add the `preload-files` to your project's `composer.json` under the `extra` section:
30+
31+
```json
32+
"extra": {
33+
"preload-files": [
34+
"app/helpers.php"
35+
]
36+
},
37+
```
38+
39+
The `preload-files` in the `extra` section will be loaded before the `files` in a standard `autoload` or `autoload-dev` section.
40+
This is true for your project, but also for any vendor package. Your project's preload files will always be loaded first.
41+
42+
## 🔌 Example Use Case
43+
44+
The best example use case is when you need to override a global helper function in a [Laravel](https://laravel.com) project.
45+
Those helper functions are declared in helper files that are loaded in the `files` array in the `autoload` section of `composer.json`:
46+
47+
```json
48+
"autoload": {
49+
"files": [
50+
"src/Illuminate/Collections/helpers.php",
51+
"src/Illuminate/Events/functions.php",
52+
"src/Illuminate/Foundation/helpers.php",
53+
"src/Illuminate/Support/helpers.php"
54+
]
55+
},
56+
```
57+
58+
These functions are declared like this:
59+
60+
```php
61+
// helpers.php
62+
if ( ! function_exists('route')) {
63+
function route($name, $parameters = [], $absolute = true)
64+
{
65+
return app('url')->route($name, $parameters, $absolute);
66+
}
67+
}
68+
```
69+
70+
If you add your own helper file to your project's `autoload` section to override such function, you will notice that Laravel's function is already loaded, and you can not redeclare it.
71+
72+
One way to solve this, is to manually `require` the helper file before Composer's `autoload.php` file.
73+
For Laravel, this means you need to `require` the file in your project's `public/index.php` file:
74+
75+
```php
76+
require __DIR__.'/../app/helpers.php';
77+
require __DIR__.'/../vendor/autoload.php';
78+
```
79+
80+
This works, but it is difficult, if not impossible to test (I did not find a way yet).
81+
If you are developing a package, it's also an extra step that users need take to install it.
82+
83+
Another solution is a package like this.
84+
85+
## ☕ Credits
86+
87+
- [@ivanvermeyen](https://github.com/ivanvermeyen)
88+
- [@hopeseekr](https://github.com/hopeseekr) - original fork: [hopeseekr-contribs/composer-include-files](https://github.com/hopeseekr-contribs/composer-include-files)
89+
- [@funkjedi](https://github.com/funkjedi) - original: [funkjedi/composer-include-files](https://github.com/funkjedi/composer-include-files)
90+
- [All contributors](https://github.com/codezero-be/composer-preload-files/contributors)
91+
92+
## 🔒 Security
93+
94+
If you discover any security related issues, please [e-mail me](mailto:ivan@codezero.be) instead of using the issue tracker.
95+
96+
## 📑 Changelog
97+
98+
A complete list of all notable changes to this package can be found on the
99+
[releases page](https://github.com/codezero-be/composer-preload-files/releases).
100+
101+
## 📜 License
102+
103+
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

src/AutoloadGenerator.php

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
namespace CodeZero\ComposerPreloadFiles;
4+
5+
use Composer\Autoload\AutoloadGenerator as ComposerAutoloadGenerator;
6+
use Composer\Composer;
7+
use Composer\IO\IOInterface;
8+
use Composer\Package\CompletePackage;
9+
use Composer\Pcre\Preg;
10+
use Composer\Util\Filesystem;
11+
use Composer\Util\Platform;
12+
13+
class AutoloadGenerator extends ComposerAutoloadGenerator
14+
{
15+
/**
16+
* Add preload files to the autoload files.
17+
*
18+
* @param \Composer\Composer $composer
19+
* @param \Composer\IO\IOInterface $io
20+
* @param \Composer\Util\Filesystem $filesystem
21+
*
22+
* @return void
23+
*/
24+
public function addPreloadFilesToAutoloadFiles(Composer $composer, IOInterface $io, Filesystem $filesystem)
25+
{
26+
$preloadFiles = $this->parsePreloadFiles($composer, $filesystem);
27+
28+
if (count($preloadFiles) === 0) {
29+
return;
30+
}
31+
32+
$io->writeError('<info>Adding preload files to the autoload files.</info>');
33+
34+
// Some pathfinding...
35+
// Do not remove double realpath() calls.
36+
// Fixes failing Windows realpath() implementation.
37+
// See https://bugs.php.net/bug.php?id=72738
38+
$basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd())));
39+
$vendorDir = $composer->getConfig()->get('vendor-dir');
40+
$vendorPath = $filesystem->normalizePath(realpath(realpath($vendorDir)));
41+
$targetDir = $vendorPath.'/composer';
42+
43+
$this->prependPreloadFilesToAutoloadFilesFile($filesystem, $preloadFiles, $targetDir, $basePath, $vendorPath);
44+
$this->regenerateAutoloadStaticFile($filesystem, $targetDir, $basePath, $vendorPath);
45+
}
46+
47+
/**
48+
* Prepend preload files to the 'autoload_files.php' file.
49+
*
50+
* @param \Composer\Util\Filesystem $filesystem
51+
* @param array $preloadFiles
52+
* @param string $targetDir
53+
* @param string $basePath
54+
* @param string $vendorPath
55+
*
56+
* @return void
57+
*/
58+
protected function prependPreloadFilesToAutoloadFilesFile(Filesystem $filesystem, $preloadFiles, $targetDir, $basePath, $vendorPath)
59+
{
60+
$vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true);
61+
$appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true);
62+
$appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode);
63+
$autoloadFilesFilePath = $targetDir.'/autoload_files.php';
64+
65+
// Merge preload files with original files.
66+
$originalFiles = $this->getOriginalAutoloadFiles($autoloadFilesFilePath);
67+
$allFiles = array_merge($preloadFiles, $originalFiles);
68+
69+
// Write new 'autoload_files.php'.
70+
$filesystem->filePutContentsIfModified(
71+
$autoloadFilesFilePath,
72+
$this->getIncludeFilesFile($allFiles, $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)
73+
);
74+
}
75+
76+
/**
77+
* Regenerate the 'autoload_static.php' file.
78+
*
79+
* @param \Composer\Util\Filesystem $filesystem
80+
* @param string $targetDir
81+
* @param string $basePath
82+
* @param string $vendorPath
83+
*
84+
* @return void
85+
*/
86+
protected function regenerateAutoloadStaticFile(Filesystem $filesystem, $targetDir, $basePath, $vendorPath)
87+
{
88+
// Get the class name suffix from 'autoload.php'.
89+
// https://github.com/composer/composer/blob/main/src/Composer/Autoload/AutoloadGenerator.php#L390-L392
90+
$autoloadContent = file_get_contents($vendorPath.'/autoload.php');
91+
$suffix = null;
92+
if (Preg::isMatch('{ComposerAutoloaderInit([^:\s]+)::}', $autoloadContent, $match)) {
93+
$suffix = $match[1];
94+
}
95+
96+
// Write new 'autoload_static.php'.
97+
$filesystem->filePutContentsIfModified(
98+
$targetDir.'/autoload_static.php',
99+
$this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath)
100+
);
101+
}
102+
103+
/**
104+
* Get the original files to autoload.
105+
*
106+
* @param string $autoloadFilesFilePath
107+
*
108+
* @return array
109+
*/
110+
protected function getOriginalAutoloadFiles($autoloadFilesFilePath)
111+
{
112+
if (file_exists($autoloadFilesFilePath)) {
113+
return include $autoloadFilesFilePath;
114+
}
115+
116+
return [];
117+
}
118+
119+
/**
120+
* Parse preload files from the root package and all vendor packages.
121+
*
122+
* @param \Composer\Composer $composer
123+
* @param \Composer\Util\Filesystem $filesystem
124+
*
125+
* @return array
126+
*/
127+
protected function parsePreloadFiles(Composer $composer, Filesystem $filesystem)
128+
{
129+
$installationManager = $composer->getInstallationManager();
130+
$preloadFilesKey = 'preload-files';
131+
$preloadFiles = [];
132+
133+
// Do not remove double realpath() calls.
134+
// Fixes failing Windows realpath() implementation.
135+
// See https://bugs.php.net/bug.php?id=72738
136+
$basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd())));
137+
138+
$rootPackage = $composer->getPackage();
139+
$rootPackageConfig = $rootPackage->getExtra();
140+
$rootPackagePreloadFiles = $rootPackageConfig[$preloadFilesKey] ?? [];
141+
142+
foreach ($rootPackagePreloadFiles as $file) {
143+
$identifier = $this->getFileIdentifier($rootPackage, $file);
144+
$preloadFiles[$identifier] = $filesystem->normalizePath($basePath . '/' . $file);
145+
}
146+
147+
$otherPackages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
148+
149+
foreach ($otherPackages as $package) {
150+
if ( ! ($package instanceof CompletePackage)) {
151+
continue;
152+
}
153+
154+
$packageBaseDir = $filesystem->normalizePath($installationManager->getInstallPath($package));
155+
$packageConfig = $package->getExtra();
156+
$packagePreloadFiles = $packageConfig[$preloadFilesKey] ?? [];
157+
158+
foreach ($packagePreloadFiles as $file) {
159+
$identifier = $this->getFileIdentifier($package, $file);
160+
$preloadFiles[$identifier] = $filesystem->normalizePath($packageBaseDir . '/' . $file);
161+
}
162+
}
163+
164+
return $preloadFiles;
165+
}
166+
}

src/Plugin.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use Composer\EventDispatcher\EventSubscriberInterface;
77
use Composer\IO\IOInterface;
88
use Composer\Plugin\PluginInterface;
9+
use Composer\Script\ScriptEvents;
10+
use Composer\Util\Filesystem;
911

1012
class Plugin implements PluginInterface, EventSubscriberInterface
1113
{
@@ -23,6 +25,13 @@ class Plugin implements PluginInterface, EventSubscriberInterface
2325
*/
2426
protected $io;
2527

28+
/**
29+
* Tells if plugin has run.
30+
*
31+
* @var bool
32+
*/
33+
protected $done;
34+
2635
/**
2736
* Returns an array of event names this subscriber wants to listen to.
2837
*
@@ -31,10 +40,30 @@ class Plugin implements PluginInterface, EventSubscriberInterface
3140
public static function getSubscribedEvents()
3241
{
3342
return [
34-
//
43+
ScriptEvents::POST_AUTOLOAD_DUMP => 'addPreloadFilesToAutoloadFiles',
44+
ScriptEvents::POST_INSTALL_CMD => 'addPreloadFilesToAutoloadFiles',
3545
];
3646
}
3747

48+
/**
49+
* Add preload files to the autoload files.
50+
*
51+
* @return void
52+
*/
53+
public function addPreloadFilesToAutoloadFiles()
54+
{
55+
// Run only once if multiple events trigger.
56+
if ($this->done === true) {
57+
return;
58+
}
59+
60+
$this->done = true;
61+
62+
$filesystem = new Filesystem();
63+
$generator = new AutoloadGenerator($this->composer->getEventDispatcher(), $this->io);
64+
$generator->addPreloadFilesToAutoloadFiles($this->composer, $this->io, $filesystem);
65+
}
66+
3867
/**
3968
* Apply plugin modifications to Composer.
4069
*

0 commit comments

Comments
 (0)