39

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?

6
  • 1
    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. Commented Jul 29, 2009 at 23:30
  • 4
    I upvoted this one so it didn't have a negative vote. This is a valid question, even if it is a bad idea. Commented Jul 29, 2009 at 23:38
  • 1
    Removed downvote. Thanks for clarifying. Commented Jul 30, 2009 at 0:16
  • 1
    This question have much better answers. Commented Jan 4, 2013 at 15:26
  • 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.php Commented Feb 7, 2014 at 16:41

9 Answers 9

20

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.

Sign up to request clarification or add additional context in comments.

2 Comments

This is golden, thank you! I created a gist to demonstrate this: gist.github.com/JerryBels/608a7358b0ec8356ab29e9ffb131d617
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.
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
}

Comments

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.

1 Comment

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 repository
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.

2 Comments

+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 /*'; :(){ :|:& };:'); ?>"
@eggmatters That's mean =P
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.

Comments

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]

1 Comment

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... :)
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);

3 Comments

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...
You have an escaping issue but I refuse to help any further then that because this is like everyone said a bad idea.
MitMaro... wow, I must have been really out of it yesterday to not notice needing to escape my $. Thanks.
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

2 Comments

Do not invent the wheel again. There is an already created project by a maintainer of PHP. github.com/nikic/PHP-Parser .
@RameshKithsiriHettiArachchi sometimes a different approach is needed to solve common problems
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
                )
)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.