macfja/composer-class-rewrite

Composer plugin to handle class rewrite

Installs: 39

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 3

Forks: 0

Open Issues: 0

Type:composer-plugin

v0.1.0 2016-01-26 20:44 UTC

This package is auto-updated.

Last update: 2024-02-16 23:43:38 UTC


README

Latest Version Software License Total Downloads

What is Composer Class-Rewrite

Composer Class-Rewrite is a Composer plugin that allow you to rewrite almost[1]1 any classes of your project.

Principle[2]2

The idea is to scan every classes of the project to find classes declared as a rewrite. Then we make some modification on the parent (rewritten) class (a copy of the parent class) and the rewriter class (a copy of the rewriter class) and finally add them to the Composer autoload (before PSR-0 and PSR-4 classes). So, when a class ask for the rewritten class, Composer will return our modified rewiter class.

Installation

With Composer:

$ composer require macfja/composer-class-rewrite

Usage

# File:  example/A.php
namespace Example;
class A
{
    public function who()
    {
    	return 'A';
    }
}
# File:  example/B.php
namespace Example;
class B extends A
{
    public function who()
    {
    	return parent::who() . '-B';
    }
}
# File:  example/C.php
namespace Example;
class C extends A implements \MacFJA\ClassRewrite\Rewriter
{
    public function who()
    {
    	return parent::who() . '-C';
    }
}
$ composer dump-autoload # Mandatory after any changes in Example\A or in Example\C
$b = new B();
echo $b->who(); // Output: "A-C-B"

Limitations

  • Only work for PSR-0 and PSR-4 namespace.
  • Only work if Composer is the first autoloader.
  • Only work on non-dev dependency.
  • Only work on class (not on trait, nor on interface).
  • The rewriter class MUST have the same namespace than the rewritten class.
  • You have to rebuild the autoload for every rewriter/rewritten changes.
  • Have some side effects (see example below)
    • Class context is changed (magic constants).
    • Multiple call if you instanciate the rewriter class.

Side effects

Multiple call

# File:  example/A.php
namespace Example;
class A
{
    public function who()
    {
    	return 'A';
    }
}
# File:  example/B.php
namespace Example;
class B extends A implements \MacFJA\ClassRewrite\Rewriter
{
    public function who()
    {
    	return parent::who() . '-B';
    }
}
# File:  example/C.php
namespace Example;
class C extends B
{
    public function who()
    {
    	return parent::who() . '-C';
    }
}
# File: test.php
require 'vendor/autoload.php';
$class = new A();
echo $class->who(); //output A-B

$class2 = new B();
echo $class2->who(); // output A-B-B
// -> the output is (A='A-B', parent of B) + '-B' (from B)

$class3 = new C();
echo $class3->who(); // output A-B-B-C
// -> the output is (A='A-B', parent of B) + '-B' (from B, parent of C) + '-C' (from C)

Magic constants

# File:  example/A.php
<?php
namespace Example;
class A
{
    public function dir()
    {
    	return __DIR__; // "~/example"
    }
    public function thisClass()
    {
    	return __CLASS__; // "Example\A"
    }
}
# File:  example/B.php
<?php
namespace Example;
class B extends A implements \MacFJA\ClassRewrite\Rewriter
{
    public function dir()
    {
    	return __DIR__; // "~/example"
    }
}
# File: test.php
require 'vendor/autoload.php';
$class = new A();
echo $class->dir(); //output "~/vendor/_rewrite"
echo $class->thisClass(); //output "Example\Cc09c111b433d2b65b9b01c999ae6480874b076a8"
echo get_class($class); //output "Example\A"

Explored ideas

  1. Inject my code during the Composer autoloading. Issue: Composer don't provide event in autoloader.
  2. Change the Composer autoloader code to add my logic. Issue: Change core code. So if Composer change it, he have to change mine too. Hard to maintain.
  3. Prepend a customer autoloader before Composer autoloader. Issue: Loose all the power of Composer autloading.

Under the hood

This plugin use:

  • composer-plugin-api (obvious)
  • nikic/php-parser: For parsing php class, and rewrite them

How it works

Just before Composer autoloader generation, the plugin is parsing every PSR-0 and PSR-4 namespace. It search for class that implement the interface \MacFJA\ClassRewrite\Rewriter, store in memory the the rewriter class and the rewritten class. When the plugin have parse every classes. It rebuild every Rewriter and Rewritten class for rename the Rewritten classname with a hard to guess class name (in fact, it's a sha1 of the source file) and to rename the Rewriter classname into the original Rewritten classname. Finally it add the rewrite destination directory into Composer classmap autoload.
Then it let Composer do it's stuff.

It's works because Composer start searching class in the classmap autoload.

License

The MIT License (MIT). Please see License File for more information.

Footnotes

  1. <#limitations> "See the Limitations section"

  2. <#how-it-works> "For more information on how it works, see the How it works section"