Skip to content

Commit

Permalink
[FEATURE] Register alias loader before autoload dump
Browse files Browse the repository at this point in the history
In order to have the class alias loader present in
post autoload execution, we now register it before
Composer generates autoloading.

As a side effect, we can remove the autoload.php rewrite hack.
  • Loading branch information
helhum committed Apr 29, 2020
1 parent ff95ef1 commit a63a582
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 128 deletions.
9 changes: 9 additions & 0 deletions res/php/alias-loader-include.tmpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

$composerAutoLoader = require dirname(__DIR__) . '/autoload.php';
$classAliasMap = require dirname(__DIR__) . '/composer/autoload_classaliasmap.php';
$classAliasLoader = new TYPO3\ClassAliasLoader\ClassAliasLoader($composerAutoLoader);
$classAliasLoader->setAliasMap($classAliasMap);
$classAliasLoader->setCaseSensitiveClassLoading('{$sensitive-loading}');
$classAliasLoader->register('{$prepend}');
TYPO3\ClassAliasLoader\ClassAliasMap::setClassAliasLoader($classAliasLoader);
173 changes: 57 additions & 116 deletions src/ClassAliasMapGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
*/

use Composer\Composer;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use TYPO3\ClassAliasLoader\Config;
use TYPO3\ClassAliasLoader\IncludeFile\CaseSensitiveToken;
use TYPO3\ClassAliasLoader\IncludeFile\PrependToken;

/**
* This class loops over all packages that are installed by composer and
Expand All @@ -36,34 +38,32 @@ class ClassAliasMapGenerator
protected $io;

/**
* @var bool
* @var Config
*/
protected $optimizeAutoloadFiles = false;
private $config;

/**
* @param Composer $composer
* @param IOInterface $io
* @param bool $optimizeAutoloadFiles
*/
public function __construct(Composer $composer, IOInterface $io = null, $optimizeAutoloadFiles = false)
public function __construct(Composer $composer, IOInterface $io = null, Config $config = null)
{
$this->composer = $composer;
$this->io = $io ?: new NullIO();
$this->optimizeAutoloadFiles = $optimizeAutoloadFiles;
$this->config = $config ?: new Config($this->composer->getPackage());
}

