简体   繁体   English

是否可以动态扩展 class?

[英]Is it possible to extend a class dynamically?

I have a class which I need to use to extend different classes (up to hundreds) depending on criteria.我有一个 class,我需要根据标准使用它来扩展不同的类(最多数百个)。 Is there a way in PHP to extend a class by a dynamic class name? PHP 中有没有办法通过动态 class 名称扩展 class?

I assume it would require a method to specify extension with instantiation.我假设它需要一种方法来指定实例化的扩展。

Ideas?想法?

While it's still not possible and not exactly your answer i needed the same thing and didn't wanted to use eval, monkey-patching etc. So i used a default class by extending it in conditions.虽然这仍然不可能,也不完全是你的答案,但我需要同样的东西并且不想使用 eval、monkey-patching 等。所以我通过在条件中扩展它来使用默认类。

Of course it means if you have 100 classes to extend you need to add 100 conditions with another extending operation but for me this looked like the right way.当然,这意味着如果您有 100 个要扩展的类,您需要使用另一个扩展操作添加 100 个条件,但对我来说,这看起来是正确的方法。

<?php
if(class_exists('SolutionClass')) {
    class DynamicParent extends SolutionClass {}
} else {
    class DynamicParent extends DefaultSolutionClass {}
}

class ProblemChild extends DynamicParent {}
?>

Yes.是的。 I like the answer with eval, but a lot of people afraid of any eval in their code, so here's one with no eval:我喜欢 eval 的答案,但很多人害怕他们代码中的任何 eval,所以这里有一个没有 eval 的:

<?php //MyClass.php 
namespace my\namespace;
function get_dynamic_parent() {
    return 'any\other\namespace\ExtendedClass';// return what you need
}
class_alias(get_dynamic_parent(), 'my\namespace\DynamicParent');

class MyClass extends DynamicParent {}

It is possible to create dynamic inheritance in PHP using the power of the magic __call function.可以使用神奇的 __call 函数在 PHP 中创建动态继承。 It takes a little bit of infrastructure code to work but isn't too daunting.它需要一些基础设施代码才能工作,但并不太令人生畏。

Disclaimer免责声明

You really ought to think at least twice before using this technique as it's actually kind of bad thing to do.在使用这种技术之前,你真的应该至少三思而后行,因为它实际上是一种不好的做法。

The only reason I'm using this technique is because I don't want to have to either create the interface definitions or setup the dependency injections when creating templates for a site.我使用这种技术的唯一原因是我不想在为站点创建模板时创建接口定义或设置依赖项注入。 I want to just be able to just define a couple of function 'blocks' in a template and then have inheritance automatically use the correct 'block'.我只想能够在模板中定义几个函数“块”,然后让继承自动使用正确的“块”。

Implementation执行

The steps required are:所需的步骤是:

  • The child class now extends a 'DynamicExtender' class.子类现在扩展了“DynamicExtender”类。 This class intercepts any calls made by the child class, to methods that don't exist in the child class and redirects them to the parent instance.该类拦截子类对子类中不存在的方法的任何调用,并将它们重定向到父实例。

  • Each 'ParentClass' is extended to a 'ProxyParentClass'.每个“ParentClass”都扩展为一个“ProxyParentClass”。 For every accessible method in the parent class, there exists an equivalent method in the 'ProxyParentClass'.对于父类中的每个可访问方法,“ProxyParentClass”中都存在一个等效的方法。 Each of those methods in the 'ProxyParentClass' checks to see if the method exists in the ChildClass and calls the childs version of the function if it exists, otherwise it calls the version from ParentClass 'ProxyParentClass' 中的每个方法都会检查该方法是否存在于 ChildClass 中,如果存在则调用该函数的 childs 版本,否则调用 ParentClass 中的版本

  • When the DynamicExtender class is constructed you pass in what parent class you require, the DynamicExtender creates a new instance of that class, and sets itself as the child of the ParentClass.在构造 DynamicExtender 类时,您传入所需的父类,DynamicExtender 创建该类的新实例,并将自身设置为 ParentClass 的子类。

