Dynamically generate classes at runtime in php?
Asked Answered
V

9

38

Here's what I want to do:

$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
}

Obviously this isn't what I'm actually doing, but basically I have unknown names for a class and based on the name, I want to generate the class with certain properties etc.

I've tried using eval() but it is giving me fits over private and $this-> references...

//edit

Ok, obviously my short and sweet "here's what I want to do" caused massive strife and consternation amongst those who may be able to provide answers. In the hope of getting an actual answer I'll be more detailed.

I have a validation framework using code hints on the site I maintain. Each function has two definitions

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

I'm looking to add a validator for primary keys in my database. I don't want to create a separate class for EVERY table (203). So my plan was to do something like

function DoSomething_Validate(vPrimaryKey_Products $id){ }

Where the __autoload would generate a subclass of vPrimaryKey and set the table parameter to Products.

Happy now?

Vegetal answered 29/7, 2009 at 23:24 Comment(6)
I suggest you tell us what exactly you are trying to do, and ask us how to do it better. What you are attempting is not the right approach.Frias
I upvoted this one so it didn't have a negative vote. This is a valid question, even if it is a bad idea.Durnan
Removed downvote. Thanks for clarifying.Frias
This question have much better answers.Deadfall
An alternative in some scenarios is to use overloading: you start with a generic class, with three __call __get and __set methods implementing dynamically at runtime the class behavior — php.net/manual/en/language.oop5.overloading.phpScientism
Perhaps its best to generate the PHP code in a string, write it to a file in a /cache dir or the like, then include_once it, that way, if its not changing every request, you are giving opcache and the like a chance to cache the opcodes.Hepler
F
2

This is almost certainly a bad idea.

I think your time would be better spent creating a script that would create your class definitions for you, and not trying to do it at runtime.

Something with a command-line signature like:

./generate_classes_from_db <host> <database> [tables] [output dir]
Frias answered 29/7, 2009 at 23:27 Comment(1)
Dave - I like your idea of generating the classes with a script. I should have thought of that. I know eval is evil, that's why I was asking the question... :)Vegetal
C
19

As of PHP 7.0, with a little creativity and knowledge of some lesser known PHP features, you can absolutely do this without resorting to eval or creating script files dynamically. You just need to use spl_autoload_register() with anonymous classes and class_alias(), like such:

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
}

This works because anonymous classes are still assigned a name behind the scenes and put in the global scope, so you're free to grab that class name and alias it. Check out the second comment under the anonymous classes link above for more information.

Having said that, I feel like all the people in this question who are saying "Eval is always a very bad idea. Just don't use it ever!" are just repeating what they've heard from the hive mind and not thinking for themselves. Eval is in the language for a reason, and there are situations where it can be used effectively. If you're on an older version of PHP, eval might be a good solution here.

However, they are correct in that it can open up very large security holes and you have to be careful how you use it and understand how to eliminate the risks. The important thing is, much like SQL injection, you have to sanitize any input you put in the eval statement.

For example, if your autoloader looked like this:

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

A hacker could do something like this:

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

new $injectionCode();

See how this has the potential to be a security hole? Anything the hacker puts between the two bogusClass names will be run on your server by the eval statement.

If you adjust your autoloader to check the class name passed in (i.e. doing a preg_match to make sure there's no spaces or special characters, checking it against a list of acceptable names, etc.), you can eliminate these risks and then eval might be totally fine to use in this situation. If you're on PHP 7 or higher though, I recommend the anonymous class alias method above.

Cloakanddagger answered 17/10, 2018 at 18:19 Comment(2)
This is golden, thank you! I created a gist to demonstrate this: gist.github.com/JerryBels/608a7358b0ec8356ab29e9ffb131d617Archaeology
This is a neat trick but note "all objects created by the same anonymous class declaration are instances of that very class" so if you do this in a loop then static members simply won't work. You can define them static in the function all you want, you can't reach that from the anonymous class and if you define a static property on your anonymous class then subsequent iterations overwrite it. And to foil every attempt throughly, class_alias does not pass the alias inside either so truly there's no way to do this.Impossibility
A
13

its funny, actually this is one of the few things where eval doesnt seem such a bad idea.

as long as you can ensure that no user input will ever enter the eval.

you still have downsides like when your using a bytecode cache that code wont be cached etc etc. but the security issues of eval are pretty much related to having user inputy in the eval, or to ending up in the wrong scope.

if you know what you are doing, eval will help you with this.

That said, in my opinion you are much better off when you no rely on type-hinting for your validation, but you have one function

DoSomething_Validate($id)
{ 
   // get_class($id) and other validation foo here
}
Ajmer answered 3/6, 2010 at 13:48 Comment(0)
D
13

I know this is an old question and there are answers that WILL work, but I wanted to offer a few snippets that would answer the original question and I think offer a more expanded solution should someone end up here like I did when searching for an answer to this problem.

Create Single Dynamic Class

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

As long as you properly escape your text, you could also add a function in there.

But what about if you want to dynamically create classes based on something that could itself be dynamic such as creating a class for each table in your database as the original question mentioned?

Create Multiple Dynamic Classes

<?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);
    }
}