/**
* @return bool
* @throws \Exception
* @return bool
*/
public function generateAliasMap()
public function generateAliasMapFiles()
{
$config = $this->composer->getConfig();

$filesystem = new Filesystem();
$filesystem->ensureDirectoryExists($config->get('vendor-dir'));
$basePath = $this->extractBasePath($config);
$vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
$basePath = $filesystem->normalizePath(substr($config->get('vendor-dir'), 0, -strlen($config->get('vendor-dir', $config::RELATIVE_PATHS))));
$vendorPath = $config->get('vendor-dir');
$targetDir = $vendorPath . '/composer';
$filesystem->ensureDirectoryExists($targetDir);

Expand All @@ -79,7 +79,7 @@ public function generateAliasMap()
foreach ($packageMap as $item) {
/** @var PackageInterface $package */
list($package, $installPath) = $item;
$aliasLoaderConfig = new \TYPO3\ClassAliasLoader\Config($package, $this->io);
$aliasLoaderConfig = new Config($package, $this->io);
if ($aliasLoaderConfig->get('class-alias-maps') !== null) {
if (!is_array($aliasLoaderConfig->get('class-alias-maps'))) {
throw new \Exception('Configuration option "class-alias-maps" must be an array');
Expand All @@ -88,118 +88,73 @@ public function generateAliasMap()
$mapFilePath = ($installPath ?: $basePath) . '/' . $filesystem->normalizePath($mapFile);
if (!is_file($mapFilePath)) {
$this->io->writeError(sprintf('The class alias map file "%s" configured in package "%s" was not found!', $mapFile, $package->getName()));
} else {
$packageAliasMap = require $mapFilePath;
if (!is_array($packageAliasMap)) {
throw new \Exception('Class alias map files must return an array', 1422625075);
}
if (!empty($packageAliasMap)) {
$classAliasMappingFound = true;
}
foreach ($packageAliasMap as $aliasClassName => $className) {
$lowerCasedAliasClassName = strtolower($aliasClassName);
$aliasToClassNameMapping[$lowerCasedAliasClassName] = $className;
$classNameToAliasMapping[$className][$lowerCasedAliasClassName] = $lowerCasedAliasClassName;
}
continue;
}
$packageAliasMap = require $mapFilePath;
if (!is_array($packageAliasMap)) {
throw new \Exception('Class alias map files must return an array', 1422625075);
}
if (!empty($packageAliasMap)) {
$classAliasMappingFound = true;
}
foreach ($packageAliasMap as $aliasClassName => $className) {
$lowerCasedAliasClassName = strtolower($aliasClassName);
$aliasToClassNameMapping[$lowerCasedAliasClassName] = $className;
$classNameToAliasMapping[$className][$lowerCasedAliasClassName] = $lowerCasedAliasClassName;
}
}
}
}

$mainPackageAliasLoaderConfig = new \TYPO3\ClassAliasLoader\Config($mainPackage);
$alwaysAddAliasLoader = $mainPackageAliasLoaderConfig->get('always-add-alias-loader');
$caseSensitiveClassLoading = $mainPackageAliasLoaderConfig->get('autoload-case-sensitivity');
$alwaysAddAliasLoader = $this->config->get('always-add-alias-loader');
$caseSensitiveClassLoading = $this->config->get('autoload-case-sensitivity');

if (!$alwaysAddAliasLoader && !$classAliasMappingFound && $caseSensitiveClassLoading) {
// No mapping found in any package and no insensitive class loading active. We return early and skip rewriting
// Unless user configured alias loader to be always added
return false;
}

$caseSensitiveClassLoadingString = $caseSensitiveClassLoading ? 'true' : 'false';
$includeFile = new IncludeFile(
$this->io,
$this->composer,
array(
new CaseSensitiveToken(
$this->io,
$this->config
),
new PrependToken(
$this->io,
$this->composer->getConfig()
),
)
);
$includeFile->register();

$this->io->write('<info>Generating ' . ($classAliasMappingFound ? '' : 'empty ') . 'class alias map file</info>');
$this->generateAliasMapFile($aliasToClassNameMapping, $classNameToAliasMapping, $targetDir);

$suffix = null;
if (!$config->get('autoloader-suffix') && is_readable($vendorPath . '/autoload.php')) {
$content = file_get_contents($vendorPath . '/autoload.php');
if (preg_match('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) {
$suffix = $match[1];
}
}

if (!$suffix) {
$suffix = $config->get('autoloader-suffix') ?: md5(uniqid('', true));
}

$prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';

$aliasLoaderInitClassContent = <<<EOF
<?php
// autoload_alias_loader_real.php @generated by typo3/class-alias-loader
class ClassAliasLoaderInit$suffix {
private static \$loader;
public static function initializeClassAliasLoader(\$composerClassLoader) {
if (null !== self::\$loader) {
return self::\$loader;
}
self::\$loader = \$composerClassLoader;
\$classAliasMap = require __DIR__ . '/autoload_classaliasmap.php';
\$classAliasLoader = new TYPO3\ClassAliasLoader\ClassAliasLoader(\$composerClassLoader);
\$classAliasLoader->setAliasMap(\$classAliasMap);
\$classAliasLoader->setCaseSensitiveClassLoading($caseSensitiveClassLoadingString);
\$classAliasLoader->register($prependAutoloader);
TYPO3\ClassAliasLoader\ClassAliasMap::setClassAliasLoader(\$classAliasLoader);
return self::\$loader;
}
}
EOF;
file_put_contents($targetDir . '/autoload_alias_loader_real.php', $aliasLoaderInitClassContent);

if (!$caseSensitiveClassLoading) {
$this->io->write('<info>Re-writing class map to support case insensitive class loading</info>');
if (!$this->optimizeAutoloadFiles) {
$this->io->write('<warning>Case insensitive class loading only works reliably if you use the optimize class loading feature of composer</warning>');
}
$this->rewriteClassMapWithLowerCaseClassNames($targetDir);
}

$this->io->write('<info>Inserting class alias loader into main autoload.php file</info>');
$this->modifyMainAutoloadFile($vendorPath . '/autoload.php', $suffix);

return true;
}

/**
* @param $autoloadFile
* @param string $suffix
* @deprecated will be removed with 2.0
* @param $optimizeAutoloadFiles
* @return bool
*/
protected function modifyMainAutoloadFile($autoloadFile, $suffix)
public function modifyComposerGeneratedFiles($optimizeAutoloadFiles = false)
{
$originalAutoloadFileContent = file_get_contents($autoloadFile);
preg_match('/return ComposerAutoloaderInit[^;]*;/', $originalAutoloadFileContent, $matches);
$originalAutoloadFileContent = str_replace($matches[0], '', $originalAutoloadFileContent);
$composerClassLoaderInit = str_replace(array('return ', ';'), '', $matches[0]);
$autoloadFileContent = <<<EOF
$originalAutoloadFileContent
// autoload.php @generated by typo3/class-alias-loader
require_once __DIR__ . '/composer/autoload_alias_loader_real.php';
return ClassAliasLoaderInit$suffix::initializeClassAliasLoader($composerClassLoaderInit);
EOF;
$caseSensitiveClassLoading = $this->config->get('autoload-case-sensitivity');
$vendorPath = $this->composer->getConfig()->get('vendor-dir');
if (!$caseSensitiveClassLoading) {
$this->io->writeError('<warning>Re-writing class map to support case insensitive class loading is deprecated</warning>');
if (!$optimizeAutoloadFiles) {
$this->io->writeError('<warning>Case insensitive class loading only works reliably if you use the optimize class loading feature of composer</warning>');
}
$this->rewriteClassMapWithLowerCaseClassNames($vendorPath . '/composer');
}

file_put_contents($autoloadFile, $autoloadFileContent);
return true;
}

/**
Expand Down Expand Up @@ -235,18 +190,4 @@ protected function rewriteClassMapWithLowerCaseClassNames($targetDir)
}, $classMapContents);
file_put_contents($targetDir . '/autoload_classmap.php', $classMapContents);
}

/**
* Extracts the base path out of composer config
*
* @param \Composer\Config $config
* @return mixed
*/
protected function extractBasePath(\Composer\Config $config)
{
$reflectionClass = new \ReflectionClass($config);
$reflectionProperty = $reflectionClass->getProperty('baseDir');
$reflectionProperty->setAccessible(true);
return $reflectionProperty->getValue($config);
}
}
103 changes: 103 additions & 0 deletions src/IncludeFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php
namespace TYPO3\ClassAliasLoader;

