Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Classmap implementation #4

Merged
merged 15 commits into from
Mar 24, 2017
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Composer/Autoload/Autoloader.php
Original file line number Diff line number Diff line change
@@ -4,5 +4,5 @@

interface Autoloader
{

public function processConfig( $config );
}
16 changes: 16 additions & 0 deletions src/Composer/Autoload/Classmap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace CoenJacobs\Mozart\Composer\Autoload;

class Classmap implements Autoloader
{
/** @var array */
public $paths = [];
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Classmap autoloader should support having files in the array, not just directories. Potentially look into splitting the $paths variable up in $directories and $files.


public function processConfig($config)
{
foreach( $config as $value) {
array_push( $this->paths, $value);
}
}
}
30 changes: 30 additions & 0 deletions src/Composer/Autoload/NamespaceAutoloader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace CoenJacobs\Mozart\Composer\Autoload;

abstract class NamespaceAutoloader implements Autoloader
{
/** @var string */
public $namespace = '';

/** @var array */
public $paths = [];

public function processConfig($config)
{
foreach( $config as $key => $value) {
$this->namespace = $key;
array_push( $this->paths, $value);
}
}

public function getSearchNamespace()
{
return $this->namespace;
}

public function getNamespacePath()
{
return '';
}
}
7 changes: 1 addition & 6 deletions src/Composer/Autoload/Psr0.php
Original file line number Diff line number Diff line change
@@ -2,11 +2,6 @@

namespace CoenJacobs\Mozart\Composer\Autoload;

class Psr0 implements Autoloader
class Psr0 extends NamespaceAutoloader
{
/** @var string */
public $namespace = '';

/** @var array */
public $paths = [];
}
14 changes: 9 additions & 5 deletions src/Composer/Autoload/Psr4.php
Original file line number Diff line number Diff line change
@@ -2,11 +2,15 @@

namespace CoenJacobs\Mozart\Composer\Autoload;

class Psr4 implements Autoloader
class Psr4 extends NamespaceAutoloader
{
/** @var string */
public $namespace = '';
public function getSearchNamespace()
{
return trim($this->namespace, '\\');
}

/** @var array */
public $paths = [];
public function getNamespacePath()
{
return str_replace('\\', '/', $this->namespace);
}
}
14 changes: 7 additions & 7 deletions src/Composer/Package.php
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

namespace CoenJacobs\Mozart\Composer;

use CoenJacobs\Mozart\Composer\Autoload\Autoloader;

class Package
{
/** @var string */
@@ -22,8 +24,9 @@ public function __construct( $path )
public function findAutoloaders()
{
$namespace_autoloaders = array(
'psr-0' => 'CoenJacobs\Mozart\Composer\Autoload\Psr0',
'psr-4' => 'CoenJacobs\Mozart\Composer\Autoload\Psr4',
'psr-0' => 'CoenJacobs\Mozart\Composer\Autoload\Psr0',
'psr-4' => 'CoenJacobs\Mozart\Composer\Autoload\Psr4',
'classmap' => 'CoenJacobs\Mozart\Composer\Autoload\Classmap',
);

if ( ! isset( $this->config->autoload ) ) return;
@@ -33,12 +36,9 @@ public function findAutoloaders()

$autoconfigs = (array)$this->config->autoload->$key;

/** @var $autoloader Autoloader */
$autoloader = new $value();

foreach( $autoconfigs as $key2 => $value2) {
$autoloader->namespace = $key2;
array_push( $autoloader->paths, $value2);
}
$autoloader->processConfig($autoconfigs);

array_push($this->autoloaders, $autoloader);
}
4 changes: 3 additions & 1 deletion src/Console/Commands/Compose.php
Original file line number Diff line number Diff line change
@@ -25,12 +25,14 @@ protected function execute(InputInterface $input, OutputInterface $output)
$config = $config->extra->mozart;

$mover = new Mover($workingDir, $config);
$mover->deleteTargetDir();
$mover->deleteTargetDirs();

foreach( $config->packages as $package_slug ) {
$package = new Package($workingDir . '/vendor/' . $package_slug);
$package->findAutoloaders();
$mover->movePackage($package);
}

$mover->replaceClassmapNames();
}
}
78 changes: 54 additions & 24 deletions src/Mover.php
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@

