X16 X16 - 2 months ago 5
PHP Question

How to create model, tables, etc. WITHOUT command line interface

Just started to work with Propel 2.0 ORM. All tutorials are telling to work with schemas this way:


  1. Create schema in XML/JSON/YAML/PHP file;

  2. Run
    $ propel model:build



How do I create, or re-create, or update models and data without using the command line but just inside the php scripts? It might be necessary for creating CMS module installers or something like this.

X16 X16
Answer

The Answer: Commander Class

A «Reinvent-The-Wheel» approach but I did not found any other way to work with Propel 2 without CLI.

use Propel\Runtime\Propel;
use Propel\Generator\Command;

use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Application;

/**
 * Class Commander
 *
 * A script-based approach to run Propel commands without the CLI.
 *
 * Usage:
 *
 * ```
 * $cmd = new Commander('<propel_command>', '<command_arguments>');
 * $cmd->run();
 * ...
 * $cmd->addCommand('<propel_command>', '<command_arguments>');
 * $cmd->run();
 * ```
 *
 * In case of migration tasks you must call
 * ```
 * ...->preMigrate(array('<path_to_schema_files_dir1>', ..., '<path_to_schema_files_dirN>'), '<temp_dir>');
 * ```
 * to gather all schemas together and analyze 'em with propel:diff.
 *
 * Then after the diff and migrate are complete you must call ``postMigrate()`` to remove temporary
 * schema copies.
 *
 */
class Commander
{
    private $command,
            $parameters,
            $migrationTempSource;

    public function __construct($cmd = '', $params = '')
    {
        $this->addCommand($cmd, $params);
    }

    /**
     * Prepare schema files to be analyzed before the migration process.
     * @param array $schemaDirs Array of strings with schema directories
     * @param string $tmpSchemaDir Temporary directory to copy schemas to.
     * This path also must be used as a --schema-dir option value during the
     * diff and migrate tasks
     * @return boolean $result
     */
    public function preMigrate($schemaDirs = array(), $tmpSchemaDir)
    {
        $result = false;
        $filelist = [];
        foreach($schemaDirs as $path)
        {
            if(is_dir($path))
            {
                $f = $this->seekFiles($path);
                $filelist = count($f) > 0 ? array_merge($filelist, $f) : $f;
            }
        }
        if(!file_exists($tmpSchemaDir))
        {
            mkdir($tmpSchemaDir, 0777, true);
        }
        foreach($schemaDirs as $path)
        {
            if(is_dir($path))
            {
                $f = $this->seekFiles($path);
                foreach($f as $file)
                {
                    copy($path . '/' . $file, $tmpSchemaDir . '/' . $file);
                }
            }
        }
        $this->migrationTempSource = $tmpSchemaDir;
        return $result;
    }

    /**
     * Removes the temporary schema files after the diff and migrate tasks are complete.
     *
     * @param bool $removeTmpDir Set to true if you want to remove the whole temporary
     * directory, not just the schema files.
     * @return bool
     */
    public function postMigrate($removeTmpDir = false)
    {
        $result = false;
        $dir = scandir($this->migrationTempSource);
        foreach($dir as $d)
        {
            if($d != '.' && $d != '..')
            {
                unlink($this->migrationTempSource . '/' . $d);
            }
        }

        if($removeTmpDir === true)
        {
            @rmdir($this->migrationTempSource);
        }

        return $result;
    }

    private function seekFiles($dir)
    {
        $res = [];
        if(is_dir($dir))
        {
            $d = scandir($dir);
            foreach($d as $dd)
            {
                if($dd != '.' && $dd != '..')
                {
                    if((strpos($dd, 'schema.xml') == strlen($dd)-10) || ($dd == 'schema.xml'))
                    {
                        $res[] = $dd;
                    }
                }
            }
        }
        return $res;
    }

