How to generate or modify a PHP class at runtime?

徘徊边缘 提交于 2019-12-20 03:29:07

问题


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.


回答1:


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)



回答2:


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.




回答3:


I needed a tool to edit PHP classes (specifically Doctrine Entities), I could not find it so I created one that might help you, I documented it heavily. So if you want to give it a try, I'll happily help.

Here it is, SourceEditor

It gives you an API like this:

    /* @var $classEditor DocDigital\Lib\SourceEditor\PhpClassEditor */
    $classEditor->parseFile($classPath);
    $classEditor->getClass('a')->getMethod('b')->addAnnotation('@auth Juan Manuel Fernandez <juanmf@gmail.com>');
    $classEditor->getClass('a')->getAttribute('attr')->addAnnotation('@Assert\Choice(...)');
    $classEditor->getClass('a')->addAttribute($attr2);
    $classEditor->getClass('a')->addUse('use DocDigital\Bundle\DocumentBundle\DocumentGenerator\Annotation as DdMapping;');
    $classEditor->getClass('a')->addConst('    const CONSTANT = 1;');

    file_put_contents($classPath, $classEditor->getClass('a')->render(false));



回答4:


Runkit extension can help you

Runkit_Sandbox — Runkit Sandbox Class -- PHP Virtual Machine
Runkit_Sandbox_Parent — Runkit Anti-Sandbox Class
runkit_class_adopt — Convert a base class to an inherited class, add ancestral methods when appropriate
runkit_class_emancipate — Convert an inherited class to a base class, removes any method whose scope is ancestral
runkit_constant_add — Similar to define(), but allows defining in class definitions as well
runkit_constant_redefine — Redefine an already defined constant
runkit_constant_remove — Remove/Delete an already defined constant
runkit_function_add — Add a new function, similar to create_function
runkit_function_copy — Copy a function to a new function name
runkit_function_redefine — Replace a function definition with a new implementation
runkit_function_remove — Remove a function definition
runkit_function_rename — Change the name of a function
runkit_import — Process a PHP file importing function and class definitions, overwriting where appropriate
runkit_lint_file — Check the PHP syntax of the specified file
runkit_lint — Check the PHP syntax of the specified php code
runkit_method_add — Dynamically adds a new method to a given class
runkit_method_copy — Copies a method from class to another
runkit_method_redefine — Dynamically changes the code of the given method
runkit_method_remove — Dynamically removes the given method
runkit_method_rename — Dynamically changes the name of the given method
runkit_return_value_used — Determines if the current functions return value will be used
runkit_sandbox_output_handler — Specify a function to capture and/or process output from a runkit sandbox
runkit_superglobals — Return numerically indexed array of registered superglobals



回答5:


You can look: https://github.com/ptrofimov/jslikeobject

Author implemented dynamic JS-like objects with support of inheritance.

But it is more like a joke than real proposition.



来源:https://stackoverflow.com/questions/12651179/how-to-generate-or-modify-a-php-class-at-runtime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!