namespace CoenJacobs\Mozart;

use CoenJacobs\Mozart\Composer\Autoload\Psr0;
use CoenJacobs\Mozart\Composer\Autoload\Psr4;
use CoenJacobs\Mozart\Composer\Autoload\NamespaceAutoloader;
use CoenJacobs\Mozart\Composer\Package;
use CoenJacobs\Mozart\Replace\ClassmapReplacer;
use CoenJacobs\Mozart\Replace\NamespaceReplacer;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use Symfony\Component\Finder\Finder;
@@ -20,17 +21,21 @@ class Mover
/** @var \stdClass */
protected $config;

/** @var array */
protected $replacedClasses = [];

public function __construct( $workingDir, $config )
{
$this->workingDir = $workingDir;
$this->targetDir = $config->dep_directory;
$this->config = $config;
}

public function deleteTargetDir()
public function deleteTargetDirs()
{
$filesystem = new Filesystem(new Local($this->workingDir));
$filesystem->deleteDir($this->targetDir);
$filesystem->deleteDir($this->config->dep_directory);
$filesystem->deleteDir($this->config->classmap_directory);
}

public function movePackage(Package $package)
@@ -39,23 +44,17 @@ public function movePackage(Package $package)
$filesystem = new Filesystem(new Local($this->workingDir));

foreach( $package->autoloaders as $autoloader ) {
foreach( $autoloader->paths as $path ) {
foreach ($autoloader->paths as $path) {
$source_path = $this->workingDir . '/vendor/' . $package->config->name . '/' . $path;

$finder->files()->in($source_path);

$searchNamespace = $autoloader->namespace;

if ( is_a($autoloader, Psr4::class)) {
$searchNamespace = trim($autoloader->namespace, '\\');
}

foreach ($finder as $file) {
if ( is_a($autoloader, Psr0::class)) {
$targetFile = str_replace($this->workingDir, $this->config->dep_directory, $file->getRealPath());
} else {
$namespacePath = str_replace('\\', '/', $autoloader->namespace);
if (is_a($autoloader, NamespaceAutoloader::class)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should prefer instanceof operator instead of is_a() function. The operator doesn't incur a function overhead, and is_a() was only de-deprecated because of popular request. It should have been gone already.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed: 05fea7d

$namespacePath = $autoloader->getNamespacePath();
$targetFile = str_replace($this->workingDir, $this->config->dep_directory . $namespacePath, $file->getRealPath());
} else {
$targetFile = str_replace($this->workingDir, $this->config->classmap_directory, $file->getRealPath());
}

$targetFile = str_replace('/vendor/' . $package->config->name . '/' . $path, '', $targetFile);
@@ -65,22 +64,53 @@ public function movePackage(Package $package)
$targetFile
);

if ( '.php' == substr($targetFile, '-4', 4 ) ) {
if ('.php' == substr($targetFile, '-4', 4)) {
$contents = $filesystem->read($targetFile);

$contents = preg_replace_callback(
'/'.addslashes($searchNamespace).'([\\\|;])/U',
function($matches) {
$replace = trim($matches[0], $matches[1]);
return $this->config->dep_namespace . $replace . $matches[1];
},
$contents
);
if (is_a($autoloader, NamespaceAutoloader::class)) {
$replacer = new NamespaceReplacer();
$replacer->dep_namespace = $this->config->dep_namespace;
} else {
$replacer = new ClassmapReplacer();
}

$replacer->setAutoloader($autoloader);
$contents = $replacer->replace($contents);

if ( is_a($replacer, ClassmapReplacer::class)) {
$this->replacedClasses = array_merge($this->replacedClasses, $replacer->replacedClasses);
}

$filesystem->put($targetFile, $contents);
}
}
}
}
}

public function replaceClassmapNames()
{
$classmap_path = $this->workingDir . $this->config->classmap_directory;
$finder = new Finder();
$finder->files()->in($classmap_path);

$filesystem = new Filesystem(new Local($this->workingDir));

foreach ($finder as $file) {
$file_path = str_replace($this->workingDir, '', $file->getRealPath());
$contents = $filesystem->read($file_path);

foreach( $this->replacedClasses as $original => $replacement ) {
$contents = preg_replace_callback(
'/\W('.$original.')(?:\(|\:\:)/U',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will miss a couple of scenarios.

The ones I can think of right now:

  1. type-hints: public function __construct( MyClass $obj );
  2. new without arguments: $obj = new MyClass;
  3. class in string: $class = 'MyClass'; $obj = new $class();

It will also probably break when using relative namespaces, like the following:

namespace Vendor;
throw new Exception\MyException(); // FQCN: \Vendor\Exception\MyException

I'm not really sure how you can add cases like these without getting false positives as well, though. I don't think this can properly be handled by regexes, you should look into using nikic/PHP-Parser perhaps.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @schlessera, I would love to make this as bullet proof as I can, but don't want this to distract too much from actually implementing the classmap handler. Improving the replacing logic can obviously be an ongoing process. Can you please post this as an issue, so I can handle it accordingly ( - it would be amazing if the GitHub UI allowed me to promote this comment to an issue, directly from here)?

function ($matches) use ($replacement) {
return str_replace($matches[1], $replacement, $matches[0]);
},
$contents
);
}

$filesystem->put($file_path, $contents);
}
}
}
16 changes: 16 additions & 0 deletions src/Replace/BaseReplacer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace CoenJacobs\Mozart\Replace;

