我有一个类需要根据条件扩展不同的类(多达数百个)。在 PHP 中,有没有一种方式可以通过动态类名称来扩展一个类?
我认为需要一种方法来指定实例化时的扩展。
有什么思路吗?
虽然目前还不可能,也不完全是我需要的答案,但我也需要同样的东西,并且不想使用eval、monkey-patching等方法。所以我通过在条件中扩展默认类来解决问题。
当然,这意味着如果你有100个类要扩展,你需要添加100个带有另一个扩展操作的条件,但对我来说,这看起来像是正确的方式。
<?php
if(class_exists('SolutionClass')) {
class DynamicParent extends SolutionClass {}
} else {
class DynamicParent extends DefaultSolutionClass {}
}
class ProblemChild extends DynamicParent {}
?>
是的,我喜欢使用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 {}
子类现在扩展了一个'DynamicExtender'类。这个类拦截了子类调用不存在于子类中的方法的任何调用,并将它们重定向到父实例。
每个'ParentClass'都被扩展为'ProxyParentClass'。对于父类中的每个可访问方法,在'ProxyParentClass'中都存在一个等效的方法。在'ProxyParentClass'中的每个这些方法都会检查方法是否存在于ChildClass中,如果存在,则调用该函数的child版本,否则调用来自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();
我已经解决了类似的问题。第一个参数定义class_alias函数的原始类名,第二个参数定义新类名。然后我们可以在if和else条件语句中使用该函数。
if(1==1){
class_alias('A', 'C');
}
else{
class_alias('B', 'C');
}
class Apple extends C{
...
}
苹果类扩展到虚拟类"C",可以根据if和else条件定义为"A"或"B"类。
更多信息,请查看此链接https://www.php.net/manual/en/function.class-alias.php
<?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_alias('TCPDF', 'TCPDF2');
。我解决了同类型的问题。
第一个参数定义了原始类名,第二个参数定义了新类名。然后我们可以在if和else条件中使用此函数。如果有人想要使用这种方法解决继承问题,我可以给出演示。 - Bhavin Thummar我必须使用一个处理器类来扩展两个抽象类中的一个。
有效的解决方案如下:
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作为类名。 您需要更具体的内容以避免冲突。
class myClass {
public $parentVar;
function __construct() {
$all_classes = get_declared_classes(); // 所有类
$parent = $parent[count($parent) -2]; //-2 是位置
$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 :)
}
}