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

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

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

Идеи?

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

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

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

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

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

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

отказ

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

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

Реализация

Необходимые шаги:

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

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

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

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

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

Исправлено наследование

введите описание изображения здесь

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

введите описание изображения здесь

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

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

Не могли бы вы просто использовать 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, но многие люди боятся какого-либо 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 {} 
  1. Получить все объявленные_классы
  2. Позиция, в которой будет объявлен класс.

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

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

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