use CoenJacobs\Mozart\Composer\Autoload\Autoloader;

abstract class BaseReplacer implements Replacer
{
/** @var Autoloader */
public $autoloader;

public function setAutoloader( $autoloader )
{
$this->autoloader = $autoloader;
}
}
27 changes: 27 additions & 0 deletions src/Replace/ClassmapReplacer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace CoenJacobs\Mozart\Replace;

class ClassmapReplacer extends BaseReplacer
{
/** @var array */
public $replacedClasses = [];

public function replace( $contents )
{
return preg_replace_callback(
'/(?:\W[abstract]*class |interface )([a-zA-Z\_]+)[ ]*{/U',
function ($matches) {
$replace = 'CJDT_' . $matches[1];
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CJDT_ prefix is hardcoded here, needs to be replaced by a config variable.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: a35ee8c

$this->saveReplacedClass($matches[1], $replace);
return str_replace($matches[1], $replace, $matches[0]);
},
$contents
);
}

public function saveReplacedClass($classname, $replacedName)
{
$this->replacedClasses[ $classname ] = $replacedName;
}
}
23 changes: 23 additions & 0 deletions src/Replace/NamespaceReplacer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace CoenJacobs\Mozart\Replace;

class NamespaceReplacer extends BaseReplacer
{
/** @var string */
public $dep_namespace = '';

public function replace($contents)
{
$searchNamespace = $this->autoloader->getSearchNamespace();

return preg_replace_callback(
'/' . addslashes($searchNamespace) . '([\\\|;])/U',
function ($matches) {
$replace = trim($matches[0], $matches[1]);
return $this->dep_namespace . $replace . $matches[1];
},
$contents
);
}
}
9 changes: 9 additions & 0 deletions src/Replace/Replacer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace CoenJacobs\Mozart\Replace;

interface Replacer
{
public function setAutoloader($autoloader);
public function replace($contents);
}