asmblah/php-code-shift

Installs: 6 323

Dependents: 6

Suggesters: 0

Security: 0

Stars: 1

Watchers: 2

Forks: 0

Open Issues: 0

Type:project

v0.1.16 2024-09-11 06:42 UTC

README

Build Status

Allows running PHP code to be monkey-patched either ahead of time or on the fly.

Why?

To allow stubbing of built-in functions during testing, for example.

Usage

Install this package with Composer:

$ composer require asmblah/php-code-shift

Hooking built-in functions

runner.php

<?php

use Asmblah\PhpCodeShift\CodeShift;
use Asmblah\PhpCodeShift\Shifter\Filter\FileFilter;
use Asmblah\PhpCodeShift\Shifter\Shift\Shift\FunctionHook\FunctionHookShiftSpec;

require_once __DIR__ . '/vendor/autoload.php';

$codeShift = new CodeShift();

$codeShift->shift(
    new FunctionHookShiftSpec(
        'substr',
        function (callable $originalSubstr) {
            return function (string $string, int $offset, ?int $length = null) use ($originalSubstr) {
                return '[substr<' . $originalSubstr($string, $offset, $length) . '>]';
            };
        }
    ),
    new FileFilter(__DIR__ . '/substr_test.php')
);

include __DIR__ . '/substr_test.php';

substr_test.php

<?php
// NB: substr(...) will be hooked by the shift defined inside runner.php.
$myResult = substr('my string', 1, 4) . ' and ' . substr('your string', 1, 2);

print $myResult;

The output will be:

[substr<y st>] and [substr<ou>]

Hooking classes

References to a class may be replaced with references to a different class. This only works for statically-referenced classes, i.e. where it is referenced with a bareword, e.g. new MyClass.

Dynamic/variable references are not supported, e.g. new $myClassName as they can only be resolved at runtime.

Any matching types are not replaced - the replacement class must extend the original class or interface in order to pass type checks.

runner.php

<?php

use Asmblah\PhpCodeShift\CodeShift;
use Asmblah\PhpCodeShift\Shifter\Filter\FileFilter;
use Asmblah\PhpCodeShift\Shifter\Shift\Shift\ClassHook\ClassHookShiftSpec;

require_once __DIR__ . '/vendor/autoload.php';

$codeShift = new CodeShift();

$codeShift->shift(
    new ClassHookShiftSpec(
        'MyClass',
        'MyReplacementClass'
    ),
    new FileFilter(__DIR__ . '/class_test.php')
);

include __DIR__ . '/class_test.php';

class_test.php

<?php

class MyClass
{
    public function getIt(): string
    {
        return 'my original string';
    }
}

class MyReplacementClass
{
    public function getIt(): string
    {
        return 'my replacement string';
    }
}

// NB: References to MyClass will be hooked by the shift defined inside runner.php.
$myObject = new MyClass;

print $myObject->getIt();

The output will be:

my replacement string

Static method calls (MyClass::myStaticMethod()) and class constant fetches (MyClass:MY_CONST) are also supported.

Limitations

Functionality is extremely limited at the moment, you may well be better off using one of the alternatives listed in See also below.

  • Does not yet support eval(...).
  • FunctionHookShiftType does not yet support variable function calls.
  • FunctionHookShiftType does not yet support call_user_func(...) and friends, nor any other functions accepting callable parameters that may refer to functions.

See also