Bläddra i källkod

[PropelBundle] Initial commit (WIP) (thanks @fabpot)

Francois Zaninotto 15 år sedan
förälder
incheckning
cb23828a0a

+ 17 - 0
src/Symfony/Framework/PropelBundle/Bundle.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Symfony\Framework\PropelBundle;
+
+use Symfony\Foundation\Bundle\Bundle as BaseBundle;
+use Symfony\Components\DependencyInjection\ContainerInterface;
+use Symfony\Components\DependencyInjection\Loader\Loader;
+use Symfony\Components\DependencyInjection\Loader\XmlFileLoader;
+use Symfony\Framework\PropelBundle\DependencyInjection\PropelExtension;
+
+class Bundle extends BaseBundle
+{
+    public function buildContainer(ContainerInterface $container)
+    {
+        Loader::registerExtension(new PropelExtension());
+    }
+}

+ 246 - 0
src/Symfony/Framework/PropelBundle/Command/BuildCommand.php

@@ -0,0 +1,246 @@
+<?php
+
+namespace Symfony\Framework\PropelBundle\Command;
+
+use Symfony\Components\Console\Command\Command;
+use Symfony\Components\Console\Input\InputArgument;
+use Symfony\Components\Console\Input\InputOption;
+use Symfony\Components\Console\Input\InputInterface;
+use Symfony\Components\Console\Output\OutputInterface;
+use Symfony\Components\Console\Output\Output;
+use Symfony\Framework\WebBundle\Util\Filesystem;
+use Symfony\Components\Finder\Finder;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * BuildCommand.
+ *
+ * @package    Symfony
+ * @subpackage Framework_PropelBundle
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+class BuildCommand extends Command
+{
+    protected $additionalPhingArgs = array();
+
+    /**
+     * @see Command
+     */
+    protected function configure()
+    {
+        $this
+            ->setDefinition(array(
+                new InputOption('--classes', '', InputOption::PARAMETER_NONE, 'Build all classes'),
+            ))
+            ->setName('propel:build')
+        ;
+    }
+
+    /**
+     * @see Command
+     *
+     * @throws \InvalidArgumentException When the target directory does not exist
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        return $this->callPhing('om');
+
+        if (!is_dir($input->getArgument('target'))) {
+            throw new \InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $input->getArgument('target')));
+        }
+
+        $filesystem = new Filesystem();
+
+        $dirs = $this->container->getKernelService()->getBundleDirs();
+        foreach ($this->container->getKernelService()->getBundles() as $bundle) {
+            $tmp = dirname(str_replace('\\', '/', get_class($bundle)));
+            $namespace = str_replace('/', '\\', dirname($tmp));
+            $class = basename($tmp);
+
+            if (isset($dirs[$namespace]) && is_dir($originDir = $dirs[$namespace].'/'.$class.'/Resources/public')) {
+                $output->writeln(sprintf('Installing assets for <comment>%s\\%s</comment>', $namespace, $class));
+
+                $targetDir = $input->getArgument('target').'/bundles/'.preg_replace('/bundle$/', '', strtolower($class));
+
+                $filesystem->remove($targetDir);
+                mkdir($targetDir, 0755, true);
+                $filesystem->mirror($originDir, $targetDir);
+            }
+        }
+    }
+
+    protected function callPhing($taskName, $properties = array())
+    {
+        $kernel = $this->application->getKernel();
+
+        $tmpDir = sys_get_temp_dir().'/propel-gen';
+        $filesystem = new Filesystem();
+        $filesystem->remove($tmpDir);
+        $filesystem->mkdirs($tmpDir);
+
+        $bundleDirs = $kernel->getBundleDirs();
+        foreach ($kernel->getBundles() as $bundle) {
+            $tmp = dirname(str_replace('\\', '/', get_class($bundle)));
+            $namespace = str_replace('/', '\\', dirname($tmp));
+            $class = basename($tmp);
+
+            if (isset($bundleDirs[$namespace]) && is_dir($dir = $bundleDirs[$namespace].'/'.$class.'/Resources/config')) {
+                $finder = new Finder();
+                $schemas = $finder->files()->name('*schema.xml')->followLinks()->in($dir);
+
+                $parts = explode(DIRECTORY_SEPARATOR, realpath($bundleDirs[$namespace]));
+                $prefix = implode('.', array_slice($parts, 1, -1));
+
+                foreach ($schemas as $schema) {
+                    $filesystem->copy((string) $schema, $file = $tmpDir.DIRECTORY_SEPARATOR.md5($schema).'_'.$schema->getBaseName());
+
+                    $content = file_get_contents($file);
+                    $content = preg_replace_callback('/package\s*=\s*"(.*?)"/', function ($matches) use ($prefix) {
+                        return sprintf('package="%s"', $prefix.'.'.$matches[1]);
+                    }, $content);
+
+                    file_put_contents($file, $content);
+                }
+            }
+        }
+
+        $filesystem->touch($tmpDir.'/build.properties');
+
+        $args = array();
+//        $bufferPhingOutput = !$this->commandApplication->withTrace();
+
+        $properties = array_merge(array(
+            'propel.database'   => 'mysql',
+            'project.dir'       => $tmpDir,
+            'propel.output.dir' => $kernel->getRootDir().'/propel',
+            'propel.php.dir'    => '/',
+        ), $properties);
+        foreach ($properties as $key => $value) {
+            $args[] = "-D$key=$value";
+        }
+
+        // Build file
+        $args[] = '-f';
+        $args[] = realpath($kernel->getContainer()->getParameter('propel.generator.path').DIRECTORY_SEPARATOR.'build.xml');
+
+/*
+        // Logger
+        if (DIRECTORY_SEPARATOR != '\\' && (function_exists('posix_isatty') && @posix_isatty(STDOUT))) {
+            $args[] = '-logger';
+            $args[] = 'phing.listener.AnsiColorLogger';
+        }
+
+        // Add our listener to detect errors
+        $args[] = '-listener';
+        $args[] = 'sfPhingListener';
+*/
+        // Add any arbitrary arguments last
+        foreach ($this->additionalPhingArgs as $arg) {
+            if (in_array($arg, array('verbose', 'debug'))) {
+                $bufferPhingOutput = false;
+            }
+
+            $args[] = '-'.$arg;
+        }
+
+        $args[] = $taskName;
+
+        // enable output buffering
+        Phing::setOutputStream(new \OutputStream(fopen('php://output', 'w')));
+        Phing::startup();
+        Phing::setProperty('phing.home', getenv('PHING_HOME'));
+
+//      $this->logSection('propel', 'Running "'.$taskName.'" phing task');
+
+        $bufferPhingOutput = false;
+        if ($bufferPhingOutput) {
+            ob_start();
+        }
+
+        $m = new Phing();
+        $m->execute($args);
+        $m->runBuild();
+
+        if ($bufferPhingOutput) {
+            ob_end_clean();
+        }
+        print $bufferPhingOutput;
+        chdir($kernel->getRootDir());
+/*
+        // any errors?
+        $ret = true;
+        if (sfPhingListener::hasErrors())
+        {
+            $messages = array('Some problems occurred when executing the task:');
+
+            foreach (sfPhingListener::getExceptions() as $exception)
+            {
+              $messages[] = '';
+              $messages[] = preg_replace('/^.*build\-propel\.xml/', 'build-propel.xml', $exception->getMessage());
+              $messages[] = '';
+            }
+
+            if (count(sfPhingListener::getErrors()))
+            {
+              $messages[] = 'If the exception message is not clear enough, read the output of the task for';
+              $messages[] = 'more information';
+            }
+
+            $this->logBlock($messages, 'ERROR_LARGE');
+
+            $ret = false;
+        }
+*/
+
+        $ret = true;
+        return $ret;
+    }
+
+    protected function getPhingPropertiesForConnection($databaseManager, $connection)
+    {
+        $database = $databaseManager->getDatabase($connection);
+
+        return array(
+            'propel.database'          => $database->getParameter('phptype'),
+            'propel.database.driver'   => $database->getParameter('phptype'),
+            'propel.database.url'      => $database->getParameter('dsn'),
+            'propel.database.user'     => $database->getParameter('username'),
+            'propel.database.password' => $database->getParameter('password'),
+            'propel.database.encoding' => $database->getParameter('encoding'),
+        );
+    }
+
+    protected function getProperties($file)
+    {
+        $properties = array();
+
+        if (false === $lines = @file($file)) {
+            throw new sfCommandException('Unable to parse contents of the "sqldb.map" file.');
+        }
+
+        foreach ($lines as $line) {
+            $line = trim($line);
+
+            if ('' == $line) {
+                continue;
+            }
+
+            if (in_array($line[0], array('#', ';'))) {
+                continue;
+            }
+
+            $pos = strpos($line, '=');
+            $properties[trim(substr($line, 0, $pos))] = trim(substr($line, $pos + 1));
+        }
+
+        return $properties;
+    }
+}

