基準に応じて、さまざまなクラス (最大数百) を拡張するために使用する必要があるクラスがあります。PHPでクラスを動的クラス名で拡張する方法はありますか?
インスタンス化で拡張子を指定するメソッドが必要になると思います。
アイデア?
はい。私は 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 {}
魔法の __call 関数の力を使用して、PHP で動的継承を作成することができます。インフラストラクチャ コードが少し必要ですが、それほど難しくはありません。
このテクニックを使用する前に、少なくとも 2 度はよく考えてください。実際には悪いことです。
私がこの手法を使用している唯一の理由は、サイトのテンプレートを作成するときにインターフェイス定義を作成したり、依存関係の挿入を設定したりしたくないからです。テンプレートでいくつかの関数「ブロック」を定義するだけで、継承が自動的に正しい「ブロック」を使用できるようにしたいのです。
必要な手順は次のとおりです。
子クラスは「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 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/> <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();
クラスを動的に拡張することはできないと思います (ただし、間違っている場合は、それがどのように行われるかを知りたいです)。Composite パターン ( http://en.wikipedia.org/wiki/Composite_pattern、http://devzone.zend.com/article/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
. "}"
);
?>
とてもシンプルなアイデアがあるので、試してみてください
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 :)
}
}
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();
}
}