Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate or modify a PHP class at runtime?

The schmittjoh/cg-library seems what I need, but there is no documentation at all.

This library provides some tools that you commonly need for generating PHP code. One of it's strength lies in the enhancement of existing classes with behaviors.

Given A class:

class A {}

I'd like to modify, at runtime of course and with some cache mechanism, class A, making it implementing a given interface:

interface I
{
    public function mustImplement();
}

... with a "default" implementation for method mustImplement() in A class.

like image 951
gremo Avatar asked Jun 27 '26 23:06

gremo


2 Answers

You can also use a Role Object pattern and good old aggregation.

Instead of having smart Entities that contain all your business logic, you make them dumb and move all the business logic into Roles that aggregate the dumb Entities then. Your behaviors are then first-class citizens living inside the Roles.

Example:

interface BannableUser
{
    public function ban();
}

Having an interface with one particular behavior follows the Interface Segregation Principle. It also dramatically increases possible reuse since you are more likely to reuse individual behaviors than an Entity with an application-specific collection of behaviors.

Now to implement that, you create an appropriate Role Class:

class BannableUserRole implements BannableUser
{
     private $user;

     public function __construct(User $user)
     {
         $this->user = $user;
     }

     public function ban()
     {
         $this->user->isBanned = true;
     }
}

You still have a User entity but it's completely stripped of all behaviors. It's essentially just a bag of Getters and Setters or public properties. It represents what your System is. It's the data part, not the interaction part. The interaction is inside the Roles now.

class User
{
    public $isBanned;

    // … more properties
}

Now assuming you have some sort of Web UI, you can do the following in your controller:

class BanUserController implements RequestHandler
{
    // …

    public function handleRequest(Request $request)
    {
        $userId = $request->getVar('user_id');
        $user = $this->userRepository->findById($userId);
        $bannableUser = new BannableUserRole($user);
        $bannableUser->ban();
    }
}

You can decouple this further by moving the actual lookup of the User and assignment of the Role into a UseCase class. Let's call it Context:

class BanUserContext implements Context
{
    public function run($userId)
    {
        $user = $this->userRepository->findById($userId);
        $bannableUser = new BannableUserRole($user);
        $bannableUser->ban();
    }
}

Now you have all the business logic inside your Model layer and fully isolated from your User Interface. The Contexts are what your system does. Your Controller will only delegate to the appropriate Context:

class BanUserController implements RequestHandler
{
    // …

    public function handleRequest(Request $request)
    {
        $this->banUserContext->run($request->getVar('user_id'));

    }
}

And that's it. No need for Runkit or similar hackery. The above is a simplified version of the Data Context Interaction architectural pattern, in case you want to further research this.

like image 51
Gordon Avatar answered Jun 30 '26 13:06

Gordon


Note: OP needs PHP 5.3 (it was not tagged that way before), this question is a general outline for PHP 5.4.

You can do that with defining the interface and adding traits that contain the default implementation for those interfaces.

Then you create a new class definition that

  • extends from your base-class,
  • implements that interface and
  • uses the default traits.

For an example, see Traits in PHP – any real world examples/best practices?

You can easily generate that class definition code and either store it and include it or eval it straight ahead.

If you make the new classname containing all the information it consists of (in your case the base classname and the interface), you can prevent to create duplicate class definitions easily.

This works without any PHP extension like runkit. If you bring serialize into the play, you can even overload existing objects at runtime with the new interface in case they can serialize / deserialize.

You can find a code-example that has been implementing this in:

  • DCI-Account-Example-in-PHP / src / DCI / Casting.php (usage)
like image 22
hakre Avatar answered Jun 30 '26 12:06

hakre



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!