+ 34 - 0
src/Symfony/Framework/PropelBundle/Command/Phing.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace Symfony\Framework\PropelBundle\Command;
+
+require_once 'phing/Phing.php';
+
+/**
+ * @package    symfony
+ * @subpackage command
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id: sfPhing.class.php 24039 2009-11-16 17:52:14Z Kris.Wallsmith $
+ */
+class Phing extends \Phing
+{
+    static public function getPhingVersion()
+    {
+        return 'Phing/Symfony';
+    }
+
+    /**
+    * @see Phing
+    */
+    public function runBuild()
+    {
+        // workaround for included phing 2.3 which by default loads many tasks
+        // that are not needed and incompatible (eg phing.tasks.ext.FtpDeployTask)
+        // by placing current directory on the include path our defaults will be loaded
+        // see ticket #5054
+        $includePath = get_include_path();
+        set_include_path(dirname(__FILE__).PATH_SEPARATOR.$includePath);
+        parent::runBuild();
+        set_include_path($includePath);
+    }
+}

+ 130 - 0
src/Symfony/Framework/PropelBundle/DependencyInjection/PropelExtension.php

@@ -0,0 +1,130 @@
+<?php
+
+namespace Symfony\Framework\PropelBundle\DependencyInjection;
+
+use Symfony\Components\DependencyInjection\Loader\LoaderExtension;
+use Symfony\Components\DependencyInjection\Loader\XmlFileLoader;
+use Symfony\Components\DependencyInjection\BuilderConfiguration;
+use Symfony\Components\DependencyInjection\Definition;
+use Symfony\Components\DependencyInjection\Reference;
+
+class PropelExtension extends LoaderExtension
+{
+    protected $resources = array(
+        'propel' => 'propel.xml',
+    );
+
+    /**
+     * Loads the DBAL configuration.
+     *
+     * @param array $config A configuration array
+     *
+     * @return BuilderConfiguration A BuilderConfiguration instance
+     */
+    public function generatorLoad($config)
+    {
+        if (!isset($config['path'])) {
+            throw new \InvalidArgumentException('The "path" parameter is mandatory.');
+        }
+
+        $configuration = new BuilderConfiguration();
+        $configuration->setParameter('propel.generator.path', $config['path']);
+
+        return $configuration;
+    }
+
+    /**
+     * Loads the DBAL configuration.
+     *
+     * @param array $config A configuration array
+     *
+     * @return BuilderConfiguration A BuilderConfiguration instance
+     */
+    public function dbalLoad($config)
+    {
+        $configuration = new BuilderConfiguration();
+
+        $loader = new XmlFileLoader(__DIR__.'/../Resources/config');
+        $configuration->merge($loader->load($this->resources['propel']));
+
+        $defaultConnection = array(
+            'driver'              => 'mysql',
+            'user'                => 'root',
+            'password'            => null,
+            'dsn'                 => null,
+            'classname'           => 'PropelPDO',
+            'options'             => array(),
+            'attributes'          => array(),
+// FIXME: Mysql wants UTF8, not UTF-8 (%kernel.charset%)
+            'settings'            => array('charset' => array('value' => 'UTF8')),
+        );
+
+        $config['default_connection'] = isset($config['default_connection']) ? $config['default_connection'] : 'default';
+
+        $connections = array();
+        if (isset($config['connections'])) {
+            foreach ($config['connections'] as $name => $connection) {
+                $connections[isset($connection['id']) ? $connection['id'] : $name] = $connection;
+            }
+        } else {
+            $connections = array($config['default_connection'] => $config);
+        }
+
+        $c = array('datasources' => array());
+        foreach ($connections as $name => $connection) {
+            $connection = array_replace($defaultConnection, $connection);
+
+            $c['datasources'][$name] = array(
+              'adapter'    => $connection['driver'],
+              'connection' => array(
+                'dsn'        => $connection['dsn'],
+                'user'       => $connection['user'],
+                'password'   => $connection['password'],
+                'classname'  => $connection['classname'],
+                'options'    => $connection['options'],
+                'attributes' => $connection['attributes'],
+                'settings'   => $connection['settings'],
+              ),
+            );
+        }
+//// FIXME
+
+        // $c['classmap'] = //...;
+
+        $configuration->getDefinition('propel.configuration')->setArguments(array($c));
+
+        return $configuration;
+    }
+
+    /**
+     * Returns the base path for the XSD files.
+     *
+     * @return string The XSD base path
+     */
+    public function getXsdValidationBasePath()
+    {
+        return __DIR__.'/../Resources/config/';
+    }
+
+    /**
+     * Returns the namespace to be used for this extension (XML namespace).
+     *
+     * @return string The XML namespace
+     */
+    public function getNamespace()
+    {
+        return 'http://www.symfony-project.org/schema/dic/propel';
+    }
+
+    /**
+     * Returns the recommended alias to use in XML.
+     *
+     * This alias is also the mandatory prefix to use when using YAML.
+     *
+     * @return string The alias
+     */
+    public function getAlias()
+    {
+        return 'propel';
+    }
+}

+ 30 - 0
src/Symfony/Framework/PropelBundle/README

@@ -0,0 +1,30 @@
+Propel Bundle
+=============
+
+This is a (work in progress) implementation of Propel in Symfony 2.
+
+Currently supports:
+
+ * Generation of model classes based on an XML schema (not YAML) placed under `BundleName/Resources/*schema.xml`.
+ * Propel runtime initialization through `$container->getPropelService()`.
+
+Sample Configuration
+--------------------
+
+    propel.generator:
+      path: %kernel.root_dir%/../src/vendor/propel/generator
+
+    propel.dbal:
+      driver:               mysql
+      user:                 root
+      password:             null
+      dsn:                  mysql:host=localhost;dbname=test
+      options:              {}
+    #  default_connection:       default
+    #  connections:
+    #    default:
+    #      driver:               mysql
+    #      user:                 root
+    #      password:             null
+    #      dsn:                  mysql:host=localhost;dbname=test
+    #      options:              {}

+ 27 - 0
src/Symfony/Framework/PropelBundle/Resources/config/propel.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" ?>
+
+<container xmlns="http://www.symfony-project.org/schema/dic/services"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
+
+  <parameters>
+    <parameter key="propel.class">Propel</parameter>
+    <parameter key="propel.configuration.class">PropelConfiguration</parameter>
+  </parameters>
+
+  <services>
+    <service id="propel" class="%propel.class%">
+      <call method="setConfiguration">
+        <argument type="service" id="propel.configuration" />
+      </call>
+      <call method="initialize" />
+    </service>
+
+    <service id="propel.configuration" class="%propel.configuration.class%" />
+<!--
+    <service id="propel.logger" class="%propel.logger.class%">
+      <argument></argument>
+    </service>
+//-->
+  </services>
+</container>