Skip to content

Commit

Permalink
First version
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanvermeyen committed Apr 2, 2023
1 parent 686950e commit 1d7e39a
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 2 deletions.
104 changes: 103 additions & 1 deletion README.md
@@ -1 +1,103 @@
# Autoload Your Files Before Vendor Files
# Composer Preload Files Plugin

[![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)
[![License](https://img.shields.io/packagist/l/codezero/composer-preload-files.svg?style=flat-square)](LICENSE.md)
[![Total Downloads](https://img.shields.io/packagist/dt/codezero/composer-preload-files.svg?style=flat-square)](https://packagist.org/packages/codezero/composer-preload-files)

## Autoload Your Files Before Vendor Files

This Composer plugin enables you to autoload files that you specify before any vendor files.

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).
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.

## ✅ Requirements

- PHP >= 7.0
- Composer ^2.0

## 📦 Install

Install this package with Composer:

```bash
composer require codezero/composer-preload-files
```

## 📘 Usage

Add the `preload-files` to your project's `composer.json` under the `extra` section:

```json
"extra": {
"preload-files": [
"app/helpers.php"
]
},
```

The `preload-files` in the `extra` section will be loaded before the `files` in a standard `autoload` or `autoload-dev` section.
This is true for your project, but also for any vendor package. Your project's preload files will always be loaded first.

## 🔌 Example Use Case

The best example use case is when you need to override a global helper function in a [Laravel](https://laravel.com) project.
Those helper functions are declared in helper files that are loaded in the `files` array in the `autoload` section of `composer.json`:

```json
"autoload": {
"files": [
"src/Illuminate/Collections/helpers.php",
"src/Illuminate/Events/functions.php",
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Support/helpers.php"
]
},
```

These functions are declared like this:

```php
// helpers.php
if ( ! function_exists('route')) {
function route($name, $parameters = [], $absolute = true)
{
return app('url')->route($name, $parameters, $absolute);
}
}
```

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.

One way to solve this, is to manually `require` the helper file before Composer's `autoload.php` file.
For Laravel, this means you need to `require` the file in your project's `public/index.php` file:

```php
require __DIR__.'/../app/helpers.php';
require __DIR__.'/../vendor/autoload.php';
```

This works, but it is difficult, if not impossible to test (I did not find a way yet).
If you are developing a package, it's also an extra step that users need take to install it.

Another solution is a package like this.

## ☕ Credits

- [@ivanvermeyen](https://github.com/ivanvermeyen)
- [@hopeseekr](https://github.com/hopeseekr) - original fork: [hopeseekr-contribs/composer-include-files](https://github.com/hopeseekr-contribs/composer-include-files)
- [@funkjedi](https://github.com/funkjedi) - original: [funkjedi/composer-include-files](https://github.com/funkjedi/composer-include-files)
- [All contributors](https://github.com/codezero-be/composer-preload-files/contributors)

## 🔒 Security

If you discover any security related issues, please [e-mail me](mailto:ivan@codezero.be) instead of using the issue tracker.

## 📑 Changelog

A complete list of all notable changes to this package can be found on the
[releases page](https://github.com/codezero-be/composer-preload-files/releases).

## 📜 License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
166 changes: 166 additions & 0 deletions src/AutoloadGenerator.php
@@ -0,0 +1,166 @@
<?php

namespace CodeZero\ComposerPreloadFiles;

use Composer\Autoload\AutoloadGenerator as ComposerAutoloadGenerator;
use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackage;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;

class AutoloadGenerator extends ComposerAutoloadGenerator
{
/**
* Add preload files to the autoload files.
*
* @param \Composer\Composer $composer
* @param \Composer\IO\IOInterface $io
* @param \Composer\Util\Filesystem $filesystem
*
* @return void
*/
public function addPreloadFilesToAutoloadFiles(Composer $composer, IOInterface $io, Filesystem $filesystem)
{
$preloadFiles = $this->parsePreloadFiles($composer, $filesystem);

if (count($preloadFiles) === 0) {
return;
}

$io->writeError('<info>Adding preload files to the autoload files.</info>');

// Some pathfinding...
// Do not remove double realpath() calls.
// Fixes failing Windows realpath() implementation.
// See https://bugs.php.net/bug.php?id=72738
$basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd())));
$vendorDir = $composer->getConfig()->get('vendor-dir');
$vendorPath = $filesystem->normalizePath(realpath(realpath($vendorDir)));
$targetDir = $vendorPath.'/composer';

$this->prependPreloadFilesToAutoloadFilesFile($filesystem, $preloadFiles, $targetDir, $basePath, $vendorPath);
$this->regenerateAutoloadStaticFile($filesystem, $targetDir, $basePath, $vendorPath);
}

/**
* Prepend preload files to the 'autoload_files.php' file.
*
* @param \Composer\Util\Filesystem $filesystem
* @param array $preloadFiles
* @param string $targetDir
* @param string $basePath
* @param string $vendorPath
*
* @return void
*/
protected function prependPreloadFilesToAutoloadFilesFile(Filesystem $filesystem, $preloadFiles, $targetDir, $basePath, $vendorPath)
{
$vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true);
$appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true);
$appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode);
$autoloadFilesFilePath = $targetDir.'/autoload_files.php';

// Merge preload files with original files.
$originalFiles = $this->getOriginalAutoloadFiles($autoloadFilesFilePath);
$allFiles = array_merge($preloadFiles, $originalFiles);

// Write new 'autoload_files.php'.
$filesystem->filePutContentsIfModified(
$autoloadFilesFilePath,
$this->getIncludeFilesFile($allFiles, $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)
);
}

/**
* Regenerate the 'autoload_static.php' file.
*
* @param \Composer\Util\Filesystem $filesystem
* @param string $targetDir
* @param string $basePath
* @param string $vendorPath
*
* @return void
*/
protected function regenerateAutoloadStaticFile(Filesystem $filesystem, $targetDir, $basePath, $vendorPath)
{
// Get the class name suffix from 'autoload.php'.
// https://github.com/composer/composer/blob/main/src/Composer/Autoload/AutoloadGenerator.php#L390-L392
$autoloadContent = file_get_contents($vendorPath.'/autoload.php');
$suffix = null;
if (Preg::isMatch('{ComposerAutoloaderInit([^:\s]+)::}', $autoloadContent, $match)) {
$suffix = $match[1];
}

// Write new 'autoload_static.php'.
$filesystem->filePutContentsIfModified(
$targetDir.'/autoload_static.php',
$this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath)
);
}

/**
* Get the original files to autoload.
*
* @param string $autoloadFilesFilePath
*
* @return array
*/
protected function getOriginalAutoloadFiles($autoloadFilesFilePath)
{
if (file_exists($autoloadFilesFilePath)) {
return include $autoloadFilesFilePath;
}

return [];
}

/**
* Parse preload files from the root package and all vendor packages.
*
* @param \Composer\Composer $composer
* @param \Composer\Util\Filesystem $filesystem
*
* @return array
*/
protected function parsePreloadFiles(Composer $composer, Filesystem $filesystem)
{
$installationManager = $composer->getInstallationManager();
$preloadFilesKey = 'preload-files';
$preloadFiles = [];

// Do not remove double realpath() calls.
// Fixes failing Windows realpath() implementation.
// See https://bugs.php.net/bug.php?id=72738
$basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd())));

$rootPackage = $composer->getPackage();
$rootPackageConfig = $rootPackage->getExtra();
$rootPackagePreloadFiles = $rootPackageConfig[$preloadFilesKey] ?? [];

foreach ($rootPackagePreloadFiles as $file) {
$identifier = $this->getFileIdentifier($rootPackage, $file);
$preloadFiles[$identifier] = $filesystem->normalizePath($basePath . '/' . $file);
}

$otherPackages = $composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();

foreach ($otherPackages as $package) {
if ( ! ($package instanceof CompletePackage)) {
continue;
}

$packageBaseDir = $filesystem->normalizePath($installationManager->getInstallPath($package));
$packageConfig = $package->getExtra();
$packagePreloadFiles = $packageConfig[$preloadFilesKey] ?? [];

foreach ($packagePreloadFiles as $file) {
$identifier = $this->getFileIdentifier($package, $file);
$preloadFiles[$identifier] = $filesystem->normalizePath($packageBaseDir . '/' . $file);
}
}

return $preloadFiles;
}
}
31 changes: 30 additions & 1 deletion src/Plugin.php
Expand Up @@ -6,6 +6,8 @@
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\ScriptEvents;
use Composer\Util\Filesystem;