/*
* This file is part of the class alias loader package.
*
* (c) Helmut Hummel <info@helhum.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use TYPO3\CMS\Composer\Plugin\Core\IncludeFile\TokenInterface;

class IncludeFile
{
const INCLUDE_FILE = '/typo3/alias-loader-include.php';
const INCLUDE_FILE_TEMPLATE = '/res/php/alias-loader-include.tmpl.php';

/**
* @var TokenInterface[]
*/
private $tokens;

/**
* @var Filesystem
*/
private $filesystem;
/**
* @var IOInterface
*/
private $io;
/**
* @var Composer
*/
private $composer;

/**
* IncludeFile constructor.
*
* @param IOInterface $io
* @param Composer $composer
* @param TokenInterface[] $tokens
* @param Filesystem $filesystem
*/
public function __construct(IOInterface $io, Composer $composer, array $tokens, Filesystem $filesystem = null)
{
$this->io = $io;
$this->composer = $composer;
$this->tokens = $tokens;
$this->filesystem = $filesystem ?: new Filesystem();
}

public function register()
{
$this->io->writeError('<info>Register typo3/class-alias-loader file in root package autoload definition</info>', true, IOInterface::VERBOSE);

// Generate and write the file
$includeFile = $this->composer->getConfig()->get('vendor-dir') . self::INCLUDE_FILE;
file_put_contents($includeFile, $this->getIncludeFileContent(dirname($includeFile)));

// Register the file in the root package
$rootPackage = $this->composer->getPackage();
$autoloadDefinition = $rootPackage->getAutoload();
$autoloadDefinition['files'][] = $includeFile;
$rootPackage->setAutoload($autoloadDefinition);
}

/**
* Constructs the include file content
*
* @param string $includeFilePath
* @throws \RuntimeException
* @throws \InvalidArgumentException
* @return string
*/
protected function getIncludeFileContent(string $includeFilePath)
{
$includeFileTemplate = $this->filesystem->normalizePath(dirname(__DIR__) . self::INCLUDE_FILE_TEMPLATE);
$includeFileContent = file_get_contents($includeFileTemplate);
foreach ($this->tokens as $token) {
$includeFileContent = self::replaceToken($token->getName(), $token->getContent($includeFilePath), $includeFileContent);
}

return $includeFileContent;
}

/**
* Replaces a token in the subject (PHP code)
*
* @param string $name
* @param string $content
* @param string $subject
* @return string
*/
private static function replaceToken($name, $content, $subject)
{
return str_replace('\'{$' . $name . '}\'', $content, $subject);
}
}

0 comments on commit a63a582

Please sign in to comment.