Динамически генерировать классы во время выполнения в php?

Вот что я хочу сделать:

$clsName = substr(md5(rand()),0,10); //generate a random name
$cls = new $clsName(); //create a new instance

function __autoload($class_name)
{
  //define that instance dynamically
}

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

Я пытался использовать eval(), но это дает мне пригонки по частным и $this-> ссылки...

//редактировать

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

У меня есть структура проверки с использованием подсказок кода на сайте, который я поддерживаю. Каждая функция имеет два определения

function DoSomething($param, $param2){
   //code
}
function DoSomething_Validate(vInteger $param, vFloat $param2){
   //return what to do if validation fails
}

Я ищу, чтобы добавить валидатор для первичных ключей в моей базе данных. Я не хочу создавать отдельный класс для КАЖДОЙ таблицы (203). Так что мой план был сделать что-то вроде

function DoSomething_Validate(vPrimaryKey_Products $id){ }

Где __autoload будет генерировать подкласс vPrimaryKey и установить для параметра таблицы значение Products.

Счастлив теперь?

9 ответов

Решение

Это почти наверняка плохая идея.

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

Что-то с подписью командной строки, например:

./generate_classes_from_db <host> <database> [tables] [output dir]

Начиная с PHP 7.0, с небольшим творческим потенциалом и знанием некоторых менее известных функций PHP, вы можете абсолютно сделать это, не прибегая к eval или созданию файлов сценариев динамически. Вам просто нужно использовать анонимные классы и class_alias(), например:

spl_autoload_register(function ($unfoundClassName) {
{
    $newClass = new class{}; //create an anonymous class
    $newClassName = get_class($newClass); //get the name PHP assigns the anonymous class
    class_alias($newClassName, $unfoundClassName); //alias the anonymous class with your class name
}

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

Сказав это, я чувствую, что все люди в этом вопросе, которые говорят: "Eval - это всегда очень плохая идея. Просто не используйте ее никогда!" просто повторяют то, что слышали от улья, и не думают сами за себя. Eval есть в языке по определенной причине, и есть ситуации, когда он может быть эффективно использован. Если вы используете более старую версию PHP, eval может быть хорошим решением здесь.

Тем не менее, они верны в том, что это может открыть очень большие дыры в безопасности, и вы должны быть осторожны в том, как вы его используете, и понимать, как устранить риски. Важно то, что, как и SQL-инъекция, вы должны очистить любой ввод, введенный в оператор eval.

Например, если ваш автозагрузчик выглядел так:

spl_autoload_register(function ($unfoundClassName) {
{
    eval("class $unfoundClassName {}");
}

Хакер может сделать что-то вроде этого:

$injectionCode = "bogusClass1{} /*insert malicious code to run on the server here */ bogusClass2";

new $injectionCode();

Видите, как это может стать дырой в безопасности? Все, что хакер помещает между двумя именами bogusClass, будет запущено на вашем сервере с помощью оператора eval.

Если вы настроите свой автозагрузчик для проверки переданного имени класса (т.е. выполните preg_match, чтобы убедиться, что нет пробелов или специальных символов, сверяете его со списком допустимых имен и т. Д.), Вы можете устранить эти риски, и тогда eval может быть совершенно нормально использовать в этой ситуации. Если вы используете PHP 7 или выше, я рекомендую анонимный метод псевдонима класса выше.

Я знаю, что это старый вопрос, и есть ответы, которые БУДУТ работать, но я хотел предложить несколько фрагментов, которые ответили бы на исходный вопрос, и я думаю, предложить более расширенное решение, если кто-то окажется здесь, как я, когда искал ответ к этой проблеме.

Создать один динамический класс

<?php
// Without properties
$myclassname = "anewclassname";
eval("class {$myclassname} { }";
// With a property
$myclassname = "anewclassname";
$myproperty = "newproperty";
eval("class {$myclassname} { protected \${$myproperty}; }";
?>

Пока вы правильно экранируете свой текст, вы также можете добавить туда функцию.

Но что делать, если вы хотите динамически создавать классы на основе чего-то, что само по себе может быть динамическим, например, создание класса для каждой таблицы в вашей базе данных, как упоминалось в первоначальном вопросе?

Создать несколько динамических классов

<?php

// Assumes $dbh is a pdo connection handle to your MySQL database
$stmt=$dbh->prepare("show tables");
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$handle = null;
$classcode = '';
foreach ($result as $key => $value) {
    foreach ($value as $key => $value) {
        $classcode = "class {$value} { ";
        $stmt2=$dbh->prepare("DESC $value");
        $stmt2->execute();
        $result2 = $stmt2->fetchAll(PDO::FETCH_ASSOC);
        foreach ($result2 as $key => $value) {
            $classcode .= "public \${$value['Field']}; ";
        }
        $classcode .=  "}";
        eval($classcode);
    }
}

?>

Это будет динамически генерировать класс для каждой таблицы в базе данных. Для каждого класса ТАКЖЕ будет создано свойство, названное в честь каждого столбца.

Теперь было указано, что вы не должны этого делать. Пока вы контролируете, что происходит в eval, риск безопасности не является проблемой. НО - скорее всего, есть решение, которое имеет больше смысла, если вы думаете об этом достаточно глубоко. Я думал, что у меня есть идеальный вариант использования для динамического создания новых классов. Тщательное изучение проблемы доказало обратное.

Одно из возможных решений - использовать stdClass для создания объектов, которые являются просто контейнерами данных и не нуждаются в каких-либо методах.

Также - как уже упоминалось, вы можете использовать скрипт для генерации вручную большого количества классов. В случае классов, отражающих таблицы вашей базы данных, вы можете использовать ту же логику, что и у меня выше, и вместо выполнения eval записать эту информацию в файл.

Забавно, на самом деле это одна из немногих вещей, где eval не кажется такой плохой идеей.

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

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

если вы знаете, что делаете, eval поможет вам в этом.

Тем не менее, по моему мнению, вам гораздо лучше, когда вы не полагаетесь на подсказки типов для проверки, но у вас есть одна функция

DoSomething_Validate($id)
{ 
   // get_class($id) and other validation foo here
}

Я думаю, используя eval() это не надежное решение, особенно если ваш сценарий или программное обеспечение будет распространяться среди разных клиентов. Общие хостинг-провайдеры всегда отключают eval() функция.

Я думаю о лучшем подходе, как это:

<?php 

 function __autoload( $class ) {
      require 'classes/'.$class.'.php';

}

$class = 'App';
$code = "<?php class $class {
    public function run() {
        echo '$class<br>';  
    }
    ".'
    public function __call($name,$args) {
        $args=implode(",",$args);
        echo "$name ($args)<br>";
    }
}';

file_put_contents('classes/App.php' ,$code);

$a = new $class();
$a->run();

Закончив выполнение кода, вы можете удалить файл, если хотите, я проверил его, и он отлично работает.

Использование eval() действительно плохая идея. Это открывает большую дыру в безопасности. Только не используйте это!

function __autoload($class)  {
    $code = "class $class {`
        public function run() {
            echo '$class<br>';  
        }
        ".'
        public function __call($name,$args) {
            $args=implode(",",$args);
            echo "$name ($args)<br>";
        }
    }';

    eval($code);
}

$app=new Klasse();
$app->run();
$app->HelloWorld();

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

Мы можем создать экземпляр класса динамически следующим образом

Я также сталкиваюсь с этой проблемой в версии Laravel 5.8, и теперь она у меня работает нормально.

Укажите полный путь вместо имени класса

class TestController extends Controller
{
    protected $className;

    public function __construct()
    {
        $this->className = 'User';
    }

    public function makeDynamicInstance()
    {
        $classNameWithPath = 'App\\' . $this->className; 
        $classInstance = new $classNameWithPath;
        $data = $classInstance::select('id','email')->get();
        return $data;
    }
}

Выход

Illuminate\Database\Eloquent\Collection Object
(
    [items:protected] => Array
        (
            [0] => App\User Object
                (
                    [fillable:protected] => Array
                        (
                            [0] => name
                            [1] => email
                            [2] => password
                            [3] => user_group_id
                            [4] => username
                            [5] => facebook_page_id
                            [6] => first_name
                            [7] => last_name
                            [8] => email_verified
                            [9] => active
                            [10] => mobile
                            [11] => user_type
                            [12] => alternate_password
                            [13] => salt
                            [14] => email_verification_token
                            [15] => parent_id
                        )

                    [hidden:protected] => Array
                        (
                            [0] => password
                            [1] => remember_token
                        )

                    [casts:protected] => Array
                        (
                            [email_verified_at] => datetime
                        )

                    [connection:protected] => mysql
                    [table:protected] => users
                    [primaryKey:protected] => id
                    [keyType:protected] => int
                    [incrementing] => 1
                    [with:protected] => Array
                        (
                        )

                    [withCount:protected] => Array
                        (
                        )

                    [perPage:protected] => 15
                    [exists] => 1
                    [wasRecentlyCreated] => 
                    [attributes:protected] => Array
                        (
                            [id] => 1
                            [email] => admin@admin.com
                        )

                    [original:protected] => Array
                        (
                            [id] => 1
                            [email] => admin@admin.com
                        )

                    [changes:protected] => Array
                        (
                        )

                    [dates:protected] => Array
                        (
                        )

                    [dateFormat:protected] => 
                    [appends:protected] => Array
                        (
                        )

                    [dispatchesEvents:protected] => Array
                        (
                        )

                    [observables:protected] => Array
                        (
                        )

                    [relations:protected] => Array
                        (
                        )

                    [touches:protected] => Array
                        (
                        )

                    [timestamps] => 1
                    [visible:protected] => Array
                        (
                        )

                    [guarded:protected] => Array
                        (
                            [0] => *
                        )

                    [rememberTokenName:protected] => remember_token
                )
)

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

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


<?php
$clname = "TestClass";

eval("class $clname{}; \$cls = new $clname();");

var_dump($cls);

Я создал пакет, который динамически создает классы / интерфейсы / признаки... и сохраняет их в файл, который вы можете просто включить созданный файл, чтобы иметь возможность использовать ваш класс

пакет: https://packagist.org/packages/kanel/enuma

Другие вопросы по тегам