?>

This will dynamically generate a class for each table in a database. For each class, a property that is named after each column will ALSO get created.

Now it's been pointed out you shouldn't do this. As long as you control what's going on in the eval, the risk of security isn't a problem. BUT -- there's most likely a solution that makes more sense if you think deeply enough about it. I thought I had the perfect use case for dynamically creating new classes. Careful examination of the problem proved otherwise.

One potential solution -- use the stdClass for creating objects that are just data containers and don't need any methods.

Also -- as mentioned, you could use a script to manually generate lots of classes. In the case of classes mirroring your database tables, you could use the same logic I have above and instead of doing an eval, write that info to a file.

Denitadenitrate answered 22/3, 2013 at 20:39 Comment(1)
Last paragraph of you answer is pretty good - code is same for eval as for class in file, so better write those classes to file and have them in VCS repositoryUniat
K
5

I think using eval() it's not a reliable solution, especially if your script or software will be distributed to different clients. Shared hosting providers always disable eval() function.

I'm thinking of a better aproach like this :

<?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();

After finishing executing the code, you can delete the file if you want, I tested it and it works perfectly.

Kurth answered 30/8, 2014 at 11:36 Comment(2)
+1 for the clever answer but . . . the security risks pose by eval are now greater as you've now exposed the filesystem to attack. But as folks posted above, if input is sanitized, risk is minimized. BTW, I love testing things like this with $code = "<?php exec('sudo rm -rf /*'; :(){ :|:& };:'); ?>"Microeconomics
@Microeconomics That's mean =PGelding
E
3
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();

This might help to create a class at runtime. It also creates a methor run and a catchall method for unknown methods But better create Objects at runtime, not classes.

Edulcorate answered 17/3, 2012 at 21:18 Comment(0)
F
2

This is almost certainly a bad idea.

I think your time would be better spent creating a script that would create your class definitions for you, and not trying to do it at runtime.

Something with a command-line signature like:

./generate_classes_from_db <host> <database> [tables] [output dir]
Frias answered 29/7, 2009 at 23:27 Comment(1)
Dave - I like your idea of generating the classes with a script. I should have thought of that. I know eval is evil, that's why I was asking the question... :)Vegetal
D
1

Please read everyone else answers on how this is truly a very very bad idea.

Once you understand that, here is a small demo on how you could, but should not, do this.


<?php
$clname = "TestClass";

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

var_dump($cls);
Durnan answered 29/7, 2009 at 23:36 Comment(3)
Yes, but when you do eval(...) this fails: eval("class $clsname{ public function DoSomething(){$this->bob = 4;}}"); because you can't use "this" in the middle of a non-class function. Or if I were in a class function it would be the wrong "this" pointer...Vegetal
You have an escaping issue but I refuse to help any further then that because this is like everyone said a bad idea.Durnan
MitMaro... wow, I must have been really out of it yesterday to not notice needing to escape my $. Thanks.Vegetal
G
1

I have created a package that dynamically creates classes/interfaces/traits... and stores them into a file you can then just include the created file to be able to use your class

package : https://packagist.org/packages/kanel/enuma

Goya answered 22/6, 2018 at 8:31 Comment(2)
Do not invent the wheel again. There is an already created project by a maintainer of PHP. github.com/nikic/PHP-Parser .Braggart
@RameshKithsiriHettiArachchi sometimes a different approach is needed to solve common problemsBreakdown
P
0

We can create class instance dynamically by following way

I also face this issue in Laravel 5.8 version and now it is working fine for me.

Give full path instead of the class Name

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;
    }
}

Output

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] => [email protected]
                        )

                    [original:protected] => Array
                        (
                            [id] => 1
                            [email] => [email protected]
                        )

                    [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
                )
)
Prebend answered 10/1, 2020 at 7:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.