class Plugin implements PluginInterface, EventSubscriberInterface
{
Expand All @@ -23,6 +25,13 @@ class Plugin implements PluginInterface, EventSubscriberInterface
*/
protected $io;

/**
* Tells if plugin has run.
*
* @var bool
*/
protected $done;

/**
* Returns an array of event names this subscriber wants to listen to.
*
Expand All @@ -31,10 +40,30 @@ class Plugin implements PluginInterface, EventSubscriberInterface
public static function getSubscribedEvents()
{
return [
//
ScriptEvents::POST_AUTOLOAD_DUMP => 'addPreloadFilesToAutoloadFiles',
ScriptEvents::POST_INSTALL_CMD => 'addPreloadFilesToAutoloadFiles',
];
}

/**
* Add preload files to the autoload files.
*
* @return void
*/
public function addPreloadFilesToAutoloadFiles()
{
// Run only once if multiple events trigger.
if ($this->done === true) {
return;
}

$this->done = true;

$filesystem = new Filesystem();
$generator = new AutoloadGenerator($this->composer->getEventDispatcher(), $this->io);
$generator->addPreloadFilesToAutoloadFiles($this->composer, $this->io, $filesystem);
}

/**
* Apply plugin modifications to Composer.
*
Expand Down

0 comments on commit 1d7e39a

Please sign in to comment.