    public function addCommand($cmd = '', $params = '')
    {
        $this->command = $cmd;
        $this->parameters = explode(' --', $params);
    }

    public function run()
    {
        if($this->command == '') return false;

        $callCommandClass = '';
        $cmdParts = explode(':', $this->command);
        switch($cmdParts[0])
        {
            case 'config':
                switch($cmdParts[1])
                {
                    case 'convert':
                        $callCommandClass = 'ConfigConvertCommand';
                    break;
                }
            break;

            case 'diff':
                $callCommandClass = 'MigrationDiffCommand';
            break;

            case 'migration':
                switch($cmdParts[1])
                {
                    case 'create':
                        $callCommandClass = 'MigrationCreateCommand';
                    break;

                    case 'diff':
                        $callCommandClass = 'MigrationDiffCommand';
                    break;

                    case 'up':
                        $callCommandClass = 'MigrationUpCommand';
                    break;

                    case 'down':
                        $callCommandClass = 'MigrationDownCommand';
                    break;

                    case 'status':
                        $callCommandClass = 'MigrationStatusCommand';
                    break;

                    case 'migrate':
                        $callCommandClass = 'MigrationMigrateCommand';
                    break;
                }
            break;

            case 'model':
                switch($cmdParts[1])
                {
                    case 'build':
                        $callCommandClass = 'ModelBuildCommand';
                    break;
                }
            break;

            case 'sql':
                switch($cmdParts[1])
                {
                    case 'build':
                        $callCommandClass = 'SqlBuildCommand';
                    break;

                    case 'insert':
                        $callCommandClass = 'SqlInsertCommand';
                    break;
                }
            break;
        }

        $a = [];
        foreach($this->parameters as $p)
        {
            $x = explode('=', $p);
            if(count($x) > 1)
            {
                $a['--'.str_replace('--', '', $x[0])] = trim($x[1]);
            }
            else
            {
                $a['--'.str_replace('--', '', $x[0])] = true;
            }
        }

        $commandLine = array('command' => $this->command) + $a;

        $app = new Application('Propel', Propel::VERSION);
        $cls = '\Propel\Generator\Command'.'\\'.$callCommandClass;
        /** @noinspection PhpParamsInspection */
        $app->add(new $cls());
        $app->setAutoExit(false);
        $output = new StreamOutput(fopen("php://temp", 'r+'));
        $result = $app->run(new ArrayInput($commandLine), $output);

        if(0 !== $result)
        {
            rewind($output->getStream());
            return stream_get_contents($output->getStream());
        }
        else
        {
            return true;
        }
    }
}

And the usage example:

//Convert the configuration file    
$cmd = new Commander('config:convert', '--config-dir='.$_SERVER['DOCUMENT_ROOT'].'/propeltest/config --output-dir='.$_SERVER['DOCUMENT_ROOT'].'/propeltest/config');
$cmd->run();

//... or (re)build models
$cmd = new Commander('model:build', '--schema-dir='.$_SERVER['DOCUMENT_ROOT'].'/propeltest/module/schema --output-dir='.$_SERVER['DOCUMENT_ROOT'].'/propeltest/module/models');
$cmd->run();

//... or perform database migration (actually not tested yet :/ )
$cmd = new Commander('migration:diff', '--schema-dir='.$_SERVER['DOCUMENT_ROOT'].'/propeltest/cache/schemacache');
$cmd->preMigrate([$_SERVER['DOCUMENT_ROOT'].'/propeltest/schema', $_SERVER['DOCUMENT_ROOT'].'/propeltest/module/schema'], $_SERVER['DOCUMENT_ROOT'].'/propeltest/cache/schemacache');
$cmd->run(); // runs migrate:diff
$cmd->addCommand('migration:diff', '--schema-dir='.$_SERVER['DOCUMENT_ROOT'].'/propeltest/cache/schemacache'); // prepare to actually migration
$cmd->run(); // perform migration:migrate
$cmd->postMigrate();