Подтвердить что ты не робот

Можно ли динамически расширять класс?

У меня есть класс, который мне нужно использовать для расширения различных классов (до сотен) в зависимости от критериев. Есть ли способ в PHP расширить класс с помощью имени динамического класса?

Я предполагаю, что для этого потребуется метод для указания расширения с помощью экземпляра.

Идеи?

4b9b3361

Ответ 1

Я не думаю, что это возможно для динамического расширения класса (однако, если я ошибаюсь, мне бы хотелось посмотреть, как это делается). Вы подумали об использовании шаблона Composite (http://en.wikipedia.org/wiki/Composite_pattern, http://devzone.zend.com/article/7)? Вы можете динамически комбинировать другой класс (даже несколько классов - это часто используется как работа вокруг множественного наследования), чтобы "вводить" методы/свойства вашего родительского класса в дочерний класс.

Ответ 2

Пока все еще невозможно, а не точно ваш ответ, мне нужно то же самое, и я не хотел использовать eval, monkey-patching и т.д. Поэтому я использовал класс по умолчанию, расширяя его в условиях.

Конечно, это означает, что у вас есть 100 классов для расширения, вам нужно добавить 100 условий с другой расширяющейся операцией, но для меня это выглядело правильно.

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

class ProblemChild extends DynamicParent {}
?>

Ответ 3

В PHP можно создать динамическое наследование, используя силу магической функции __call. Для работы требуется немного кода инфраструктуры, но он не слишком сложный.

Отказ

Вам действительно следует подумать, по крайней мере, дважды, прежде чем использовать эту технику, поскольку это действительно плохой поступок.

Единственная причина, по которой я использую этот метод, заключается в том, что я не хочу либо создавать определения интерфейса, либо настраивать инъекции зависимостей при создании шаблонов для сайта. Я хочу просто просто определить пару "блоков" функций в шаблоне, а затем наследовать автоматически использовать правильный "блок".

Реализация

Требуемые шаги:

  • Теперь дочерний класс расширяет класс DynamicExtender. Этот класс перехватывает любые вызовы, выполняемые дочерним классом, методам, которые не существуют в дочернем классе, и перенаправляет их в родительский экземпляр.

  • Каждый "ParentClass" распространяется на "ProxyParentClass" . Для каждого доступного метода в родительском классе существует эквивалентный метод в "ProxyParentClass" . Каждый из этих методов в "ProxyParentClass" проверяет, существует ли метод в ChildClass и вызывает дочернюю версию функции, если он существует, иначе он вызывает версию из ParentClass

  • Когда класс DynamicExtender сконструирован, вы передаете требуемый родительский класс, DynamicExtender создает новый экземпляр этого класса и устанавливает себя как дочерний элемент ParentClass.

Итак, теперь, когда мы создаем дочерний объект, мы можем указать требуемый родительский класс, и DynamicExtender создаст его для нас, и он будет выглядеть так, как если бы дочерний класс был расширен из класса, который мы запрашивали во время выполнения, а не он жестко закодирован.

Это может быть проще понять как пару изображений:

Фиксированное наследование фиксировано

enter image description here

Динамическое наследование с прокси-серверами

enter image description here

Демонстрационная реализация

Код для этого решения доступен в 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 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/>&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();

Ответ 4

Да. Мне нравится ответ с 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 {}

Ответ 5

Не могли бы вы использовать 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
    . "}"
);
?>

Ответ 6

  • Получить все объявленные_классы
  • Позиция, в которой будет объявлен класс.

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

Ответ 7

Я решил свою проблему такого же типа. Первый параметр определяет исходное имя класса, а второй параметр определяет новое имя класса. Тогда мы можем использовать эту функцию в условиях if и else.

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

class Apple extends C{
      ...
}

Класс Apple распространяется на виртуальный класс "C", который может быть определен как класс "A" или "B" в зависимости от условия if и else.

Ответ 8

У меня есть идея такая простая, вы можете попробовать

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