магический метод __call() аргументы как «реальные» аргументы

#php #oop

#php #ооп

Вопрос:

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

Например:

 class test {
    
    public function __call($name, $args){
        /* blah, blah class exists? require */
        return new $name($args);
    }

    public function __toString(){
        return (string) $this->extension(); // to enforce __toString of extension
    }
}

class extension extends test {
    public function __construct(classTypeHint $object, $required, $random = 25){
        // This does not work, results in:
        // "Argument 1 passed to extension::__construct must be an instance of classTypeHint, array given...
    }

    public function __toString(){
        return var_export($this, true);
    }
}

echo new test;
  

Как указано в комментарии, это приводит к ошибке, но я ищу возможность доступа $object как ( $args[0] ), $required как ( $args[1] ) и т.д. Кроме того, применяйте «правильные» аргументы.

Почему я ищу это?

__autoload Функциональность PHP ограничена. Ну, может быть, это не так, но я не нашел простого способа дифференцировать автозагрузку классов.

С помощью этой функциональности я могу использовать __call() , __get() __callStatic() для трех разных автозагрузок. Нравится:

  1. __call() ищет библиотеки в /includes/libraries
  2. __get() ищет плагины в /application/plugins
  3. __callStatic() ищет, ммм, кошек в /application/cats

Если есть лучший способ через автозагрузку, пожалуйста, поделитесь. Но я все еще ищу такое решение, если это возможно. Может быть, с Reflection классами?

Несколько рабочий пример с ReflectionClass:

 $instance = new ReflectionClass($name);
$instance->newInstanceArgs($arguments);
return $instance;
  

Но здесь очевидно, что он вызовет ReflectionClass->__toString() здесь. Кроме того, игнорируя возвращаемый дамп, он фактически не использует newInstanceArgs() здесь.

Примечания:

Меня интересует пример, работающий до PHP 5.3.

Комментарии:

1. Я думаю, вы могли бы извлечь выгоду из php.net/manual/en/function.call-user-func-array.php

2. Я не мог найти способ, как создать новый экземпляр класса с этим.

Ответ №1:

Используйте отражение:

 public function __call($name, $args)
{
    return (new ReflectionClass($name))->newInstanceArgs($args);
}
  

он принимает массив, подобный call_user_func_array

Редактировать: рабочий пример:http://codepad.org/ZZpBwRij

 <?php

class MyClass
{
    public function __construct($a, $b)
    {
        echo "__construct($a, $b) called";
    }
}

function create($name, $args)
{
    $class = new ReflectionClass($name);
    return $class->newInstanceArgs($args);
}

var_dump(create('MyClass', array(1, 2)));

?>

Output:

__construct(1, 2) called
object(MyClass)#2 (0) {
}
  

Комментарии:

1. Хотя теоретически это выглядит так, у него есть 2 недостатка: 1) результаты копирования-вставки в syntax error, unexpected T_OBJECT_OPERATOR 2) при преобразовании в несколько рабочий пример он использует __toString() of ReflectionClass , подчиняющийся фактическому классу __toString() , и дает бесполезный результат.. этот неприятный дамп.

2. Смотрите обновленный пост, плюс, на самом деле это не рабочий пример, потому что он не переопределяет параметры.

3. @Tom: почему очевидно, что он будет вызывать ReflectionClass->__toString() ?

Ответ №2:

Есть лучший способ через автозагрузку.

Пространство имен вашего кода, т. Е. Создайте пространства имен «Библиотека», «Плагин» и «Кошки» в соответствующих каталогах. Например

 // includes/libraries/Library/Foo.php

namespace Library;

class Foo {
    // ...
}
  

 // application/plugins/Plugin/Bar.php

namespace Plugin;

class Bar {
    // ...
}
  

Используйте что-то вроде универсального загрузчика классов Symfony

 require_once 'path/to/Symfony/Component/ClassLoader/UniversalClassLoader.php';

$loader = new SymfonyComponentClassLoaderUniversalClassLoader;
$loader->registerNamespaces(array(
    'Library' => 'path/to/includes/libraries',
    'Plugin'  => 'path/to/application/plugins'
));
$loader->register();

$foo = new LibraryFoo;
$bar = new PluginBar;
  

Комментарии:

1. Была идея о namespaces , но есть проблема — нет пространств имен до версии 5.3. А общедоступные хосты здесь, в Латвии, в основном 5.2 , и я сомневаюсь, что они обновятся в ближайшем будущем. И это на самом деле чертовски хакерское решение, которое они получили.

Ответ №3:

Итак, рабочий пример, полученный из ответа @Dani.

 public function __call($name, $args){
    $reflection = new ReflectionClass($name);
    return $reflection->newInstanceArgs($args);
}
  

Работает как шарм, за исключением того, что я пока понятия не имею о производительности.