Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modifying a PHP class file or stub programmatically

Tags:

php

laravel

Given a PSR-2 compliant PHP class file (or stub)

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Car extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        //
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        //
    ];
}

Is there some tool to easily parse and modify it? In the best of worlds with a fluent API, maybe something like:

PhpClass::make(".../Car.php")
    ->setNamespace("Some/New/Namespace")
    ->use("Some\Dependency")
    ->addMethod($newFunctionBody)

Im aware this might be a naive example, but how close to such a tool is there out there? By tool I mean something I can pull in as a dependency preferably via composer.

Right now I have a somewhat working solution using placeholders and regex but it starts to grow out of control with many edge cases. Therefore im thinking I might need to actually interpret the PHP.

like image 314
ajthinking Avatar asked Dec 20 '25 17:12

ajthinking


2 Answers

Componere

Componere is a PHP 7 extension, available on pecl and documented in the PHP manual ...

The code below registers the definition in the class entry table:

<?php
/* just making the example standalone */
class Model {}

class Car extends Model {
    public function something() {}      
}

trait SaysVroomVroomWhenPoked {

    public function poke() {
        return "Vroom Vroom";
    }
}

$def = new \Componere\Definition(Some\Place\Car::class, Car::class);

$def->addTrait(SaysVroomVroomWhenPoked::class);

$def->addMethod("newMethod", new \Componere\Method(function(){
    return 42;
}));

$def->register();

$car = new \Some\Place\Car();

printf("%s and %d\n", $car->poke(), $car->newMethod());
?>

Registering the class in the entry table may be desirable in some cases, it's effects are application wide for the life of the request.

The code below patches a particular instance of an object with the required features:

<?php
/* just making the example standalone */
class Model {}

class Car extends Model {
    public function something() {}      
}

trait SaysVroomVroomWhenPoked {

    public function poke() {
        return "Vroom Vroom";
    }
}

function accept(Car $car) {
    $patch = new \Componere\Patch($car);
    $patch->addTrait(SaysVroomVroomWhenPoked::class);

    $patch->addMethod("newMethod", new \Componere\Method(function(){
        return 42;
    }));
    $patch->apply();

    printf("%s and %d\n", $car->poke(), $car->newMethod());
}

$car = new Car();

accept($car);

var_dump(method_exists($car, "newMethod")); # false
?>

Note that doing this everywhere is much less efficient than changing the definition, but it's side effects disappear when the accept function returns.

http://php.net/componere

Note: it has a fluid interface, but for the purposes of clarity I did not use it here ...

Edit:

I just noticed your comment expressing an interest in pre-processing code, or possibly using anonymous classes, and not runtime manipulation. Using anonymous classes is an excellent idea, but I don't think you need an example of that ...

Full disclosure: I wrote Componere, and I wrote the implementation of anonymous classes for PHP ...

Anything I say to dissuade you from pre-processing is probably going to be pretty empty now ...

like image 180
Joe Watkins Avatar answered Dec 23 '25 07:12

Joe Watkins


I built a Laravel package for this use case: https://github.com/ajthinking/php-file-manipulator

like image 29
ajthinking Avatar answered Dec 23 '25 05:12

ajthinking