Динамически генерировать классы во время выполнения в 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);
Я создал пакет, который динамически создает классы / интерфейсы / признаки... и сохраняет их в файл, который вы можете просто включить созданный файл, чтобы иметь возможность использовать ваш класс