PHP
Article

Automatic PHP Code Generation with Memio

By Jacek Barecki

Ever thought of writing code responsible for generating certain PHP classes, methods, properties automatically? Read on to get the details on when exactly automatic code generation may be helpful and – what’s most important – how to implement it properly using the Memio library.

Code Generation Hero Image

The Concept

The basic idea is quite simple. You write code which will create other parts of the code, functions, variables, classes, doc blocks, etc. As you do it in a programming language (PHP in our case), you can specify parameters, if-else statements, loops, and so on.

Of course, being able to create PHP code automatically doesn’t mean we, the developers, will be replaced. But it may be used to form a basic structure which will later be further developed by a human. For example, instead of copy-pasting to prepare an initial set of classes in your application, you can use a generator.

Code generation is already being used in various frameworks. See Symfony2 GeneratorBundle, CakePHP console commands or Laravel Artisan for examples.

Generating PHP Classes with Memio

If you want to write your own script that generates PHP code automatically, one of the options is to use the Memio library. The good thing about Memio is that it’s well written, using object-oriented code. You don’t need to write the target code in strings, work on joining string variables, etc. Everything is being done by creating class instances and calling their methods. The templates for the output code itself are stored as Twig templates.

To start using Memio in your project, just add it to the composer.json file, as written in the documentation. The core class, responsible for generating the code – PrettyPrinter – requires a Twig_Environment instance as a constructor argument. It should be initialized the following way:

$twigLoaderFilesystem = new Twig_Loader_Filesystem('vendor/memio/memio/templates');
$twigEnvironment = new Twig_Environment($twigLoaderFilesystem, []);

$memioPrettyPrinter = new \Memio\Memio\PrettyPrinter($twigEnvironment);

To generate some PHP code with Memio, you have to create objects that represent specific code parts, then just pass them to the PrettyPrinter instance and you will get the output code as a result. Each of the objects that represents the auto-generated code is an instance of one of the Memio model classes. To customize the output code, you need to call specific methods on these instances and the Memio library will do the rest when printing. Adding a body to a method, setting the visibility of a property, setting an interface that the class implements – all that is being done by calling the proper methods. Here’s an example of generating a User class:

$class = \Memio\Memio\Model\Object::make('User');

$nameProperty = \Memio\Memio\Model\Property::make('name');
$class->addProperty($nameProperty);

$getNameMethod = \Memio\Memio\Model\Method::make('getName')->setBody('return $this->name');
$class->addMethod($getNameMethod);

echo $memioPrettyPrinter->generateCode($class);

This will produce the following output:

class User
{
    private $name;

    public function getName()
    {
        return $this->name;
    }
}

Real World Example: Object-Relational Mapping

Auto-generating PHP code is often being used when mapping the database structure to PHP classes. Each table in a database can be represented by a separate class in a PHP application. Then each of the table columns will be represented by a class property. Let’s try to write a simple script that will create such classes, based on a MySQL database structure.

To begin with our script, we need to fetch a list of the tables in a selected database. We will also need the list of columns in each of these tables. To get all this data we have to use two MySQL commands: SHOW TABLES at the beginning and then DESC <table name> for each of the tables returned in the first query.

The next step is to generate a separate PHP class for each of the tables. The name of the class will be the same as the table name, just starting with a capital letter:

foreach($tableNames as $table) {
    $class = new \Memio\Memio\Model\Object(ucfirst($table));

    //...
}

To make our classes more useful, we will add class properties representing each of the table columns. Assuming that we stored the column names in a flat array, the code will look as follows:

foreach($columnNames as $column) {
    $property = \Memio\Memio\Model\Property::make($column);
    $class->addProperty($property);
}

And… that’s all! To get the output code for the class, just pass the $class variable to the Memio PrettyPrinter@generateCode method:

$code = $memioPrettyPrinter->generateCode($class);

This lets us automatically generate classes for all of the tables in our database.

Extending the Model Generator

The example above is just a simple introduction to working with Memio. To make our classes more usable, we can extend them in many ways. First, let’s generate getters and setters for each of the properties. Our loop through column names in a table will now look as follows:

foreach($columnNames as $column) {
    $property = \Memio\Memio\Model\Property::make($column);
    $class->addProperty($property);

    $getter = Method::make('get' . ucfirst($column))->setBody('return $this->' . $column . ';');
    $class->addMethod($getter);

    $setterArgument = Argument::make('string', $column);
    $setter = Method::make('set' . ucfirst($column))->addArgument($setterArgument)->setBody('$this->' . $column . ' = $' . $column . ';');
    $class->addMethod($setter);
}

As you can see, we created two variables that instantiate the Memio Method class: $getter and $setter. The name of the methods that will be generated is get<Column> and set<Column>. As the setter method needs an argument, we need to create an instance of the Memio Argument class. Then we pass it to our setter method by calling the addArgument() on the $setter variable. The next step is to add the body to both getter and setter methods, just by calling the setBody() method. Finally, we add these methods to the class by calling the addMethod() on the $class variable.

The example above shows one important aspect of working with Memio. Please notice that we always pass objects representing small parts of code to higher level ones. First comes the method argument (the Argument class). Then we create a method (the Method class) and add the argument to the method ($method->addArgument()). The method should be put inside a class, so we create a class (the Object class) and add the method to the class ($class->addMethod()). So the general idea is to start from small parts of the code and link them to a higher level containers.

To represent the whole code structure in Memio, you can additionally put the output class (the Object class) in a file (instance of the File class). Including the File class in your Memio script allows you to generate code with namespace declaration, license information and the PHP opening tag at the beginning of the output. See the documentation to check how it can be implemented and try adding the proper code by yourself. To get the whole app based on the example above, just check the Github repo connected with the article.

Next Steps

In our example we created just a simple class generator that maps database tables into objects. The sample code can be extended into a much more advanced script. For example, you can use the information about column types from MySQL and validate variables that are being passed to the setter methods. As a next step, you can generate code that persists the object in a database, by transferring an object instance into a MySQL INSERT or UPDATE statement.

Also remember that the output of Memio-based scripts can be modified by changing the default templates. For example, if you want to generate code that adheres to the coding standards used in your project, all you need to do is make a change in the template. Then all the auto-generated code will be produced based on your own coding style and conventions. The template documentation contains all the details on how to replace the default templates with your own files.

I encourage you to write some auto-generating scripts by yourself to check out all the features of the Memio library. Share your results and thoughts in the comments below. Have fun!

Comments
Justin_Dalton

Code generation can be really useful. I used to use a framework called QCodo that was a code generation framework. The framework was really well written, and I used it for several projects. Unfortunately the framework developer let the framework go. A group of developers forked the project into QCubed, but it really wasn't the same without Mike Ho.

Michael_Morris

Word of caution regarding libraries like this and APC cache -- they can easily end up building a code file larger than the code cache allows for, particularly when building dependency injection classes. Granted, you need a very large project to be facing this issue, but it's not inconceivable.

Radek

Hi,
is there a situation where Memio really shines? I got the impression (based on the article and docs) that it is just another code generation library, nothing exciting.

You don’t need to write the target code in strings, work on joining string variables, etc.

Can the setBody call be "worked around"? Can I actually generate the body code?

swader

What other ones do you know of that are good and in sync with modern PHP?

Worked around to do what?

Radek

What other ones do you know of that are good and in sync with modern PHP?

From memory I know of zendframework/zend-code and nette/php-generator.

Worked around to do what?

The article states that I would not need to write target code in strings. On the contrary - compete function body seems to be a string variable. Maybe a I could have asked the question another way: is there an alternative way of setting method body (preferably in compliance with the statement about not needing string variables)?

swader

Hmm, I see. I don't think so. How would you propose that be done? I can't think of a reliable one-size-fits-all solution.

Radek

That is exactly the information I expected to find out in the article smile I can imagine a template method is created beforehand, it's body parsed (nikic/php-parser may help with that), the AST transformed and assembled again. Also a (very) complex AST builder is possible (yet probably impractical).

swader

Interesting. Might be a good project to adopt..

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in PHP, once a week, for free.