是否可以动态扩展一个类?

Posted

技术标签:

【中文标题】是否可以动态扩展一个类?【英文标题】:Is it possible to extend a class dynamically? 【发布时间】:2010-12-05 02:26:51 【问题描述】:

我有一个类,我需要使用它来根据标准扩展不同的类(最多数百个)。 php中有没有办法通过动态类名来扩展一个类?

我认为它需要一种方法来指定具有实例化的扩展。

想法?

【问题讨论】:

【参考方案1】:

我已经解决了我同样类型的问题。第一个参数定义了原来的类名,第二个参数定义了class_alias函数的新类名。然后我们可以在 if 和 else 条件下使用这个函数。

if(1==1)
  class_alias('A', 'C');

else
  class_alias('B', 'C');


class Apple extends C
      ...

Apple 类扩展为虚拟类“C”,可以定义为类“A”或“B”取决于 if 和 else 条件。

欲了解更多信息,您可以查看此链接https://www.php.net/manual/en/function.class-alias.php

【讨论】:

对于困惑的谷歌,如果你在命名空间中工作,别名将在全局命名空间中创建,所以你需要use它。【参考方案2】:

我有一个很简单的想法,你可以试试

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 :)
   

【讨论】:

【参考方案3】:
    获取所有声明的类 声明类的位置。

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();

【讨论】:

【参考方案4】:

虽然这仍然不可能并且不完全是您的答案,但我需要同样的东西并且不想使用 eval、monkey-patching 等。所以我通过在条件下扩展它来使用默认类。

当然,这意味着如果您有 100 个要扩展的类,则需要通过另一个扩展操作添加 100 个条件,但对我来说,这看起来是正确的方法。

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


class ProblemChild extends DynamicParent 
?>

【讨论】:

我在 Zend 框架(特别是 Magento)中遇到了这个问题,其中出现了第 3 方模块并扩展了我已经扩展的类。使用这种方法,我可以更改我的类以动态扩展第 3 方类,而不是我们都追求的核心类。 出色的解决方案,如此简单! (“啰嗦”?!伙计们,你看到替代品了吗?:))无法停止爱它并将我的头撞到我没有想出的桌子上......【参考方案5】:

是的。我喜欢 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 

【讨论】:

好主意,缺点是你只能为一个父类制作一个孩子。【参考方案6】:

可以使用神奇的 __call 函数在 PHP 中创建动态继承。需要一点基础架构代码才能工作,但不会太令人生畏。

免责声明

在使用这种技术之前,你真的应该至少三思而后行,因为它实际上是一件坏事。

我使用这种技术的唯一原因是因为我不想在为站点创建模板时创建接口定义或设置依赖注入。我只想能够在模板中定义几个函数“块”,然后让继承自动使用正确的“块”。

实施

需要的步骤是:

子类现在扩展了“DynamicExtender”类。该类拦截子类对子类中不存在的方法的任何调用,并将它们重定向到父实例。

每个“父类”都扩展为一个“代理父类”。对于父类中的每个可访问方法,“ProxyParentClass”中都存在一个等效方法。 'ProxyParentClass' 中的每个方法都会检查该方法是否存在于 ChildClass 中,如果存在则调用该函数的子版本,否则调用 ParentClass 中的版本

在构造 DynamicExtender 类时,传入所需的父类,DynamicExtender 会创建该类的新实例,并将自身设置为 ParentClass 的子类。

所以,现在当我们创建子对象时,我们可以指定所需的父类,DynamicExtender 将为我们创建它,它看起来好像子类是从我们在运行时请求的类扩展而来的,而不是它是硬编码的。

这可能更容易理解为几张图片:

固定继承是固定的

使用代理的动态继承

演示实现

这个解决方案的代码是available on Github,对这个can be used here的解释稍微完整一点,但是上图的代码是:

//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();

【讨论】:

谢谢你,我想知道是否有可能实现类似Threaded::extend 并发现这个答案很有帮助。【参考方案7】:

你不能只使用一个 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
    . ""
);
?>

【讨论】:

我通常很犹豫是否在任何类型的生产环境中使用 eval()。 如果您使用的是带有 php 加速器(例如 APC)的网络服务器,则 eval 不会存储在操作码缓存中 @OhhMee ***.com/questions/951373/when-is-eval-evil-in-php @Jared 在这种情况下, eval 函数并不邪恶。其次,我们要时刻牢记“能力越大责任越大”。 我使用 class_alias('TCPDF', 'TCPDF2'); 之类的 class_alias 函数设置了一个动态类名。我已经解决了我同样类型的问题。第一个参数定义原始类名,第二个参数定义新类名。然后我们可以在 if 和 else 条件下使用这个函数。如果有人希望这种方法使用 extends 动态类解决继承问题,我可以给出一个演示。【参考方案8】:

我认为动态扩展一个类是不可能的(但如果我错了,我很想看看它是如何完成的)。您是否考虑过使用复合模式(http://en.wikipedia.org/wiki/Composite_pattern、http://devzone.zend.com/article/7)?您可以动态组合另一个类(甚至是多个类 - 这通常用作多继承的一种解决方法)以将父类的方法/属性“注入”到子类中。

【讨论】:

关于复合模式 PHP的实现,有简单的例子吗?有one example here,够了吗?

以上是关于是否可以动态扩展一个类?的主要内容,如果未能解决你的问题,请参考以下文章

如何动态启用/禁用响应式扩展

php类成员函数二次定义,动态定义类成员函数

是否可以从动态变量实例化一个类?

Java静态代理和动态代理的区别

带有扩展类的 AngularJS 动态样式

是否可以动态检查文本字符串是否是 C++ 中给定类的成员?