So, now when we create the child object we can specify the parent class required and the DynamicExtender will create it for us, and it will appear as if the child class is extended from the class we requested at run-time rather than it being hard-coded.因此,现在当我们创建子对象时,我们可以指定所需的父类,DynamicExtender 将为我们创建它,并且看起来好像子类是从我们在运行时请求的类扩展而来的,而不是很难-编码。

This may be easier to understand as a couple of images:这可能更容易理解为几个图像:

Fixed inheritance is fixed固定继承是固定的

在此处输入图片说明

Dynamic inheritance with proxies使用代理进行动态继承

在此处输入图片说明

Demo implementation演示实现

The code for this solution is available on Github and a slightly fuller explanation of how this can be used here , but the code for the above image is:该解决方案的代码可以在Github上,以及如何这个稍微更全面的解释,这里可以使用,但对于上面的图像的代码是:

//An interface that defines the method that must be implemented by any renderer.
interface Render {
    public function render();
}


/**
 * Class DynamicExtender
 */
class DynamicExtender implements Render {

    var $parentInstance = null;

    /**
     * Construct a class with it's parent class chosen dynamically.
     *
     * @param $parentClassName The parent class to extend.
     */
    public function __construct($parentClassName) {
        $parentClassName = "Proxied".$parentClassName;

        //Check that the requested parent class implements the interface 'Render'
        //to prevent surprises later.
        if (is_subclass_of($parentClassName, 'Render') == false) {
            throw new Exception("Requested parent class $parentClassName does not implement Render, so cannot extend it.");
        }

        $this->parentInstance = new $parentClassName($this);
    }

    /**
     * Magic __call method is triggered whenever the child class tries to call a method that doesn't
     * exist in the child class. This is the case whenever the child class tries to call a method of
     * the parent class. We then redirect the method call to the parentInstance.
     *
     * @param $name
     * @param array $arguments
     * @return mixed
     * @throws PHPTemplateException
     */
    public function __call($name, array $arguments) {
        if ($this->parentInstance == null) {
            throw new Exception("parentInstance is null in Proxied class in renderInternal.");
        }

        return call_user_func_array([$this->parentInstance, $name], $arguments);
    }

    /**
     * Render method needs to be defined to satisfy the 'implements Render' but it
     * also just delegates the function to the parentInstance.
     * @throws Exception
     */
    function render() {
        $this->parentInstance->render();
    }
}



/**
 * Class PageLayout
 *
 * Implements render with a full HTML layout.
 */
class PageLayout implements Render {

    //renders the whole page.
    public function render() {
        $this->renderHeader();
        $this->renderMainContent();
        $this->renderFooter();
    }

    //Start HTML page
    function renderHeader() {
        echo "<html><head></head><body>";
        echo "<h2>Welcome to a test server!</h2>";

        echo "<span id='mainContent'>";
    }

    //Renders the main page content. This method should be overridden for each page
    function renderMainContent(){
        echo "Main content goes here.";
    }

    //End the HTML page, including Javascript
    function renderFooter(){
        echo "</span>";
        echo "<div style='margin-top: 20px'>Dynamic Extension Danack@basereality.com</div>";
        echo "</body>";
        echo "<script type='text/javascript' src='jquery-1.9.1.js' ></script>";
        echo "<script type='text/javascript' src='content.js' ></script>";
        echo "</html>";
    }

    //Just to prove we're extending dynamically.
    function getLayoutType() {
        return get_class($this);
    }
}

/**
 * Class ProxiedPageLayout
 *
 * Implements render for rendering some content surrounded by the opening and closing HTML
 * tags, along with the Javascript required for a page.
 */
class ProxiedPageLayout extends PageLayout {

    /**
     * The child instance which has extended this class.
     */
    var $childInstance = null;

