是否可以动态扩展类?

发布于 2024-08-07 07:58:42 字数 104 浏览 6 评论 0原文

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

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

有想法吗?

I have a class which I need to use to extend different classes (up to hundreds) depending on criteria. Is there a way in PHP to extend a class by a dynamic class name?

I assume it would require a method to specify extension with instantiation.

Ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

梦幻的心爱 2024-08-14 07:58:43

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

Couldn't you just use an 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
    . "}"
);
?>
牵你手 2024-08-14 07:58:43

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

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

class Apple extends C{
      ...
}

Apple 类扩展为虚拟类“C”,根据 if 和 else 条件,可以将其定义为类“A”或“B”。

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

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. Then we can use this function in if and else condition.

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.

For More information you can check this link https://www.php.net/manual/en/function.class-alias.php

愁杀 2024-08-14 07:58:43
  1. 获取所有声明的类
  2. 位置,将在其中声明类。

类 myClass {
公共$parentVar;
函数 __construct() {
$all_classes = get_declared_classes(); // 所有类
$parent = $parent[count($parent) -2]; //-2是位置
$this->parentVar = new $parent();
}
}

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

最后的乘客 2024-08-14 07:58:43

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

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 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 :)
   }
}
叹梦 2024-08-14 07:58:43

我必须使用扩展两个抽象类之一的处理器类来完成此操作。

工作解决方案如下所示:

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 */
}

如果抽象父类包含抽象方法,则不需要在任何动态父类中实现它们。

如果您正在处理一个大型项目,您可能不想使用 DynamicParent 作为类名,除非这些类是命名空间的。您需要更具体的东西来避免冲突。

I had to do this with a processor class that extends one of two abstract classes.

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 . You'll need something more specific to avoid collisions .

茶底世界 2024-08-14 07:58:42

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

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

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

class ProblemChild extends DynamicParent {}
?>

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.

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.

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

class ProblemChild extends DynamicParent {}
?>
大海や 2024-08-14 07:58:42

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

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:

<?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 {}
Bonjour°[大白 2024-08-14 07:58:42

可以使用神奇的 __call 函数的强大功能在 PHP 中创建动态继承。它需要一些基础设施代码才能工作,但并不是太令人畏惧。

免责声明

在使用此技术之前,您确实应该至少三思而后行,因为这实际上是一件坏事。

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

实现

所需的步骤是:

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

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

  • 当构造 DynamicExtender 类时,您传入当您需要什么父类时,DynamicExtender 创建该类的一个新实例,并将其自身设置为 ParentClass 的子类。

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

这可能更容易理解为几个图像:

固定继承是固定的

在此处输入图像描述

使用代理的动态继承

在此处输入图像描述

演示实现

此解决方案的代码为 可在 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 [email protected]</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/> <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();

It is possible to create dynamic inheritance in PHP using the power of the magic __call function. 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. 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'. For every accessible method in the parent class, there exists an equivalent method in the '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

  • 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.

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.

This may be easier to understand as a couple of images:

Fixed inheritance is fixed

enter image description here

Dynamic inheritance with proxies

enter image description here

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:

//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 [email protected]</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/> <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();
ι不睡觉的鱼゛ 2024-08-14 07:58:42

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

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)? 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文