    /**
     * Construct a ProxiedPageLayout. The child class must be passed in so that any methods
     * implemented by the child class can override the same method in this class.
     * @param $childInstance
     */
    function __construct($childInstance){
        $this->childInstance = $childInstance;
    }

    /**
     * Check if method exists in child class or just call the version in PageLayout
     */
    function renderHeader() {
        if (method_exists ($this->childInstance, 'renderHeader') == true) {
            return $this->childInstance->renderHeader();
        }
        parent::renderHeader();
    }

    /**
     * Check if method exists in child class or just call the version in PageLayout
     */
    function renderMainContent(){
        if (method_exists ($this->childInstance, 'renderMainContent') == true) {
            return $this->childInstance->renderMainContent();
        }
        parent::renderMainContent();
    }

    /**
     * Check if method exists in child class or just call the version in PageLayout
     */
    function renderFooter(){
        if (method_exists ($this->childInstance, 'renderFooter') == true) {
            return $this->childInstance->renderFooter();
        }
        parent::renderFooter();
    }
}


/**
 * Class AjaxLayout
 *
 * Implements render for just rendering a panel to replace the existing content.
 */
class AjaxLayout implements Render {

    //Render the Ajax request.
    public function render() {
        $this->renderMainContent();
    }

    //Renders the main page content. This method should be overridden for each page
    function renderMainContent(){
        echo "Main content goes here.";
    }

    //Just to prove we're extending dynamically.
    function getLayoutType() {
        return get_class($this);
    }
}

/**
 * Class ProxiedAjaxLayout
 *
 * Proxied version of AjaxLayout. All public functions must be overridden with a version that tests
 * whether the method exists in the child class.
 */
class ProxiedAjaxLayout extends AjaxLayout {

    /**
     * The child instance which has extended this class.
     */
    var $childInstance = null;

    /**
     * Construct a ProxiedAjaxLayout. The child class must be passed in so that any methods
     * implemented by the child class can override the same method in this class.
     * @param $childInstance
     */
    function __construct($childInstance){
        $this->childInstance = $childInstance;
    }

    /**
     * Check if method exists in child class or just call the version in AjaxLayout
     */
    function renderMainContent() {
        if (method_exists ($this->childInstance, 'renderMainContent') == true) {
            return $this->childInstance->renderMainContent();
        }
        parent::renderMainContent();
    }
}



/**
 * Class ImageDisplay
 *
 * Renders some images on a page or Ajax request.
 */
class ImageDisplay extends DynamicExtender {

    private $images = array(
        "6E6F0115.jpg",
        "6E6F0294.jpg",
        "6E6F0327.jpg",
        "6E6F0416.jpg",
        "6E6F0926.jpg",
        "6E6F1061.jpg",
        "6E6F1151.jpg",
        "IMG_4353_4_5_6_7_8.jpg",
        "IMG_4509.jpg",
        "IMG_4785.jpg",
        "IMG_4888.jpg",
        "MK3L5774.jpg",
        "MK3L5858.jpg",
        "MK3L5899.jpg",
        "MK3L5913.jpg",
        "MK3L7764.jpg",
        "MK3L8562.jpg",
    );

    //Renders the images on a page, along with a refresh button
    function renderMainContent() {
        $totalImages = count($this->images);
        $imagesToShow = 4;
        $startImage = rand(0, $totalImages - $imagesToShow);

        //Code inspection will not be available for 'getLayoutType' as it
        //doesn't exist statically in the class hierarchy
        echo "Parent class is of type: ".$this->getLayoutType()."<br/>";

        for($x=0 ; $x<$imagesToShow ; $x++) {
            echo "<img src='images/".$this->images[$startImage + $x]."'/>";
        }

        echo "<br/>&nbsp;<br/>";
        echo "<span onclick='loadImagesDynamic();' style='border: 2px solid #000000; padding: 4px:'>Click to refresh images</span>";
    }
}


$parentClassName = 'PageLayout';

if (isset($_REQUEST['panel']) && $_REQUEST['panel']) {
    //YAY! Dynamically set the parent class.
    $parentClassName = 'AjaxLayout';
}

$page = new ImageDisplay($parentClassName);

$page->render();

I don't think it's possible to dynamically extend a class (however if I'm wrong I'd love to see how it's done).我认为动态扩展类是不可能的(但是,如果我错了,我很想看看它是如何完成的)。 Have you thought about using the Composite pattern ( http://en.wikipedia.org/wiki/Composite_pattern , http://devzone.zend.com/article/7 )?您是否考虑过使用复合模式( http://en.wikipedia.org/wiki/Composite_patternhttp://devzone.zend.com/article/7 )? You could dynamically composite another class (even multiple classes - this is often used as a work around to multiple inheritance) to 'inject' the methods/properties of your parent class into the child class.您可以动态组合另一个类(甚至多个类 - 这通常用作多重继承的解决方法)以将父类的方法/属性“注入”到子类中。

Couldn't you just use an eval?你不能只使用 eval 吗?

<?php
function dynamic_class_name() {
    if(time() % 60)
        return "Class_A";
    if(time() % 60 == 0)
        return "Class_B";
}
eval(
    "class MyRealClass extends " . dynamic_class_name() . " {" . 
    # some code string here, possibly read from a file
    . "}"
);
?>

I have solved my same type of problem.我已经解决了我的同类问题。 The first parameter defines the original class name and the second parameter defines the new class name of class_alias function.第一个参数定义原始类名,第二个参数定义 class_alias 函数的新类名。 Then we can use this function in if and else condition.然后我们可以在 if 和 else 条件下使用这个函数。

if(1==1){
  class_alias('A', 'C');
}
else{
  class_alias('B', 'C');
}

class Apple extends C{
      ...
}

Apple class extends to virtual class "C" which can be defined as class "A" Or "B" depend on if and else condition. Apple 类扩展到虚拟类“C”,它可以根据 if 和 else 条件定义为类“A”或“B”。

For More information you can check this link https://www.php.net/manual/en/function.class-alias.php有关更多信息,您可以查看此链接https://www.php.net/manual/en/function.class-alias.php

  1. Get All declared_classes获取所有声明的类
  2. Position, where the class will be declare.位置,类将被声明的位置。

class myClass { public $parentVar; function __construct() { $all_classes = get_declared_classes(); // all classes $parent = $parent[count($parent) -2]; //-2 is the position $this->parentVar = new $parent(); } }

I have an idea so simple, you can try我有一个很简单的想法,你可以试试

class A {} 
class B {}
$dynamicClassName = "A";
eval("class DynamicParent extends $dynamicClassName {}");

class C extends DynamicParent{
   // extends success
   // Testing
   function __construct(){
        echo get_parent_class('DynamicParent'); exit; //A :)
   }
}

I had to do this with a processor class that extends one of two abstract classes.我必须使用扩展两个抽象类之一的处理器 class 来执行此操作。

The working solution looks like this:工作解决方案如下所示:

if (class_exists('MODX\Revolution\Processors\Processor')) {
    abstract class DynamicProcessorParent extends 
        MODX\Revolution\Processors\Processor {}
} else {
    abstract class DynamicProcessorParent extends modProcessor {}
}

class NfSendEmailProcessor extends DynamicProcessorParent {
  /* Concrete class */
}

If the abstract parent classes contain abstract methods, they don't need to be implemented in either of the dynamic parent classes.如果抽象父类包含抽象方法,则不需要在任何一个动态父类中实现它们。

If you're working on a large project, you probably don't want to use DynamicParent as the class name unless the classes are namespaced.如果您正在处理大型项目,您可能不想使用 DynamicParent 作为 class 名称,除非这些类已命名空间。 You'll need something more specific to avoid collisions.你需要更具体的东西来避免碰撞。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM