Merge pull request #252 from K-Phoen/2.0

[WIP] Propel2 integration
This commit is contained in:
Toni Uebernickel 2014-09-25 17:37:44 +02:00
commit 3d74fbdb94
121 changed files with 15821 additions and 11 deletions

View file

@ -1,16 +1,18 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.4
- 5.5
- 5.6
matrix:
allow_failures:
- php: 5.5
env:
- SYMFONY_VERSION="~2.3.0"
- SYMFONY_VERSION="~2.4.0"
- SYMFONY_VERSION="~2.5.0"
before_script:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install --dev --prefer-source
- curl -s http://getcomposer.org/installer | php
- php composer.phar require --no-update symfony/symfony:${SYMFONY_VERSION}
- php composer.phar install --prefer-source
script: phpunit --coverage-text
script: ./vendor/bin/phpunit --coverage-text

355
Command/AbstractCommand.php Normal file
View file

@ -0,0 +1,355 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
abstract class AbstractCommand extends ContainerAwareCommand
{
/**
* @var string
*/
protected $cacheDir = null;
/**
* @var Symfony\Component\HttpKernel\Bundle\BundleInterface
*/
protected $bundle = null;
/**
* @var InputInterface
*/
protected $input;
use FormattingHelpers;
/**
* {@inheritdoc}
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$kernel = $this->getApplication()->getKernel();
$this->input = $input;
$this->cacheDir = $kernel->getCacheDir().'/propel';
if ($input->hasArgument('bundle') && '@' === substr($input->getArgument('bundle'), 0, 1)) {
$this->bundle = $this
->getContainer()
->get('kernel')
->getBundle(substr($input->getArgument('bundle'), 1));
}
}
/**
* Create all the files needed by Propel's commands.
*/
protected function setupBuildTimeFiles()
{
$kernel = $this->getApplication()->getKernel();
$fs = new Filesystem();
$fs->mkdir($this->cacheDir);
// collect all schemas
$this->copySchemas($kernel, $this->cacheDir);
// propel.json
$this->createPropelConfigurationFile($this->cacheDir.'/propel.json');
}
/**
* @param KernelInterface $kernel The application kernel.
* @param string $cacheDir The directory in which the schemas will
* be copied.
*/
protected function copySchemas(KernelInterface $kernel, $cacheDir)
{
$filesystem = new Filesystem();
$base = ltrim(realpath($kernel->getRootDir().'/..'), DIRECTORY_SEPARATOR);
$finalSchemas = $this->getFinalSchemas($kernel, $this->bundle);
foreach ($finalSchemas as $schema) {
list($bundle, $finalSchema) = $schema;
$packagePrefix = $this->getPackagePrefix($bundle, $base);
$tempSchema = $bundle->getName().'-'.$finalSchema->getBaseName();
$this->tempSchemas[$tempSchema] = array(
'bundle' => $bundle->getName(),
'basename' => $finalSchema->getBaseName(),
'path' => $finalSchema->getPathname(),
);
$file = $cacheDir.DIRECTORY_SEPARATOR.$tempSchema;
$filesystem->copy((string) $finalSchema, $file, true);
// the package needs to be set absolute
// besides, the automated namespace to package conversion has
// not taken place yet so it needs to be done manually
$database = simplexml_load_file($file);
if (isset($database['package'])) {
// Do not use the prefix!
// This is used to override the package resulting from namespace conversion.
$database['package'] = $database['package'];
} elseif (isset($database['namespace'])) {
$database['package'] = $packagePrefix . str_replace('\\', '.', $database['namespace']);
} else {
throw new \RuntimeException(
sprintf('%s : Please define a `package` attribute or a `namespace` attribute for schema `%s`',
$bundle->getName(), $finalSchema->getBaseName())
);
}
if ($this->input->hasOption('connection')) {
$connections = $this->input->getOption('connection') ?: array($this->getDefaultConnection());
if (!in_array((string) $database['name'], $connections)) {
// we skip this schema because the connection name doesn't
// match the input values
unset($this->tempSchemas[$tempSchema]);
$filesystem->remove($file);
continue;
}
}
foreach ($database->table as $table) {
if (isset($table['package'])) {
$table['package'] = $table['package'];
} elseif (isset($table['namespace'])) {
$table['package'] = $packagePrefix . str_replace('\\', '.', $table['namespace']);
} else {
$table['package'] = $database['package'];
}
}
file_put_contents($file, $database->asXML());
}
}
/**
* Return a list of final schema files that will be processed.
*
* @param KernelInterface $kernel The application kernel.
* @param BundleInterface $bundle If given, only the bundle's schemas will
* be returned.
*
* @return array A list of schemas.
*/
protected function getFinalSchemas(KernelInterface $kernel, BundleInterface $bundle = null)
{
if (null !== $bundle) {
return $this->getSchemaLocator()->locateFromBundle($bundle);
}
return $this->getSchemaLocator()->locateFromBundles($kernel->getBundles());
}
/**
* Run a Symfony command.
*
* @param Command $command The command to run.
* @param array $parameters An array of parameters to give to the command.
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*
* @return int The command return code.
*/
protected function runCommand(Command $command, array $parameters, InputInterface $input, OutputInterface $output)
{
// add the command's name to the parameters
array_unshift($parameters, $this->getName());
// merge the default parameters
$parameters = array_merge(array(
'--input-dir' => $this->cacheDir,
'--verbose' => $input->getOption('verbose'),
), $parameters);
if ($input->hasOption('platform')) {
$parameters['--platform'] = $input->getOption('platform') ?: $this->getPlatform();
}
$command->setApplication($this->getApplication());
// and run the sub-command
return $command->run(new ArrayInput($parameters), $output);
}
/**
* Create an XML file which represents propel.configuration
*
* @param string $file Should be 'propel.json'.
*/
protected function createPropelConfigurationFile($file)
{
$config = array(
'propel' => $this->getContainer()->getParameter('propel.configuration')
);
file_put_contents($file, json_encode($config, JSON_PRETTY_PRINT));
}
/**
* Translates a list of connection names to their DSN equivalents.
*
* @param array $connections The names.
*
* @return array
*/
protected function getConnections(array $connections)
{
$dsnList = array();
foreach ($connections as $connection) {
$dsnList[] = sprintf('%s=%s', $connection, $this->getDsn($connection));
}
return $dsnList;
}
/**
* Get the data (host, user, ...) for a given connection.
*
* @param string $name The connection name.
*
* @return array The connection data.
*/
protected function getConnectionData($name)
{
$knownConnections = $this->getContainer()->getParameter('propel.configuration');
if (!isset($knownConnections['database']['connections'][$name])) {
throw new \InvalidArgumentException(sprintf('Unknown connection "%s"', $name));
}
return $knownConnections['database']['connections'][$name];
}
/**
* Get the DSN for a given connection.
*
* @param string $connectionName The connection name.
*
* @return string The DSN.
*/
protected function getDsn($connectionName)
{
$connection = $this->getConnectionData($connectionName);
return $connection['dsn'];
}
/**
* @return \Symfony\Component\Config\FileLocatorInterface
*/
protected function getSchemaLocator()
{
return $this->getContainer()->get('propel.schema_locator');
}
/**
* Return the package prefix for a given bundle.
*
* @param Bundle $bundle
* @param string $baseDirectory The base directory to exclude from prefix.
*
* @return string
*/
protected function getPackagePrefix(Bundle $bundle, $baseDirectory = '')
{
$parts = explode(DIRECTORY_SEPARATOR, realpath($bundle->getPath()));
$length = count(explode('\\', $bundle->getNamespace())) * (-1);
$prefix = implode(DIRECTORY_SEPARATOR, array_slice($parts, 0, $length));
$prefix = ltrim(str_replace($baseDirectory, '', $prefix), DIRECTORY_SEPARATOR);
if (!empty($prefix)) {
$prefix = str_replace(DIRECTORY_SEPARATOR, '.', $prefix).'.';
}
return $prefix;
}
/**
* Return the current Propel cache directory.
*
* @return string The current Propel cache directory.
*/
protected function getCacheDir()
{
return $this->cacheDir;
}
/**
* Extract the database name from a given DSN
*
* @param string $dsn A DSN
* @return string The database name extracted from the given DSN
*/
protected function parseDbName($dsn)
{
preg_match('#dbname=([a-zA-Z0-9\_]+)#', $dsn, $matches);
if (isset($matches[1])) {
return $matches[1];
}
// e.g. SQLite
return null;
}
/**
* Returns the name of the migrations table.
*
* @return string
*/
protected function getMigrationsTable()
{
$config = $this->getContainer()->getParameter('propel.configuration');
return $config['migrations']['tableName'];
}
/**
* Returns the name of the default connection.
*
* @return string
*/
protected function getDefaultConnection()
{
$config = $this->getContainer()->getParameter('propel.configuration');
return $config['generator']['defaultConnection'];
}
/**
* Reads the platform class from the configuration
*
* @return The platform class name.
*/
protected function getPlatform()
{
$config = $this->getContainer()->getParameter('propel.configuration');
$className = $config['generator']['platformClass'];
return ($pos = strrpos($className, '\\')) === false ? $className : substr($className, $pos + 1);
}
}

130
Command/AclInitCommand.php Normal file
View file

@ -0,0 +1,130 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AclInitCommand extends AbstractCommand
{
protected function configure()
{
$this
->setDescription('Initialize "Access Control Lists" model and SQL')
->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action.')
->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
->setHelp(<<<EOT
The <info>%command.name%</info> command connects to the database and executes all SQL statements required to setup the ACL database, it also generates the ACL model.
<info>php %command.full_name%</info>
The <info>--force</info> parameter has to be used to actually insert SQL.
The <info>--connection</info> parameter allows you to change the connection to use.
The default connection is the active connection (propel.dbal.default_connection).
EOT
)
->setName('propel:acl:init')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$outputDir = realpath($this->getApplication()->getKernel()->getRootDir().'/../');
// Generate ACL model
$modelBuildCmd = new \Propel\Generator\Command\ModelBuildCommand();
$modelBuildArgs = array(
'--output-dir' => $outputDir,
);
if ($this->runCommand($modelBuildCmd, $modelBuildArgs, $input, $output) === 0) {
$output->writeln(sprintf(
'>> <info>%20s</info> Generated model classes from <comment>%s</comment>',
$this->getApplication()->getKernel()->getBundle('PropelBundle')->getName(),
'acl_schema.xml'
));
} else {
$this->writeTaskError($output, 'model:build');
return 1;
}
// Prepare SQL
$sqlBuildCmd = new \Propel\Generator\Command\SqlBuildCommand();
$sqlBuildArgs = array(
'--connection' => $this->getConnections($input->getOption('connection')),
'--output-dir' => $this->getCacheDir(),
);
if ($this->runCommand($sqlBuildCmd, $sqlBuildArgs, $input, $output) === 0) {
$this->writeSection(
$output,
'<comment>1</comment> <info>SQL file has been generated.</info>'
);
} else {
$this->writeTaskError($output, 'sql:build');
return 2;
}
// insert sql
$sqlInsertCmd = new \Propel\Generator\Command\SqlInsertCommand();
$sqlInsertArgs = array(
'--connection' => $this->getConnections($input->getOption('connection')),
);
if ($this->runCommand($sqlBuildCmd, $sqlBuildArgs, $input, $output) === 0) {
$this->writeSection(
$output,
'<comment>1</comment> <info>SQL file has been inserted.</info>'
);
} else {
$this->writeTaskError($output, 'sql:insert');
return 3;
}
}
/**
* {@inheritdoc}
*
* @note We override this method to only return the acl-related schema
*/
protected function getFinalSchemas(KernelInterface $kernel, BundleInterface $bundle = null)
{
$aclSchema = new \SplFileInfo($kernel->locateResource('@PropelBundle/Resources/acl_schema.xml'));
return array(
array($kernel->getBundle('PropelBundle'), $aclSchema)
);
}
/**
* {@inheritdoc}
*
* @note We override this method to modify the cache directory
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
parent::initialize($input, $output);
$this->cacheDir = $this->cacheDir . '/acl';
$this->setupBuildTimeFiles();
}
}

79
Command/BuildCommand.php Normal file
View file

@ -0,0 +1,79 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class BuildCommand extends ContainerAwareCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('propel:build')
->setDescription('Hub for Propel build commands (Model classes, SQL)')
->setDefinition(array(
new InputOption('classes', '', InputOption::VALUE_NONE, 'Build only classes'),
new InputOption('sql', '', InputOption::VALUE_NONE, 'Build only SQL'),
new InputOption('insert-sql', '', InputOption::VALUE_NONE, 'Build all and insert SQL'),
new InputOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
))
;
}
/**
* @see Command
*
* @throws \InvalidArgumentException When the target directory does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!$input->getOption('sql')) {
$in = new ArrayInput(array(
'command' => 'propel:model:build',
'--connection' => $input->getOption('connection'),
'--verbose' => $input->getOption('verbose')
));
$cmd = $this->getApplication()->find('propel:model:build');
$cmd->run($in, $output);
}
if (!$input->getOption('classes')) {
$in = new ArrayInput(array(
'command' => 'propel:build:sql',
'--connection' => $input->getOption('connection'),
'--verbose' => $input->getOption('verbose'),
));
$cmd = $this->getApplication()->find('propel:sql:build');
$cmd->run($in, $output);
}
if ($input->getOption('insert-sql')) {
$in = new ArrayInput(array(
'command' => 'propel:sql:insert',
'--connection' => $input->getOption('connection'),
'--verbose' => $input->getOption('verbose'),
));
$cmd = $this->getApplication()->find('propel:sql:insert');
$cmd->run($in, $output);
}
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Propel\Runtime\Connection\ConnectionManagerSingle;
use Propel\Runtime\Propel;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
/**
* DatabaseCreateCommand class.
* Useful to create a database.
*
* @author William DURAND
*/
class DatabaseCreateCommand extends AbstractCommand
{
/**
* @see Command
*/
protected function configure()
{
$this
->setName('propel:database:create')
->setDescription('Create a given database or the default one.')
->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'Set this parameter to define a connection to use')
;
}
/**
* @see Command
*
* @throws \InvalidArgumentException When the target directory does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$connectionName = $input->getOption('connection') ?: $this->getDefaultConnection();
$config = $this->getConnectionData($connectionName);
$dbName = $this->parseDbName($config['dsn']);
if (null === $dbName) {
return $output->writeln('<error>No database name found.</error>');
} else {
$query = 'CREATE DATABASE '. $dbName .';';
}
try {
$manager = new ConnectionManagerSingle();
$manager->setConfiguration($this->getTemporaryConfiguration($config));
$serviceContainer = Propel::getServiceContainer();
$serviceContainer->setAdapterClass($connectionName, $config['adapter']);
$serviceContainer->setConnectionManager($connectionName, $manager);
$connection = Propel::getConnection($connectionName);
$statement = $connection->prepare($query);
$statement->execute();
$output->writeln(sprintf('<info>Database <comment>%s</comment> has been created.</info>', $dbName));
} catch (\Exception $e) {
$this->writeSection($output, array(
'[Propel] Exception caught',
'',
$e->getMessage()
), 'fg=white;bg=red');
}
}
/**
* Create a temporary configuration to connect to the database in order
* to create a given database. This idea comes from Doctrine1.
*
* @see https://github.com/doctrine/doctrine1/blob/master/lib/Doctrine/Connection.php#L1491
*
* @param array $config A Propel connection configuration.
* @return array
*/
private function getTemporaryConfiguration($config)
{
$dbName = $this->parseDbName($config['dsn']);
$config['dsn'] = preg_replace(
'#dbname='.$dbName.';#',
'',
$config['dsn']
);
return $config;
}
}

View file

@ -0,0 +1,95 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Propel\Runtime\Propel;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* DatabaseDropCommand class.
* Useful to drop a database.
*
* @author William DURAND
*/
class DatabaseDropCommand extends AbstractCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('propel:database:drop')
->setDescription('Drop a given database or the default one.')
->setHelp(<<<EOT
The <info>propel:database:drop</info> command will drop your database.
<info>php app/console propel:database:drop</info>
The <info>--force</info> parameter has to be used to actually drop the database.
The <info>--connection</info> parameter allows you to change the connection to use.
The default connection is the active connection (propel.dbal.default_connection).
EOT
)
->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action.')
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!$input->getOption('force')) {
$output->writeln('<error>You have to use the "--force" option to drop the database.</error>');
return;
}
if ('prod' === $this->getApplication()->getKernel()->getEnvironment()) {
$this->writeSection($output, 'WARNING: you are about to drop a database in production !', 'bg=red;fg=white');
if (false === $this->askConfirmation($output, 'Are you sure ? (y/n) ', false)) {
$output->writeln('Aborted, nice decision !');
return -2;
}
}
$connectionName = $input->getOption('connection') ?: $this->getDefaultConnection();
$config = $this->getConnectionData($connectionName);
$connection = Propel::getConnection($connectionName);
$dbName = $this->parseDbName($config['dsn']);
if (null === $dbName) {
return $output->writeln('<error>No database name found.</error>');
} else {
$query = 'DROP DATABASE '. $dbName .';';
}
try {
$statement = $connection->prepare($query);
$statement->execute();
$output->writeln(sprintf('<info>Database <comment>%s</comment> has been dropped.</info>', $dbName));
} catch (\Exception $e) {
$this->writeSection($output, array(
'[Propel] Exception caught',
'',
$e->getMessage()
), 'fg=white;bg=red');
}
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Propel\Generator\Command\DatabaseReverseCommand as BaseDatabaseReverseCommand;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class DatabaseReverseCommand extends WrappedCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('propel:database:reverse')
->setDescription('Reverse-engineer a XML schema file based on given database')
->addArgument('connection', InputArgument::REQUIRED, 'Connection to use. Example: "default"')
->addOption('output-dir', null, InputOption::VALUE_REQUIRED, 'The output directory', BaseDatabaseReverseCommand::DEFAULT_OUTPUT_DIRECTORY)
->addOption('database-name', null, InputOption::VALUE_REQUIRED, 'The database name to reverse', BaseDatabaseReverseCommand::DEFAULT_DATABASE_NAME)
->addOption('schema-name', null, InputOption::VALUE_REQUIRED, 'The schema name to generate', BaseDatabaseReverseCommand::DEFAULT_SCHEMA_NAME)
;
}
/**
* {@inheritdoc}
*/
protected function createSubCommandInstance()
{
return new BaseDatabaseReverseCommand();
}
/**
* {@inheritdoc}
*/
protected function getSubCommandArguments(InputInterface $input)
{
return array(
'--output-dir' => $input->getOption('output-dir'),
'--database-name' => $input->getOption('database-name'),
'--schema-name' => $input->getOption('schema-name'),
// this one is an argument, so no leading '--'
'connection' => $this->getDsn($input->getArgument('connection')),
);
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
/**
* FixturesDumpCommand.
*
* @author William DURAND <william.durand1@gmail.com>
*/
class FixturesDumpCommand extends AbstractCommand
{
/**
* Default fixtures directory.
* @var string
*/
protected $defaultFixturesDir = 'app/propel/fixtures';
/**
* @see Command
*/
protected function configure()
{
$this
->setName('propel:fixtures:dump')
->setDescription('Dump data from database into YAML fixtures file.')
->setHelp(<<<EOT
The <info>propel:fixtures:dump</info> dumps data from database into YAML fixtures file.
<info>php app/console propel:fixtures:dump</info>
The <info>--connection</info> parameter allows you to change the connection to use.
The <info>--dir</info> parameter allows you to change the output directory.
The default connection is the active connection (propel.dbal.default_connection).
EOT
)
->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'Set this parameter to define a connection to use')
->addOption('dir', null, InputOption::VALUE_OPTIONAL, 'Set this parameter to define a fixture directory')
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$fixtureDir = $input->getOption('dir') ?: $this->defaultFixturesDir;
$path = realpath($this->getApplication()->getKernel()->getRootDir() . '/../') . '/' . $fixtureDir;
if (!file_exists($path)) {
$output->writeln("<info>The $path folder does not exists.</info>");
if ($this->askConfirmation($output, "<question>Do you want me to create it for you ?</question> [Yes]")) {
$fs = new Filesystem();
$fs->mkdir($path);
$this->writeNewDirectory($output, $path);
} else {
throw new \IOException(sprintf('Unable to find the %s folder', $path));
}
}
$filename = $path . '/fixtures_' . time() . '.yml';
$dumper = $this->getContainer()->get('propel.dumper.yaml');
try {
$dumper->dump($filename, $input->getOption('connection'));
} catch (\Exception $e) {
$this->writeSection($output, array(
'[Propel] Exception',
'',
$e->getMessage()), 'fg=white;bg=red');
return false;
}
$this->writeNewFile($output, $filename);
return true;
}
}

View file

@ -0,0 +1,326 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
/**
* FixturesLoadCommand
*
* @author William DURAND <william.durand1@gmail.com>
*/
class FixturesLoadCommand extends AbstractCommand
{
/**
* Default fixtures directory.
* @var string
*/
private $defaultFixturesDir = 'app/propel/fixtures';
/**
* Absolute path for fixtures directory
* @var string
*/
private $absoluteFixturesPath = '';
/**
* Filesystem for manipulating files
* @var \Symfony\Component\Filesystem\Filesystem
*/
private $filesystem = null;
/**
* @see Command
*/
protected function configure()
{
$this
->setName('propel:fixtures:load')
->setDescription('Load XML, SQL and/or YAML fixtures')
->setHelp(<<<EOT
The <info>propel:fixtures:load</info> loads <info>XML</info>, <info>SQL</info> and/or <info>YAML</info> fixtures.
<info>php app/console propel:fixtures:load</info>
The <info>--connection</info> parameter allows you to change the connection to use.
The default connection is the active connection (propel.dbal.default_connection).
The <info>--dir</info> parameter allows you to change the directory that contains <info>XML</info> or/and <info>SQL</info> fixtures files <comment>(default: app/propel/fixtures)</comment>.
The <info>--xml</info> parameter allows you to load only <info>XML</info> fixtures.
The <info>--sql</info> parameter allows you to load only <info>SQL</info> fixtures.
The <info>--yml</info> parameter allows you to load only <info>YAML</info> fixtures.
You can mix <info>--xml</info>, <info>--sql</info> and <info>--yml</info> parameters to load XML, YAML and SQL fixtures at the same time.
If none of this parameter is set, all XML, YAML and SQL files in the directory will be load.
XML fixtures files are the same XML files you can get with the command <info>propel:data-dump</info>:
<comment>
<Fixtures>
<Object Namespace="Awesome">
<o1 Title="My title" MyFoo="bar" />
</Object>
<Related Namespace="Awesome">
<r1 ObjectId="o1" Description="Hello world !" />
</Related>
</Fixtures>
</comment>
YAML fixtures are:
<comment>
\Awesome\Object:
o1:
Title: My title
MyFoo: bar
\Awesome\Related:
r1:
ObjectId: o1
Description: Hello world !
</comment>
EOT
)
->addArgument('bundle', InputArgument::OPTIONAL, 'The bundle to load fixtures from')
->addOption(
'dir', 'd', InputOption::VALUE_OPTIONAL,
'The directory where XML, SQL and/or YAML fixtures files are located',
$this->defaultFixturesDir
)
->addOption('xml', '', InputOption::VALUE_NONE, 'Load XML fixtures')
->addOption('sql', '', InputOption::VALUE_NONE, 'Load SQL fixtures')
->addOption('yml', '', InputOption::VALUE_NONE, 'Load YAML fixtures')
->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'Set this parameter to define a connection to use')
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->filesystem = new Filesystem();
if (null !== $this->bundle) {
$this->absoluteFixturesPath = $this->getFixturesPath($this->bundle);
} else {
$this->absoluteFixturesPath = realpath($this->getApplication()->getKernel()->getRootDir() . '/../' . $input->getOption('dir'));
}
if (!$this->absoluteFixturesPath && !file_exists($this->absoluteFixturesPath)) {
return $this->writeSection($output, array(
'The fixtures directory "' . $this->absoluteFixturesPath . '" does not exist.'
), 'fg=white;bg=red');
}
$noOptions = !$input->getOption('xml') && !$input->getOption('sql') && !$input->getOption('yml');
if ($input->getOption('sql') || $noOptions) {
if (-1 === $this->loadSqlFixtures($input, $output)) {
$output->writeln('No <info>SQL</info> fixtures found.');
}
}
if ($input->getOption('xml') || $noOptions) {
if (-1 === $this->loadFixtures($input, $output, 'xml')) {
$output->writeln('No <info>XML</info> fixtures found.');
}
}
if ($input->getOption('yml') || $noOptions) {
if (-1 === $this->loadFixtures($input, $output, 'yml')) {
$output->writeln('No <info>YML</info> fixtures found.');
}
}
}
/**
* Load fixtures
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param string $type If specified, only fixtures with the given type will be loaded (yml, xml).
*/
protected function loadFixtures(InputInterface $input, OutputInterface $output, $type = null)
{
if (null === $type) {
return;
}
$datas = $this->getFixtureFiles($type);
if (count(iterator_to_array($datas)) === 0) {
return -1;
}
$connectionName = $input->getOption('connection');
if ('yml' === $type) {
$loader = $this->getContainer()->get('propel.loader.yaml');
} elseif ('xml' === $type) {
$loader = $this->getContainer()->get('propel.loader.xml');
} else {
return;
}
try {
$nb = $loader->load($datas, $connectionName);
} catch (\Exception $e) {
$this->writeSection($output, array(
'[Propel] Exception',
'',
$e->getMessage()), 'fg=white;bg=red');
return false;
}
$output->writeln(sprintf('<comment>%s</comment> %s fixtures file%s loaded.', $nb, strtoupper($type), $nb > 1 ? 's' : ''));
return true;
}
/**
* Load SQL fixtures
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
protected function loadSqlFixtures(InputInterface $input, OutputInterface $output)
{
$tmpdir = $this->getCacheDir();
$datas = $this->getFixtureFiles('sql');
$this->prepareCache($tmpdir);
$connectionName = $input->getOption('connection') ?: $this->getContainer()->getParameter('propel.dbal.default_connection');
// Create a "sqldb.map" file
$sqldbContent = '';
foreach ($datas as $data) {
$output->writeln(sprintf('<info>Loading SQL fixtures from</info> <comment>%s</comment>.', $data));
$sqldbContent .= $data->getFilename() . '=' . $connectionName . PHP_EOL;
$this->filesystem->copy($data, $tmpdir . '/' . $data->getFilename(), true);
}
if ('' === $sqldbContent) {
return -1;
}
$sqldbFile = $tmpdir . '/sqldb.map';
file_put_contents($sqldbFile, $sqldbContent);
if (!$this->insertSql($connectionName, $input, $output)) {
return -1;
}
$this->filesystem->remove($tmpdir);
return 0;
}
/**
* Prepare the cache directory
*
* @param string $tmpdir The temporary directory path.
*/
protected function prepareCache($tmpdir)
{
// Recreate a propel directory in cache
$this->filesystem->remove($tmpdir);
$this->filesystem->mkdir($tmpdir);
}
/**
* Insert SQL
*/
protected function insertSql($connectionName, InputInterface $input, OutputInterface $output)
{
$parameters = array(
'--connection' => array($connectionName),
'--verbose' => $input->getOption('verbose'),
);
// add the command's name to the parameters
array_unshift($parameters, $this->getName());
$command = $this->getApplication()->find('propel:sql:insert');
$command->setApplication($this->getApplication());
// and run the sub-command
$ret = $command->run(new ArrayInput($parameters), $output);
if ($ret === 0) {
$output->writeln('All SQL statements have been inserted.');
} else {
$this->writeTaskError($output, 'insert-sql', false);
}
return $ret === 0;
}
/**
* Returns the fixtures files to load.
*
* @param string $type The extension of the files.
* @param string $in The directory in which we search the files. If null,
* we'll use the absoluteFixturesPath property.
*
* @return \Iterator An iterator through the files.
*/
protected function getFixtureFiles($type = 'sql', $in = null)
{
$finder = new Finder();
$finder->sortByName()->name('*.' . $type);
$files = $finder->in(null !== $in ? $in : $this->absoluteFixturesPath);
if (null === $this->bundle) {
return $files;
}
$finalFixtureFiles = array();
foreach ($files as $file) {
$fixtureFilePath = str_replace($this->getFixturesPath($this->bundle) . DIRECTORY_SEPARATOR, '', $file->getRealPath());
$logicalName = sprintf('@%s/Resources/fixtures/%s', $this->bundle->getName(), $fixtureFilePath);
$finalFixtureFiles[] = new \SplFileInfo($this->getFileLocator()->locate($logicalName));
}
return new \ArrayIterator($finalFixtureFiles);
}
/**
* Returns the path the command will look into to find fixture files
*
* @param BundleInterface $bundle The bundle to explore.
*
* @return String
*/
protected function getFixturesPath(BundleInterface $bundle)
{
return $bundle->getPath() . DIRECTORY_SEPARATOR . 'Resources' . DIRECTORY_SEPARATOR . 'fixtures';
}
/**
* @return \Symfony\Component\Config\FileLocatorInterface
*/
protected function getFileLocator()
{
return $this->getContainer()->get('file_locator');
}
}

View file

@ -0,0 +1,257 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Propel\Generator\Config\GeneratorConfig;
use Propel\Generator\Command\ModelBuildCommand as BaseModelBuildCommand;
use Propel\Generator\Model\Database;
use Propel\Generator\Model\Table;
use Propel\Generator\Manager\ModelManager;
use Propel\Runtime\Propel;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
/**
* @author William DURAND <william.durand1@gmail.com>
*/
class FormGenerateCommand extends AbstractCommand
{
const DEFAULT_FORM_TYPE_DIRECTORY = '/Form/Type';
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('propel:form:generate')
->setDescription('Generate Form types stubs based on the schema.xml')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Overwrite existing Form types')
->addOption('platform', null, InputOption::VALUE_REQUIRED, 'The platform')
->addArgument('bundle', InputArgument::REQUIRED, 'The bundle to use to generate Form types (Ex: @AcmeDemoBundle)')
->addArgument('models', InputArgument::IS_ARRAY, 'Model classes to generate Form Types from')
->setHelp(<<<EOT
The <info>%command.name%</info> command allows you to quickly generate Form Type stubs for a given bundle.
<info>php app/console %command.full_name%</info>
The <info>--force</info> parameter allows you to overwrite existing files.
EOT
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$kernel = $this->getApplication()->getKernel();
$models = $input->getArgument('models');
$force = $input->getOption('force');
if (!$this->bundle) {
throw new \InvalidArgumentException('No valid bundle given');
}
$this->setupBuildTimeFiles();
if (!($schemas = $this->getFinalSchemas($kernel, $this->bundle))) {
$output->writeln(sprintf('No <comment>*schemas.xml</comment> files found in bundle <comment>%s</comment>.', $this->bundle->getName()));
return;
}
$manager = $this->getModelManager($input, $schemas);
foreach ($manager->getDataModels() as $dataModel) {
foreach ($dataModel->getDatabases() as $database) {
$this->createFormTypeFromDatabase($this->bundle, $database, $models, $output, $force);
}
}
}
/**
* Create FormTypes from a given database, bundle and models.
*
* @param BundleInterface $bundle The bundle for which the FormTypes will be generated.
* @param Database $database The database to inspect.
* @param array $models The models to build.
* @param OutputInterface $output An OutputInterface instance
* @param boolean $force Override files if present.
*/
protected function createFormTypeFromDatabase(BundleInterface $bundle, Database $database, $models, OutputInterface $output, $force = false)
{
$dir = $this->createDirectory($bundle, $output);
foreach ($database->getTables() as $table) {
if (0 < count($models) && !in_array($table->getPhpName(), $models)) {
continue;
}
$file = new \SplFileInfo(sprintf('%s/%sType.php', $dir, $table->getPhpName()));
if (!file_exists($file) || true === $force) {
$this->writeFormType($bundle, $table, $file, $force, $output);
} else {
$output->writeln(sprintf('File <comment>%-60s</comment> exists, skipped. Try the <info>--force</info> option.', $this->getRelativeFileName($file)));
}
}
}
/**
* Create the FormType directory and log the result.
*
* @param BundleInterface $bundle The bundle in which we'll create the directory.
* @param OutputInterface $output An OutputInterface instance.
*
* @return string The path to the created directory.
*/
protected function createDirectory(BundleInterface $bundle, OutputInterface $output)
{
$fs = new Filesystem();
if (!$fs->exists($dir = $bundle->getPath() . self::DEFAULT_FORM_TYPE_DIRECTORY)) {
$fs->mkdir($dir);
$this->writeNewDirectory($output, $dir);
}
return $dir;
}
/**
* Write a FormType.
*
* @param BundleInterface $bundle The bundle in which the FormType will be created.
* @param Table $table The table for which the FormType will be created.
* @param SplFileInfo $file File representing the FormType.
* @param boolean $force Is the write forced?
* @param OutputInterface $output An OutputInterface instance.
*/
protected function writeFormType(BundleInterface $bundle, Table $table, \SplFileInfo $file, $force, OutputInterface $output)
{
$modelName = $table->getPhpName();
$formTypeContent = file_get_contents(__DIR__ . '/../Resources/skeleton/FormType.php');
$formTypeContent = str_replace('##NAMESPACE##', $bundle->getNamespace() . str_replace('/', '\\', self::DEFAULT_FORM_TYPE_DIRECTORY), $formTypeContent);
$formTypeContent = str_replace('##CLASS##', $modelName . 'Type', $formTypeContent);
$formTypeContent = str_replace('##FQCN##', sprintf('%s\%s', $table->getNamespace(), $modelName), $formTypeContent);
$formTypeContent = str_replace('##TYPE_NAME##', strtolower($modelName), $formTypeContent);
$formTypeContent = $this->addFields($table, $formTypeContent);
file_put_contents($file->getPathName(), $formTypeContent);
$this->writeNewFile($output, $this->getRelativeFileName($file) . ($force ? ' (forced)' : ''));
}
/**
* Add the fields in the FormType.
*
* @param Table $table Table from which the fields will be extracted.
* @param string $formTypeContent FormType skeleton.
*
* @return string The FormType code.
*/
protected function addFields(Table $table, $formTypeContent)
{
$buildCode = '';
foreach ($table->getColumns() as $column) {
if (!$column->isPrimaryKey()) {
$buildCode .= sprintf("\n \$builder->add('%s');", lcfirst($column->getPhpName()));
}
}
return str_replace('##BUILD_CODE##', $buildCode, $formTypeContent);
}
/**
* @param \SplFileInfo $file
* @return string
*/
protected function getRelativeFileName(\SplFileInfo $file)
{
return substr(str_replace(realpath($this->getContainer()->getParameter('kernel.root_dir') . '/../'), '', $file), 1);
}
/**
* Get the GeneratorConfig instance to use.
*
* @param InputInterface $input An InputInterface instance.
*
* @return GeneratorConfig
*/
protected function getGeneratorConfig(InputInterface $input)
{
$generatorConfig = array(
'propel.platform.class' => $input->getOption('platform'),
'propel.builder.object.class' => BaseModelBuildCommand::DEFAULT_OBJECT_BUILDER,
'propel.builder.objectstub.class' => BaseModelBuildCommand::DEFAULT_OBJECT_STUB_BUILDER,
'propel.builder.objectmultiextend.class' => BaseModelBuildCommand::DEFAULT_MULTIEXTEND_OBJECT_BUILDER,
'propel.builder.query.class' => BaseModelBuildCommand::DEFAULT_QUERY_BUILDER,
'propel.builder.querystub.class' => BaseModelBuildCommand::DEFAULT_QUERY_STUB_BUILDER,
'propel.builder.queryinheritance.class' => BaseModelBuildCommand::DEFAULT_QUERY_INHERITANCE_BUILDER,
'propel.builder.queryinheritancestub.class' => BaseModelBuildCommand::DEFAULT_QUERY_INHERITANCE_STUB_BUILDER,
'propel.builder.tablemap.class' => BaseModelBuildCommand::DEFAULT_TABLEMAP_BUILDER,
'propel.builder.pluralizer.class' => BaseModelBuildCommand::DEFAULT_PLURALIZER,
'propel.disableIdentifierQuoting' => true,
'propel.packageObjectModel' => true,
'propel.namespace.autoPackage' => true,
'propel.addGenericAccessors' => true,
'propel.addGenericMutators' => true,
'propel.addSaveMethod' => true,
'propel.addTimeStamp' => false,
'propel.addValidateMethod' => true,
'propel.addHooks' => true,
'propel.namespace.map' => 'Map',
'propel.useLeftJoinsInDoJoinMethods' => true,
'propel.emulateForeignKeyConstraints' => false,
'propel.schema.autoPrefix' => false,
'propel.dateTimeClass' => '\DateTime',
// MySQL specific
'propel.mysql.tableType' => BaseModelBuildCommand::DEFAULT_MYSQL_ENGINE,
'propel.mysql.tableEngineKeyword' => 'ENGINE',
);
// merge the custom build properties
$buildProperties = parse_ini_file($this->getCacheDir().'/build.properties');
$generatorConfig = array_merge($generatorConfig, $buildProperties);
return new GeneratorConfig($generatorConfig);
}
/**
* Get the ModelManager to use.
*
* @param InputInterface $input An InputInterface instance.
* @param array $schemas A list of schemas.
*
* @return ModelManager
*/
protected function getModelManager(InputInterface $input, array $schemas)
{
$schemaFiles = array();
foreach ($schemas as $data) {
$schemaFiles[] = $data[1];
}
$manager = new ModelManager();
$manager->setFilesystem(new Filesystem());
$manager->setGeneratorConfig($this->getGeneratorConfig($input));
$manager->setSchemas($schemaFiles);
return $manager;
}
}

View file

@ -0,0 +1,84 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
trait FormattingHelpers
{
/**
* Comes from the SensioGeneratorBundle.
* @see https://github.com/sensio/SensioGeneratorBundle/blob/master/Command/Helper/DialogHelper.php#L52
*
* @param OutputInterface $output The output.
* @param string $text A text message.
* @param string $style A style to apply on the section.
*/
protected function writeSection(OutputInterface $output, $text, $style = 'bg=blue;fg=white')
{
$output->writeln(array(
'',
$this->getHelperSet()->get('formatter')->formatBlock($text, $style, true),
'',
));
}
/**
* Ask confirmation from the user.
*
* @param OutputInterface $output The output.
* @param string $question A given question.
* @param string $default A default response.
*/
protected function askConfirmation(OutputInterface $output, $question, $default = null)
{
return $this->getHelperSet()->get('dialog')->askConfirmation($output, $question, $default);
}
/**
* @param OutputInterface $output The output.
* @param string $filename The filename.
*/
protected function writeNewFile(OutputInterface $output, $filename)
{
$output->writeln('>> <info>File+</info> ' . $filename);
}
/**
* @param OutputInterface $output The output.
* @param string $directory The directory.
*/
protected function writeNewDirectory(OutputInterface $output, $directory)
{
$output->writeln('>> <info>Dir+</info> ' . $directory);
}
/**
* Renders an error message if a task has failed.
*
* @param OutputInterface $output The output.
* @param string $taskName A task name.
* @param Boolean $more Whether to add a 'more details' message or not.
*/
protected function writeTaskError($output, $taskName, $more = true)
{
$moreText = $more ? ' To get more details, run the command with the "--verbose" option.' : '';
return $this->writeSection($output, array(
'[Propel] Error',
'',
'An error has occured during the "' . $taskName . '" task process.' . $moreText
), 'fg=white;bg=red');
}
}

View file

@ -0,0 +1,55 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Propel\Generator\Command\GraphvizGenerateCommand as BaseGraphvizGenerateCommand;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class GraphvizGenerateCommand extends WrappedCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('propel:graphviz:generate')
->setDescription('Generate Graphviz files (.dot)')
->addOption('output-dir', null, InputOption::VALUE_REQUIRED, 'The output directory', BaseGraphvizGenerateCommand::DEFAULT_OUTPUT_DIRECTORY)
;
}
/**
* {@inheritdoc}
*/
protected function createSubCommandInstance()
{
return new BaseGraphvizGenerateCommand();
}
/**
* {@inheritdoc}
*/
protected function getSubCommandArguments(InputInterface $input)
{
return array(
'--output-dir' => $input->getOption('output-dir'),
);
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Propel\Generator\Command\MigrationDiffCommand as BaseMigrationCommand;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class MigrationDiffCommand extends WrappedCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('propel:migration:diff')
->setDescription('Generate diff classes')
->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
->addOption('output-dir', null, InputOption::VALUE_OPTIONAL, 'The output directory')
->addOption('migration-table', null, InputOption::VALUE_OPTIONAL, 'Migration table name (if none given, the configured table is used)', null)
->addOption('table-renaming', null, InputOption::VALUE_NONE, 'Detect table renaming', null)
->addOption('editor', null, InputOption::VALUE_OPTIONAL, 'The text editor to use to open diff files', null)
->addOption('skip-removed-table', null, InputOption::VALUE_NONE, 'Option to skip removed table from the migration')
->addOption('skip-tables', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'List of excluded tables', array())
;
}
/**
* {@inheritdoc}
*/
protected function createSubCommandInstance()
{
return new BaseMigrationCommand();
}
/**
* {@inheritdoc}
*/
protected function getSubCommandArguments(InputInterface $input)
{
$defaultOutputDir = $this->getApplication()->getKernel()->getRootDir().'/propel/migrations';
return array(
'--connection' => $this->getConnections($input->getOption('connection')),
'--migration-table' => $input->getOption('migration-table') ?: $this->getMigrationsTable(),
'--output-dir' => $input->getOption('output-dir') ?: $defaultOutputDir,
'--table-renaming' => $input->getOption('table-renaming'),
'--editor' => $input->getOption('editor'),
'--skip-removed-table' => $input->getOption('skip-removed-table'),
'--skip-tables' => $input->getOption('skip-tables'),
);
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Propel\Generator\Command\MigrationDownCommand as BaseMigrationCommand;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class MigrationDownCommand extends WrappedCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('propel:migration:down')
->setDescription('Execute migrations down')
->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
->addOption('migration-table', null, InputOption::VALUE_OPTIONAL, 'Migration table name (if none given, the configured table is used)', null)
->addOption('output-dir', null, InputOption::VALUE_OPTIONAL, 'The output directory')
;
}
/**
* {@inheritdoc}
*/
protected function createSubCommandInstance()
{
return new BaseMigrationCommand();
}
/**
* {@inheritdoc}
*/
protected function getSubCommandArguments(InputInterface $input)
{
$defaultOutputDir = $this->getApplication()->getKernel()->getRootDir().'/propel/migrations';
return array(
'--connection' => $this->getConnections($input->getOption('connection')),
'--migration-table' => $input->getOption('migration-table') ?: $this->getMigrationsTable(),
'--output-dir' => $input->getOption('output-dir') ?: $defaultOutputDir,
);
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Propel\Generator\Command\MigrationStatusCommand as BaseMigrationCommand;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class MigrationStatusCommand extends WrappedCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('propel:migration:status')
->setDescription('Get migration status')
->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
->addOption('migration-table', null, InputOption::VALUE_OPTIONAL, 'Migration table name (if none given, the configured table is used)', null)
->addOption('output-dir', null, InputOption::VALUE_OPTIONAL, 'The output directory')
;
}
/**
* {@inheritdoc}
*/
protected function createSubCommandInstance()
{
return new BaseMigrationCommand();
}
/**
* {@inheritdoc}
*/
protected function getSubCommandArguments(InputInterface $input)
{
$defaultOutputDir = $this->getApplication()->getKernel()->getRootDir().'/propel/migrations';
return array(
'--connection' => $this->getConnections($input->getOption('connection')),
'--migration-table' => $input->getOption('migration-table') ?: $this->getMigrationsTable(),
'--output-dir' => $input->getOption('output-dir') ?: $defaultOutputDir,
);
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Propel\Generator\Command\MigrationUpCommand as BaseMigrationCommand;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class MigrationUpCommand extends WrappedCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('propel:migration:up')
->setDescription('Execute migrations up')
->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
->addOption('migration-table', null, InputOption::VALUE_OPTIONAL, 'Migration table name (if none given, the configured table is used)', null)
->addOption('output-dir', null, InputOption::VALUE_OPTIONAL, 'The output directory')
;
}
/**
* {@inheritdoc}
*/
protected function createSubCommandInstance()
{
return new BaseMigrationCommand();
}
/**
* {@inheritdoc}
*/
protected function getSubCommandArguments(InputInterface $input)
{
$defaultOutputDir = $this->getApplication()->getKernel()->getRootDir().'/propel/migrations';
return array(
'--connection' => $this->getConnections($input->getOption('connection')),
'--migration-table' => $input->getOption('migration-table') ?: $this->getMigrationsTable(),
'--output-dir' => $input->getOption('output-dir') ?: $defaultOutputDir,
);
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Propel\Generator\Command\ModelBuildCommand as BaseModelBuildCommand;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class ModelBuildCommand extends WrappedCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('propel:model:build')
->setDescription('Build the model classes based on Propel XML schemas')
->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
->addArgument('bundle', InputArgument::OPTIONAL, 'The bundle to generate model classes from')
;
}
/**
* {@inheritdoc}
*/
protected function createSubCommandInstance()
{
return new BaseModelBuildCommand();
}
/**
* {@inheritdoc}
*/
protected function getSubCommandArguments(InputInterface $input)
{
$outputDir = $this->getApplication()->getKernel()->getRootDir().'/../';
return array(
'--output-dir' => $outputDir,
);
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class SqlBuildCommand extends WrappedCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this
->setName('propel:sql:build')
->setDescription('Build SQL files')
->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
;
}
/**
* {@inheritdoc}
*/
protected function createSubCommandInstance()
{
return new \Propel\Generator\Command\SqlBuildCommand();
}
/**
* {@inheritdoc}
*/
protected function getSubCommandArguments(InputInterface $input)
{
return array(
'--output-dir' => $this->getCacheDir(),
'--connection' => $this->getConnections($input->getOption('connection')),
);
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class SqlInsertCommand extends WrappedCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('propel:sql:insert')
->setDescription('Insert SQL statements')
->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
;
}
/**
* {@inheritdoc}
*/
protected function createSubCommandInstance()
{
return new \Propel\Generator\Command\SqlInsertCommand();
}
/**
* {@inheritdoc}
*/
protected function getSubCommandArguments(InputInterface $input)
{
return array(
'--connection' => $this->getConnections($input->getOption('connection')),
'--sql-dir' => $this->cacheDir,
);
}
}

View file

@ -0,0 +1,120 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Propel\Runtime\Adapter\Pdo\MysqlAdapter;
use Propel\Runtime\Propel;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class TableDropCommand extends ContainerAwareCommand
{
use FormattingHelpers;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('propel:table:drop')
->setDescription('Drop a given table or all tables in the database.')
->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action.')
->addArgument('table', InputArgument::IS_ARRAY, 'Set this parameter to défine which table to delete (default all the table in the database.')
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$connection = Propel::getConnection($input->getOption('connection'));
$adapter = Propel::getAdapter($connection->getName());
if (!$adapter instanceof MysqlAdapter) {
return $output->writeln('<error>This command is MySQL only.</error>');
}
if (!$input->getOption('force')) {
return $output->writeln('<error>You have to use the "--force" option to drop some tables.</error>');
}
$tablesToDelete = $input->getArgument('table');
$nbTable = count($tablesToDelete);
$tablePlural = (($nbTable > 1 || $nbTable == 0) ? 's' : '' );
if ('prod' === $this->getApplication()->getKernel()->getEnvironment()) {
$count = $nbTable ?: 'all';
$this->writeSection(
$output,
'WARNING: you are about to drop ' . $count . ' table' . $tablePlural . ' in production !',
'bg=red;fg=white'
);
if (false === $this->askConfirmation($output, 'Are you sure ? (y/n) ', false)) {
$output->writeln('<info>Aborted, nice decision !</info>');
return -2;
}
}
try {
$showStatement = $connection->prepare('SHOW TABLES;');
$showStatement->execute();
$allTables = $showStatement->fetchAll(\PDO::FETCH_COLUMN);
if ($nbTable) {
foreach ($tablesToDelete as $tableToDelete) {
if (!array_search($tableToDelete, $allTables)) {
throw new \InvalidArgumentException(sprintf('Table %s doesn\'t exist in the database.', $tableToDelete));
}
}
} else {
$tablesToDelete = $allTables;
}
$connection->exec('SET FOREIGN_KEY_CHECKS = 0;');
array_walk($tablesToDelete, function (&$table, $key, $dbAdapter) {
$table = $dbAdapter->quoteIdentifierTable($table);
}, $adapter);
$tablesToDelete = join(', ', $tablesToDelete);
if ('' !== $tablesToDelete) {
$connection->exec('DROP TABLE ' . $tablesToDelete . ' ;');
$output->writeln(sprintf('Table' . $tablePlural . ' <info><comment>%s</comment> has been dropped.</info>', $tablesToDelete));
} else {
$output->writeln('<info>No tables have been dropped</info>');
}
$connection->exec('SET FOREIGN_KEY_CHECKS = 1;');
} catch (\Exception $e) {
$this->writeSection($output, array(
'[Propel] Exception caught',
'',
$e->getMessage()
), 'fg=white;bg=red');
}
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
abstract class WrappedCommand extends AbstractCommand
{
/**
* Creates the instance of the Propel sub-command to execute.
*
* @return \Symfony\Component\Console\Command\Command
*/
abstract protected function createSubCommandInstance();
/**
* Returns all the arguments and options needed by the Propel sub-command.
*
* @return array
*/
abstract protected function getSubCommandArguments(InputInterface $input);
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->addOption('platform', null, InputOption::VALUE_OPTIONAL, 'The platform')
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$params = $this->getSubCommandArguments($input);
$command = $this->createSubCommandInstance();
$this->setupBuildTimeFiles();
return $this->runCommand($command, $params, $input, $output);
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Controller;
use Propel\Runtime\Propel;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\Response;
/**
* PanelController is designed to display information in the Propel Panel.
*
* @author William DURAND <william.durand1@gmail.com>
*/
class PanelController extends ContainerAware
{
/**
* This method renders the global Propel configuration.
*/
public function configurationAction()
{
$templating = $this->container->get('templating');
return $templating->renderResponse(
'PropelBundle:Panel:configuration.html.twig',
array(
'propel_version' => Propel::VERSION,
'configuration' => $this->container->getParameter('propel.configuration'),
'logging' => $this->container->getParameter('propel.logging'),
)
);
}
/**
* Renders the profiler panel for the given token.
*
* @param string $token The profiler token
* @param string $connection The connection name
* @param integer $query
*
* @return Symfony\Component\HttpFoundation\Response A Response instance
*/
public function explainAction($token, $connection, $query)
{
$profiler = $this->container->get('profiler');
$profiler->disable();
$profile = $profiler->loadProfile($token);
$queries = $profile->getCollector('propel')->getQueries();
if (!isset($queries[$query])) {
return new Response('This query does not exist.');
}
// Open the connection
$con = Propel::getConnection($connection);
try {
$dataFetcher = $con->query('EXPLAIN ' . $queries[$query]['sql']);
$results = array();
while (($results[] = $dataFetcher->fetch(\PDO::FETCH_ASSOC)));
} catch (\Exception $e) {
return new Response('<div class="error">This query cannot be explained.</div>');
}
return $this->container->get('templating')->renderResponse(
'PropelBundle:Panel:explain.html.twig',
array(
'data' => $results,
'query' => $query,
)
);
}
}

View file

@ -0,0 +1,108 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\DataCollector;
use Propel\PropelBundle\Logger\PropelLogger;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* The PropelDataCollector collector class collects information.
*
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class PropelDataCollector extends DataCollector
{
protected $logger;
public function __construct(PropelLogger $logger)
{
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data = array(
'queries' => $this->buildQueries(),
'querycount' => $this->countQueries(),
);
}
/**
* Returns the collector name.
*
* @return string The collector name.
*/
public function getName()
{
return 'propel';
}
/**
* Returns queries.
*
* @return array Queries
*/
public function getQueries()
{
return $this->data['queries'];
}
/**
* Returns the query count.
*
* @return int The query count
*/
public function getQueryCount()
{
return $this->data['querycount'];
}
/**
* Returns the total time of queries.
*
* @return float The total time of queries
*/
public function getTime()
{
$time = 0;
foreach ($this->data['queries'] as $query) {
$time += (float) $query['time'];
}
return $time;
}
/**
* Creates an array of Build objects.
*
* @return array An array of Build objects
*/
private function buildQueries()
{
return $this->logger->getQueries();
}
/**
* Count queries.
*
* @return int The number of queries.
*/
private function countQueries()
{
return count($this->logger->getQueries());
}
}

View file

@ -0,0 +1,160 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DataFixtures;
use Propel\Runtime\Map\DatabaseMap;
use Propel\Runtime\Propel;
use Symfony\Component\Finder\Finder;
/**
* @author William Durand <william.durand1@gmail.com>
*/
abstract class AbstractDataHandler
{
/**
* @var string
*/
protected $rootDir;
/**
* @var \PDO
*/
protected $con;
/**
* @var DatabaseMap
*/
protected $dbMap;
/**
* @var array
*/
protected $datasources = array();
/**
* Default constructor
*
* @param string $rootDir The root directory.
* @param array $datasources
*/
public function __construct($rootDir, array $datasources)
{
$this->rootDir = $rootDir;
$this->datasources = $datasources;
}
/**
* @return string
*/
protected function getRootDir()
{
return $this->rootDir;
}
/**
* Load Map builders.
*
* @param string $connectionName A connection name.
*/
protected function loadMapBuilders($connectionName = null)
{
if (null !== $this->dbMap) {
return;
}
$this->dbMap = Propel::getDatabaseMap($connectionName);
if (0 === count($this->dbMap->getTables())) {
$finder = new Finder();
$files = $finder
->files()->name('*TableMap.php')
->in($this->getModelSearchPaths($connectionName))
->notName('TableMap.php')
->exclude('PropelBundle')
->exclude('Tests');
foreach ($files as $file) {
$class = $this->guessFullClassName($file->getRelativePath(), basename($file, '.php'));
if (null !== $class && $this->isInDatabase($class, $connectionName)) {
$this->dbMap->addTableFromMapClass($class);
}
}
}
}
/**
* Check if a table is in a database
*
* @param string $class
* @param string $connectionName
*
* @return boolean
*/
protected function isInDatabase($class, $connectionName)
{
return constant($class.'::DATABASE_NAME') === $connectionName;
}
/**
* Try to find a valid class with its namespace based on the filename.
* Based on the PSR-0 standard, the namespace should be the directory structure.
*
* @param string $path The relative path of the file.
* @param string $shortClassName The short class name aka the filename without extension.
*
* @return string|null
*/
private function guessFullClassName($path, $shortClassName)
{
$array = array();
$path = str_replace('/', '\\', $path);
$array[] = $path;
while ($pos = strpos($path, '\\')) {
$path = substr($path, $pos + 1, strlen($path));
$array[] = $path;
}
$array = array_reverse($array);
while ($ns = array_pop($array)) {
$class = $ns . '\\' . $shortClassName;
if (class_exists($class)) {
return $class;
}
}
return null;
}
/**
* Gets the search path for models out of the configuration.
*
* @param string $connectionName A connection name.
*
* @return string[]
*/
protected function getModelSearchPaths($connectionName)
{
$searchPath = array();
if (!empty($this->datasources[$connectionName]['connection']['model_paths'])) {
$modelPaths = $this->datasources[$connectionName]['connection']['model_paths'];
foreach ($modelPaths as $modelPath) {
$searchPath[] = $this->getRootDir() . '/../' . $modelPath;
}
} else {
$searchPath[] = $this->getRootDir() . '/../';
}
return $searchPath;
}
}

View file

@ -0,0 +1,242 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DataFixtures\Dumper;
use \PDO;
use Propel\PropelBundle\DataFixtures\AbstractDataHandler;
use Propel\Generator\Model\PropelTypes;
use Propel\Runtime\Propel;
/**
* Abstract class to manage a common logic to dump data.
*
* @author William Durand <william.durand1@gmail.com>
*/
abstract class AbstractDataDumper extends AbstractDataHandler implements DataDumperInterface
{
/**
* {@inheritdoc}
*/
public function dump($filename, $connectionName = null)
{
if (null === $filename || '' === $filename) {
throw new \Exception('Invalid filename provided.');
}
$this->loadMapBuilders($connectionName);
$this->con = Propel::getConnection($connectionName);
$array = $this->getDataAsArray($connectionName);
$data = $this->transformArrayToData($array);
if (false === file_put_contents($filename, $data)) {
throw new \Exception(sprintf('Cannot write file: %s', $filename));
}
}
/**
* Transforms an array of data to a specific format
* depending on the specialized dumper. It should return
* a string content ready to write in a file.
*
* @return string
*/
abstract protected function transformArrayToData($data);
/**
* Dumps data to fixture from a given connection and
* returns an array.
*
* @param string $connectionName The connection name
* @return array
*/
protected function getDataAsArray()
{
$tables = array();
foreach ($this->dbMap->getTables() as $table) {
$tables[] = $table->getClassname();
}
$tables = $this->fixOrderingOfForeignKeyData($tables);
$dumpData = array();
foreach ($tables as $tableName) {
$tableMap = $this->dbMap->getTable(constant(constant($tableName.'::TABLE_MAP').'::TABLE_NAME'));
$hasParent = false;
$haveParents = false;
$fixColumn = null;
$shortTableName = substr($tableName, strrpos($tableName, '\\') + 1, strlen($tableName));
foreach ($tableMap->getColumns() as $column) {
$col = strtolower($column->getName());
if ($column->isForeignKey()) {
$relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
if ($tableName === $relatedTable->getPhpName()) {
if ($hasParent) {
$haveParents = true;
} else {
$fixColumn = $column;
$hasParent = true;
}
}
}
}
if ($haveParents) {
// unable to dump tables having multi-recursive references
continue;
}
// get db info
$resultsSets = array();
if ($hasParent) {
$resultsSets[] = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $fixColumn);
} else {
$in = array();
foreach ($tableMap->getColumns() as $column) {
$in[] = strtolower($column->getName());
}
$stmt = $this
->con
->query(sprintf('SELECT %s FROM %s', implode(',', $in), constant(constant($tableName.'::TABLE_MAP').'::TABLE_NAME')));
$set = array();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$set[] = $row;
}
$resultsSets[] = $set;
$stmt->close();
unset($stmt);
}
foreach ($resultsSets as $rows) {
if (count($rows) > 0 && !isset($dumpData[$tableName])) {
$dumpData[$tableName] = array();
foreach ($rows as $row) {
$pk = $shortTableName;
$values = array();
$primaryKeys = array();
$foreignKeys = array();
foreach ($tableMap->getColumns() as $column) {
$col = strtolower($column->getName());
$isPrimaryKey = $column->isPrimaryKey();
if (null === $row[$col]) {
continue;
}
if ($isPrimaryKey) {
$value = $row[$col];
$pk .= '_'.$value;
$primaryKeys[$col] = $value;
}
if ($column->isForeignKey()) {
$relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
if ($isPrimaryKey) {
$foreignKeys[$col] = $row[$col];
$primaryKeys[$col] = $relatedTable->getPhpName().'_'.$row[$col];
} else {
$values[$col] = $relatedTable->getPhpName().'_'.$row[$col];
$values[$col] = strlen($row[$col]) ? $relatedTable->getPhpName().'_'.$row[$col] : '';
}
} elseif (!$isPrimaryKey || ($isPrimaryKey && !$tableMap->isUseIdGenerator())) {
if (!empty($row[$col]) && PropelTypes::PHP_ARRAY === $column->getType()) {
$serialized = substr($row[$col], 2, -2);
$row[$col] = $serialized ? explode(' | ', $serialized) : array();
}
// We did not want auto incremented primary keys
$values[$col] = $row[$col];
}
if (PropelTypes::OBJECT === $column->getType()) {
$values[$col] = unserialize($row[$col]);
}
}
if (count($primaryKeys) > 1 || (count($primaryKeys) > 0 && count($foreignKeys) > 0)) {
$values = array_merge($primaryKeys, $values);
}
$dumpData[$tableName][$pk] = $values;
}
}
}
}
return $dumpData;
}
/**
* Fixes the ordering of foreign key data, by outputting data
* a foreign key depends on before the table with the foreign key.
*
* @param array $classes The array with the class names
* @return array
*/
protected function fixOrderingOfForeignKeyData($classes)
{
// reordering classes to take foreign keys into account
for ($i = 0, $count = count($classes); $i < $count; $i++) {
$class = $classes[$i];
$tableMap = $this->dbMap->getTable(constant(constant($class.'::TABLE_MAP').'::TABLE_NAME'));
foreach ($tableMap->getColumns() as $column) {
if ($column->isForeignKey()) {
$relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
$relatedTablePos = array_search($relatedTable->getClassname(), $classes);
// check if relatedTable is after the current table
if ($relatedTablePos > $i) {
// move related table 1 position before current table
$classes = array_merge(
array_slice($classes, 0, $i),
array($classes[$relatedTablePos]),
array_slice($classes, $i, $relatedTablePos - $i),
array_slice($classes, $relatedTablePos + 1)
);
// we have moved a table, so let's see if we are done
return $this->fixOrderingOfForeignKeyData($classes);
}
}
}
}
return $classes;
}
protected function fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in = null)
{
$sql = sprintf('SELECT * FROM %s WHERE %s %s',
constant(constant($tableName.'::TABLE_MAP').'::TABLE_NAME'),
strtolower($column->getName()),
null === $in ? 'IS NULL' : 'IN ('.$in.')');
$stmt = $this->con->prepare($sql);
$stmt->execute();
$in = array();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$in[] = "'".$row[strtolower($column->getRelatedColumnName())]."'";
$resultsSets[] = $row;
}
if ($in = implode(', ', $in)) {
$resultsSets = $this->fixOrderingOfForeignKeyDataInSameTable($resultsSets, $tableName, $column, $in);
}
return $resultsSets;
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DataFixtures\Dumper;
/**
* Interface that exposes how Propel data dumpers should work.
*
* @author William Durand <william.durand1@gmail.com>
*/
interface DataDumperInterface
{
/**
* Dumps data to fixtures from a given connection.
*
* @param string $filename The file name to write data.
* @param string $connectionName The Propel connection name.
*/
public function dump($filename, $connectionName = null);
}

View file

@ -0,0 +1,35 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DataFixtures\Dumper;
use Symfony\Component\Yaml\Yaml;
/**
* YAML fixtures dumper.
*
* @author William Durand <william.durand1@gmail.com>
*/
class YamlDataDumper extends AbstractDataDumper
{
/**
* {@inheritdoc}
*/
protected function transformArrayToData($data)
{
return Yaml::dump(
$data,
$inline = 3,
$indent = 4,
$exceptionOnInvalidType = false,
$objectSupport = true
);
}
}

View file

@ -0,0 +1,309 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DataFixtures\Loader;
use Propel\PropelBundle\DataFixtures\AbstractDataHandler;
use Propel\PropelBundle\Util\PropelInflector;
use Propel\Generator\Model\PropelTypes;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Propel\Runtime\Map\Exception\TableNotFoundException;
use Propel\Runtime\Map\TableMap;
use Propel\Runtime\Propel;
/**
* Abstract class to manage a common logic to load datas.
*
* @author William Durand <william.durand1@gmail.com>
*/
abstract class AbstractDataLoader extends AbstractDataHandler implements DataLoaderInterface
{
/**
* @var array
*/
protected $deletedClasses = array();
/**
* @var array
*/
protected $object_references = array();
/**
* Transforms a file containing data in an array.
*
* @param string $file A filename.
* @return array
*/
abstract protected function transformDataToArray($file);
/**
* {@inheritdoc}
*/
public function load($files = array(), $connectionName)
{
$nbFiles = 0;
$this->deletedClasses = array();
$this->loadMapBuilders($connectionName);
$this->con = Propel::getConnection($connectionName);
try {
$this->con->beginTransaction();
$datas = array();
foreach ($files as $file) {
$content = $this->transformDataToArray($file);
if (count($content) > 0) {
$datas = array_merge_recursive($datas, $content);
$nbFiles++;
}
}
$this->deleteCurrentData($datas);
$this->loadDataFromArray($datas);
$this->con->commit();
} catch (\Exception $e) {
$this->con->rollBack();
throw $e;
}
return $nbFiles;
}
/**
* Deletes current data.
*
* @param array $data The data to delete
*/
protected function deleteCurrentData($data = null)
{
if ($data !== null) {
$classes = array_keys($data);
foreach (array_reverse($classes) as $class) {
$class = trim($class);
if (in_array($class, $this->deletedClasses)) {
continue;
}
$this->deleteClassData($class);
}
}
}
/**
* Delete data for a given class, and for its ancestors (if any).
*
* @param string $class Class name to delete
*/
protected function deleteClassData($class)
{
$tableMap = $this->dbMap->getTable(constant(constant($class.'::TABLE_MAP').'::TABLE_NAME'));
$tableMap->doDeleteAll($this->con);
$this->deletedClasses[] = $class;
// Remove ancestors data
if (false !== ($parentClass = get_parent_class(get_parent_class($class)))) {
$reflectionClass = new \ReflectionClass($parentClass);
if (!$reflectionClass->isAbstract()) {
$this->deleteClassData($parentClass);
}
}
}
/**
* Loads the data using the generated data model.
*
* @param array|null $data The data to be loaded
*/
protected function loadDataFromArray($data = null)
{
if ($data === null) {
return;
}
foreach ($data as $class => $datas) {
// iterate through datas for this class
// might have been empty just for force a table to be emptied on import
if (!is_array($datas)) {
continue;
}
$class = trim($class);
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
$tableMap = $this->dbMap->getTable(constant(constant($class.'::TABLE_MAP').'::TABLE_NAME'));
$column_names = $tableMap->getFieldnames(TableMap::TYPE_PHPNAME);
foreach ($datas as $key => $data) {
// create a new entry in the database
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Unknown class "%s".', $class));
}
$obj = new $class();
if (!$obj instanceof ActiveRecordInterface) {
throw new \RuntimeException(
sprintf('The class "%s" is not a Propel class. There is probably another class named "%s" somewhere.', $class, $class)
);
}
if (!is_array($data)) {
throw new \InvalidArgumentException(sprintf('You must give a name for each fixture data entry (class %s).', $class));
}
foreach ($data as $name => $value) {
if (is_array($value) && 's' === substr($name, -1)) {
try {
// many to many relationship
$this->loadManyToMany($obj, substr($name, 0, -1), $value);
continue;
} catch (TableNotFoundException $e) {
// Check whether this is actually an array stored in the object.
if ('Cannot fetch TableMap for undefined table: ' . substr($name, 0, -1) === $e->getMessage()) {
if (PropelTypes::PHP_ARRAY !== $tableMap->getColumn($name)->getType()
&& PropelTypes::OBJECT !== $tableMap->getColumn($name)->getType()) {
throw $e;
}
}
}
}
$isARealColumn = true;
if ($tableMap->hasColumn($name)) {
$column = $tableMap->getColumn($name);
} elseif ($tableMap->hasColumnByPhpName($name)) {
$column = $tableMap->getColumnByPhpName($name);
} else {
$isARealColumn = false;
}
// foreign key?
if ($isARealColumn) {
/*
* A column, which is a PrimaryKey (self referencing, e.g. versionable behavior),
* but which is not a ForeignKey (e.g. delegatable behavior on 1:1 relation).
*/
if ($column->isPrimaryKey() && null !== $value && !$column->isForeignKey()) {
if (isset($this->object_references[$this->cleanObjectRef($class.'_'.$value)])) {
$obj = $this->object_references[$this->cleanObjectRef($class.'_'.$value)];
continue;
}
}
if ($column->isForeignKey() && null !== $value) {
$relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
if (!isset($this->object_references[$this->cleanObjectRef($relatedTable->getClassname().'_'.$value)])) {
var_dump($this->object_references, $this->cleanObjectRef($relatedTable->getClassname().'_'.$value));
throw new \InvalidArgumentException(
sprintf('The object "%s" from class "%s" is not defined in your data file.', $value, $relatedTable->getClassname())
);
}
$value = $this
->object_references[$this->cleanObjectRef($relatedTable->getClassname().'_'.$value)]
->getByName($column->getRelatedName(), TableMap::TYPE_COLNAME);
}
}
if (false !== $pos = array_search($name, $column_names)) {
$obj->setByPosition($pos, $value);
} elseif (is_callable(array($obj, $method = 'set'.ucfirst(PropelInflector::camelize($name))))) {
$obj->$method($value);
} else {
throw new \InvalidArgumentException(sprintf('Column "%s" does not exist for class "%s".', $name, $class));
}
}
$obj->save($this->con);
$this->saveParentReference($class, $key, $obj);
}
}
}
/**
* Save a reference to the specified object (and its ancestors) before loading them.
*
* @param string $class Class name of passed object
* @param string $key Key identifying specified object
* @param ActiveRecordInterface $obj A Propel object
*/
protected function saveParentReference($class, $key, &$obj)
{
if (!method_exists($obj, 'getPrimaryKey')) {
return;
}
$this->object_references[$this->cleanObjectRef($class.'_'.$key)] = $obj;
// Get parent (schema ancestor) of parent (Propel base class) in case of inheritance
if (false !== ($parentClass = get_parent_class(get_parent_class($class)))) {
$reflectionClass = new \ReflectionClass($parentClass);
if (!$reflectionClass->isAbstract()) {
$parentObj = new $parentClass();
$parentObj->fromArray($obj->toArray());
$this->saveParentReference($parentClass, $key, $parentObj);
}
}
}
/**
* Loads many to many objects.
*
* @param ActiveRecordInterface $obj A Propel object
* @param string $middleTableName The middle table name
* @param array $values An array of values
*/
protected function loadManyToMany($obj, $middleTableName, $values)
{
$middleTable = $this->dbMap->getTable($middleTableName);
$middleClass = $middleTable->getClassname();
$tableName = constant(constant(get_class($obj).'::TABLE_MAP').'::TABLE_NAME');
foreach ($middleTable->getColumns() as $column) {
if ($column->isForeignKey()) {
if ($tableName !== $column->getRelatedTableName()) {
$relatedClass = $this->dbMap->getTable($column->getRelatedTableName())->getClassname();
$relatedSetter = 'set' . $column->getRelation()->getName();
} else {
$setter = 'set' . $column->getRelation()->getName();
}
}
}
if (!isset($relatedClass)) {
throw new \InvalidArgumentException(sprintf('Unable to find the many-to-many relationship for object "%s".', get_class($obj)));
}
foreach ($values as $value) {
if (!isset($this->object_references[$this->cleanObjectRef($relatedClass.'_'.$value)])) {
throw new \InvalidArgumentException(
sprintf('The object "%s" from class "%s" is not defined in your data file.', $value, $relatedClass)
);
}
$middle = new $middleClass();
$middle->$setter($obj);
$middle->$relatedSetter($this->object_references[$this->cleanObjectRef($relatedClass.'_'.$value)]);
$middle->save($this->con);
}
}
protected function cleanObjectRef($ref)
{
return $ref[0] === '\\' ? substr($ref, 1) : $ref;
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DataFixtures\Loader;
/**
* Interface that exposes how Propel data loaders should work.
*
* @author William Durand <william.durand1@gmail.com>
*/
interface DataLoaderInterface
{
/**
* Loads data from a set of files.
*
* @param array $files A set of files containing datas to load.
* @param string $connectionName The Propel connection name
*/
public function load($files = array(), $connectionName);
}

View file

@ -0,0 +1,61 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DataFixtures\Loader;
/**
* XML fixtures loader.
*
* @author William Durand <william.durand1@gmail.com>
*/
class XmlDataLoader extends AbstractDataLoader
{
/**
* {@inheritdoc}
*/
protected function transformDataToArray($file)
{
$xml = simplexml_load_file($file);
return $this->simpleXmlToArray($xml);
}
/**
* @param SimpleXMLElement $xml
* @return array
*/
protected function simpleXmlToArray($xml)
{
$array = array();
if ($xml instanceof \SimpleXMLElement) {
foreach ($xml as $key => $value) {
// First make a valid key which is the Ns (Namespace) attribute
// + the element name (the class name)
foreach ($value->attributes() as $subkey => $subvalue) {
if ('Namespace' === (string) $subkey) {
$key = $subvalue . '\\' . $key;
break;
}
}
$array[$key] = array();
foreach ($value as $elementKey => $elementValue) {
$array[$key][$elementKey] = array();
foreach ($elementValue->attributes() as $subkey => $subvalue) {
$array[$key][$elementKey][$subkey] = (string) $subvalue;
}
}
}
}
return $array;
}
}

View file

@ -0,0 +1,78 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DataFixtures\Loader;
use Faker\Generator;
use Symfony\Component\Yaml\ParseException;
use Symfony\Component\Yaml\Yaml;
/**
* YAML fixtures loader.
*
* @author William Durand <william.durand1@gmail.com>
*/
class YamlDataLoader extends AbstractDataLoader
{
/**
* @var \Faker\Generator
*/
private $faker;
/**
* {@inheritdoc}
*/
public function __construct($rootDir, array $datasources, Generator $faker = null)
{
parent::__construct($rootDir, $datasources);
$this->faker = $faker;
}
/**
* {@inheritdoc}
*/
protected function transformDataToArray($file)
{
if (strpos($file, "\n") === false && is_file($file)) {
if (false === is_readable($file)) {
throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $file));
}
if (null !== $this->faker) {
$generator = $this->faker;
$faker = function ($type) use ($generator) {
$args = func_get_args();
array_shift($args);
echo Yaml::dump(call_user_func_array(array($generator, $type), $args)) . "\n";
};
} else {
$faker = function ($text) {
echo $text . "\n";
};
}
ob_start();
$retval = include $file;
$content = ob_get_clean();
// if an array is returned by the config file assume it's in plain php form else in YAML
$file = is_array($retval) ? $retval : $content;
// if an array is returned by the config file assume it's in plain php form else in YAML
if (is_array($file)) {
return $file;
}
}
return Yaml::parse($file);
}
}

View file

@ -0,0 +1,111 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DependencyInjection;
use Propel\Common\Config\PropelConfiguration;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
/**
* This class contains the configuration information for the bundle
*/
class Configuration extends PropelConfiguration
{
private $debug;
public function __construct($debug = true)
{
$this->debug = $debug;
}
protected function addDatabaseSection(ArrayNodeDefinition $node)
{
$node
->children()
->arrayNode('database')
->isRequired()
->addDefaultsIfNotSet()
->children()
->arrayNode('connections')
->isRequired()
->requiresAtLeastOneElement()
->normalizeKeys(false)
->prototype('array')
->fixXmlConfig('slave')
->children()
->scalarNode('classname')->defaultValue($this->debug ? '\Propel\Runtime\Connection\DebugPDO' : '\Propel\Runtime\Connection\ConnectionWrapper')->end()
->enumNode('adapter')
->isRequired()
->cannotBeEmpty()
->values(array('mysql', 'pgsql', 'sqlite', 'mssql', 'sqlsrv', 'oracle'))
->end()
->scalarNode('dsn')->isRequired()->cannotBeEmpty()->end()
->scalarNode('user')->isRequired()->end()
->scalarNode('password')->isRequired()->treatNullLike('')->end()
->arrayNode('options')
->children()
->booleanNode('ATTR_PERSISTENT')->defaultFalse()->end()
->end()
->end()
->arrayNode('attributes')
->children()
->booleanNode('ATTR_EMULATE_PREPARES')->defaultFalse()->end()
->end()
->end()
->arrayNode('settings')
->fixXmlConfig('query', 'queries')
->children()
->scalarNode('charset')->defaultValue('utf8')->end()
->arrayNode('queries')
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('slaves')
->prototype('array')
->children()
->scalarNode('dsn')->end()
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('adapters')
->addDefaultsIfNotSet()
->children()
->arrayNode('mysql')
->addDefaultsIfNotSet()
->children()
->scalarNode('tableType')->defaultValue('InnoDB')->treatNullLike('InnoDB')->end()
->scalarNode('tableEngineKeyword')->defaultValue('ENGINE')->end()
->end()
->end()
->arrayNode('sqlite')
->addDefaultsIfNotSet()
->children()
->scalarNode('foreignKey')->end()
->scalarNode('tableAlteringWorkaround')->end()
->end()
->end()
->arrayNode('oracle')
->addDefaultsIfNotSet()
->children()
->scalarNode('autoincrementSequencePattern')->defaultValue('${table}_SEQ')->end()
->end()
->end()
->end()
->end() //adapters
->end()
->end() //database
->end()
;
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Definition\Processor;
/**
* PropelExtension loads the PropelBundle configuration.
*
* @author William DURAND <william.durand1@gmail.com>
*/
class PropelExtension extends Extension
{
/**
* Loads the Propel configuration.
*
* @param array $configs An array of configuration settings
* @param ContainerBuilder $container A ContainerBuilder instance
*/
public function load(array $configs, ContainerBuilder $container)
{
$processor = new Processor();
$configuration = $this->getConfiguration($configs, $container);
$config = $processor->processConfiguration($configuration, $configs);
$container->setParameter('propel.logging', $container->getParameter('kernel.debug'));
$container->setParameter('propel.configuration', $config);
// Load services
if (!$container->hasDefinition('propel')) {
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('propel.xml');
$loader->load('converters.xml');
$loader->load('security.xml');
}
}
public function getConfiguration(array $config, ContainerBuilder $container)
{
return new Configuration($container->getParameter('kernel.debug'));
}
/**
* Returns the base path for the XSD files.
*
* @return string The XSD base path
*/
public function getXsdValidationBasePath()
{
return __DIR__.'/../Resources/config/schema';
}
/**
* 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';
}
}

72
Form/BaseAbstractType.php Normal file
View file

@ -0,0 +1,72 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* @author William DURAND <william.durand1@gmail.com>
*/
abstract class BaseAbstractType extends AbstractType
{
protected $options = array(
'name' => '',
);
public function __construct($mergeOptions = null)
{
if ($mergeOptions) {
$this->mergeOptions($mergeOptions);
}
}
public function setOption($name, $value)
{
$this->options[$name] = $value;
}
public function getOption($name)
{
return $this->options[$name];
}
public function setOptions($options)
{
$this->options = $options;
}
public function getOptions()
{
return $this->options;
}
public function mergeOptions($options)
{
$this->options = array_merge($this->options, $options);
}
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults($this->options);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->getOption('name');
}
}

View file

@ -0,0 +1,430 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form\ChoiceList;
use Propel\Runtime\Map\ColumnMap;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Symfony\Component\Form\Exception\StringCastException;
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* Widely inspired by the EntityChoiceList.
*
* @author William Durand <william.durand1@gmail.com>
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class ModelChoiceList extends ObjectChoiceList
{
/**
* The fields of which the identifier of the underlying class consists
*
* This property should only be accessed through identifier.
*
* @var array
*/
protected $identifier = array();
/**
* The query to retrieve the choices of this list.
*
* @var ModelCriteria
*/
protected $query;
/**
* The query to retrieve the preferred choices for this list.
*
* @var ModelCriteria
*/
protected $preferredQuery;
/**
* Whether the model objects have already been loaded.
*
* @var Boolean
*/
protected $loaded = false;
/**
* Whether to use the identifier for index generation
*
* @var Boolean
*/
private $identifierAsIndex = false;
/**
* Constructor.
*
* @see Symfony\Bridge\Propel1\Form\Type\ModelType How to use the preferred choices.
*
* @param string $class The FQCN of the model class to be loaded.
* @param string $labelPath A property path pointing to the property used for the choice labels.
* @param array $choices An optional array to use, rather than fetching the models.
* @param ModelCriteria $queryObject The query to use retrieving model data from database.
* @param string $groupPath A property path pointing to the property used to group the choices.
* @param array|ModelCriteria $preferred The preferred items of this choice.
* Either an array if $choices is given,
* or a ModelCriteria to be merged with the $queryObject.
* @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths.
*/
public function __construct($class, $labelPath = null, $choices = null, $queryObject = null, $groupPath = null, $preferred = array(), PropertyAccessorInterface $propertyAccessor = null)
{
$this->class = $class;
$queryClass = $this->class.'Query';
$query = new $queryClass();
$this->identifier = $query->getTableMap()->getPrimaryKeys();
$this->query = $queryObject ?: $query;
$this->loaded = is_array($choices) || $choices instanceof \Traversable;
if ($preferred instanceof ModelCriteria) {
$this->preferredQuery = $preferred->mergeWith($this->query);
}
if (!$this->loaded) {
// Make sure the constraints of the parent constructor are
// fulfilled
$choices = array();
$preferred = array();
}
if (1 === count($this->identifier) && $this->isInteger(current($this->identifier))) {
$this->identifierAsIndex = true;
}
parent::__construct($choices, $labelPath, $preferred, $groupPath, null, $propertyAccessor);
}
/**
* Returns the class name
*
* @return string
*/
public function getClass()
{
return $this->class;
}
/**
* Returns the list of model objects
*
* @return array
*
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
public function getChoices()
{
if (!$this->loaded) {
$this->load();
}
return parent::getChoices();
}
/**
* Returns the values for the model objects
*
* @return array
*
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
public function getValues()
{
if (!$this->loaded) {
$this->load();
}
return parent::getValues();
}
/**
* Returns the choice views of the preferred choices as nested array with
* the choice groups as top-level keys.
*
* @return array
*
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
public function getPreferredViews()
{
if (!$this->loaded) {
$this->load();
}
return parent::getPreferredViews();
}
/**
* Returns the choice views of the choices that are not preferred as nested
* array with the choice groups as top-level keys.
*
* @return array
*
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
public function getRemainingViews()
{
if (!$this->loaded) {
$this->load();
}
return parent::getRemainingViews();
}
/**
* Returns the model objects corresponding to the given values.
*
* @param array $values
*
* @return array
*
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
public function getChoicesForValues(array $values)
{
if (!$this->loaded) {
if (1 === count($this->identifier)) {
$filterBy = 'filterBy'.current($this->identifier)->getPhpName();
return $this->query->create()
->$filterBy($values)
->find()
->getData();
}
$this->load();
}
return parent::getChoicesForValues($values);
}
/**
* Returns the values corresponding to the given model objects.
*
* @param array $models
*
* @return array
*
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
public function getValuesForChoices(array $models)
{
if (!$this->loaded) {
// Optimize performance for single-field identifiers. We already
// know that the IDs are used as values
// Attention: This optimization does not check choices for existence
if (1 === count($this->identifier)) {
$values = array();
foreach ($models as $model) {
if ($model instanceof $this->class) {
// Make sure to convert to the right format
$values[] = $this->fixValue(current($this->getIdentifierValues($model)));
}
}
return $values;
}
$this->load();
}
return parent::getValuesForChoices($models);
}
/**
* Returns the indices corresponding to the given models.
*
* @param array $models
*
* @return array
*
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
public function getIndicesForChoices(array $models)
{
$indices = array();
if (!$this->loaded) {
// Optimize performance for single-field identifiers. We already
// know that the IDs are used as indices
// Attention: This optimization does not check choices for existence
if ($this->identifierAsIndex) {
foreach ($models as $model) {
if ($model instanceof $this->class) {
// Make sure to convert to the right format
$indices[] = $this->fixIndex(current($this->getIdentifierValues($model)));
}
}
return $indices;
}
$this->load();
}
/*
* Overwriting default implementation.
*
* The two objects may represent the same entry in the database,
* but if they originated from different queries, there are not the same object within the code.
*
* This happens when using m:n relations with either sides model as data_class of the form.
* The choicelist will retrieve the list of available related models with a different query, resulting in different objects.
*/
$choices = $this->fixChoices($models);
foreach ($this->getChoices() as $i => $choice) {
foreach ($choices as $j => $givenChoice) {
if (null !== $givenChoice && $this->getIdentifierValues($choice) === $this->getIdentifierValues($givenChoice)) {
$indices[] = $i;
unset($choices[$j]);
if (0 === count($choices)) {
break 2;
}
}
}
}
return $indices;
}
/**
* Returns the models corresponding to the given values.
*
* @param array $values
*
* @return array
*
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
public function getIndicesForValues(array $values)
{
if (!$this->loaded) {
// Optimize performance for single-field identifiers. We already
// know that the IDs are used as indices and values
// Attention: This optimization does not check values for existence
if ($this->identifierAsIndex) {
return $this->fixIndices($values);
}
$this->load();
}
return parent::getIndicesForValues($values);
}
/**
* Creates a new unique index for this model.
*
* If the model has a single-field identifier, this identifier is used.
*
* Otherwise a new integer is generated.
*
* @param mixed $model The choice to create an index for
*
* @return integer|string A unique index containing only ASCII letters,
* digits and underscores.
*/
protected function createIndex($model)
{
if ($this->identifierAsIndex) {
return current($this->getIdentifierValues($model));
}
return parent::createIndex($model);
}
/**
* Creates a new unique value for this model.
*
* If the model has a single-field identifier, this identifier is used.
*
* Otherwise a new integer is generated.
*
* @param mixed $model The choice to create a value for
*
* @return integer|string A unique value without character limitations.
*/
protected function createValue($model)
{
if (1 === count($this->identifier)) {
return (string) current($this->getIdentifierValues($model));
}
return parent::createValue($model);
}
/**
* Loads the list with model objects.
*/
private function load()
{
$models = $this->query->find()->getData();
$preferred = array();
if ($this->preferredQuery instanceof ModelCriteria) {
$preferred = $this->preferredQuery->find()->getData();
}
try {
// The second parameter $labels is ignored by ObjectChoiceList
parent::initialize($models, array(), $preferred);
} catch (StringCastException $e) {
throw new StringCastException(str_replace('argument $labelPath', 'option "property"', $e->getMessage()), null, $e);
}
$this->loaded = true;
}
/**
* Returns the values of the identifier fields of an model
*
* Propel must know about this model, that is, the model must already
* be persisted or added to the idmodel map before. Otherwise an
* exception is thrown.
*
* @param object $model The model for which to get the identifier
*
* @return array
*/
private function getIdentifierValues($model)
{
if (null === $model) {
return array();
}
if ($model instanceof ActiveRecordInterface) {
return array($model->getPrimaryKey());
}
return $model->getPrimaryKeys();
}
/**
* Whether this column in an integer
*
* @param ColumnMap $column
*
* @return Boolean
*/
private function isInteger(ColumnMap $column)
{
return $column->getPdoType() === \PDO::PARAM_INT;
}
}

View file

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form\DataTransformer;
use Propel\Runtime\Collection\ObjectCollection;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
/**
* CollectionToArrayTransformer class.
*
* @author William Durand <william.durand1@gmail.com>
* @author Pierre-Yves Lebecq <py.lebecq@gmail.com>
*/
class CollectionToArrayTransformer implements DataTransformerInterface
{
public function transform($collection)
{
if (null === $collection) {
return array();
}
if (!$collection instanceof ObjectCollection) {
throw new TransformationFailedException('Expected a \ObjectCollection.');
}
return $collection->getData();
}
public function reverseTransform($array)
{
$collection = new ObjectCollection();
if ('' === $array || null === $array) {
return $collection;
}
if (!is_array($array)) {
throw new TransformationFailedException('Expected an array.');
}
$collection->setData($array);
return $collection;
}
}

View file

@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form\EventListener;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
/**
* listener class for propel_translatable_collection
*
* @author Patrick Kaufmann
*/
class TranslationCollectionFormListener implements EventSubscriberInterface
{
private $i18nClass;
private $languages;
public function __construct($languages, $i18nClass)
{
$this->i18nClass = $i18nClass;
$this->languages = $languages;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => array('preSetData', 1),
);
}
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
if (null === $data) {
return;
}
if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) {
throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)');
}
//get the class name of the i18nClass
$temp = explode('\\', $this->i18nClass);
$dataClass = end($temp);
$rootData = $form->getRoot()->getData();
$foundData = false;
$addFunction = 'add'.$dataClass;
//add a database row for every needed language
foreach ($this->languages as $lang) {
$found = false;
foreach ($data as $i18n) {
if (!method_exists($i18n, 'getLocale')) {
throw new UnexpectedTypeException($i18n, 'Propel i18n object');
}
if ($i18n->getLocale() == $lang) {
$found = true;
break;
}
}
if (!$found) {
$currentForm = $form;
while (!$foundData) {
if (method_exists($rootData, $addFunction)) {
$foundData = true;
break;
} elseif ($currentForm->hasParent()) {
$currentForm = $currentForm->getParent();
$rootData = $currentForm->getData();
} else {
break;
}
}
if (!$foundData) {
throw new UnexpectedTypeException($rootData, 'Propel i18n object');
}
$newTranslation = new $this->i18nClass();
$newTranslation->setLocale($lang);
$rootData->$addFunction($newTranslation);
}
}
}
}

View file

@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
/**
* Event Listener class for propel_translation
*
* @author Patrick Kaufmann
*/
class TranslationFormListener implements EventSubscriberInterface
{
private $columns;
private $dataClass;
public function __construct($columns, $dataClass)
{
$this->columns = $columns;
$this->dataClass = $dataClass;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => array('preSetData', 1),
);
}
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
if (!$data instanceof $this->dataClass) {
return;
}
//loop over all columns and add the input
foreach ($this->columns as $column => $options) {
if (is_string($options)) {
$column = $options;
$options = array();
}
if (null === $options) {
$options = array();
}
$type = 'text';
if (array_key_exists('type', $options)) {
$type = $options['type'];
}
$label = $column;
if (array_key_exists('label', $options)) {
$label = $options['label'];
}
$customOptions = array();
if (array_key_exists('options', $options)) {
$customOptions = $options['options'];
}
$options = array(
'label' => $label.' '.strtoupper($data->getLocale())
);
$options = array_merge($options, $customOptions);
$form->add($column, $type, $options);
}
}
}

37
Form/PropelExtension.php Normal file
View file

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form;
use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\PropertyAccess\PropertyAccess;
/**
* Represents the Propel form extension, which loads the Propel functionality.
*
* @author Joseph Rouff <rouffj@gmail.com>
*/
class PropelExtension extends AbstractExtension
{
protected function loadTypes()
{
return array(
new Type\ModelType(PropertyAccess::getPropertyAccessor()),
new Type\TranslationCollectionType(),
new Type\TranslationType()
);
}
protected function loadTypeGuesser()
{
return new TypeGuesser();
}
}

109
Form/Type/ModelType.php Normal file
View file

@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form\Type;
use Propel\PropelBundle\Form\ChoiceList\ModelChoiceList;
use Propel\PropelBundle\Form\DataTransformer\CollectionToArrayTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* ModelType class.
*
* @author William Durand <william.durand1@gmail.com>
* @author Toni Uebernickel <tuebernickel@gmail.com>
*
* Example using the preferred_choices option.
*
* <code>
* public function buildForm(FormBuilderInterface $builder, array $options)
* {
* $builder
* ->add('product', 'model', array(
* 'class' => 'Model\Product',
* 'query' => ProductQuery::create()
* ->filterIsActive(true)
* ->useI18nQuery($options['locale'])
* ->orderByName()
* ->endUse()
* ,
* 'preferred_choices' => ProductQuery::create()
* ->filterByIsTopProduct(true)
* ,
* ))
* ;
* }
* </code>
*/
class ModelType extends AbstractType
{
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
{
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::getPropertyAccessor();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['multiple']) {
$builder->addViewTransformer(new CollectionToArrayTransformer(), true);
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$propertyAccessor = $this->propertyAccessor;
$choiceList = function (Options $options) use ($propertyAccessor) {
return new ModelChoiceList(
$options['class'],
$options['property'],
$options['choices'],
$options['query'],
$options['group_by'],
$options['preferred_choices'],
$propertyAccessor
);
};
$resolver->setDefaults(array(
'template' => 'choice',
'multiple' => false,
'expanded' => false,
'class' => null,
'property' => null,
'query' => null,
'choices' => null,
'choice_list' => $choiceList,
'group_by' => null,
'by_reference' => false,
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'model';
}
}

View file

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Propel\PropelBundle\Form\EventListener\TranslationCollectionFormListener;
/**
* form type for i18n-columns in propel
*
* @author Patrick Kaufmann
*/
class TranslationCollectionType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (!isset($options['options']['data_class']) || null === $options['options']['data_class']) {
throw new MissingOptionsException('data_class must be set');
}
if (!isset($options['options']['columns']) || null === $options['options']['columns']) {
throw new MissingOptionsException('columns must be set');
}
$listener = new TranslationCollectionFormListener($options['languages'], $options['options']['data_class']);
$builder->addEventSubscriber($listener);
}
public function getParent()
{
return 'collection';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'propel_translation_collection';
}
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'languages'
));
$resolver->setDefaults(array(
'type' => 'propel_translation',
'allow_add' => false,
'allow_delete' => false,
'options' => array(
'data_class' => null,
'columns' => null
)
));
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Propel\PropelBundle\Form\EventListener\TranslationFormListener;
/**
* Translation type class
*
* @author Patrick Kaufmann
*/
class TranslationType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber(
new TranslationFormListener($options['columns'], $options['data_class'])
);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'propel_translation';
}
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'data_class',
'columns'
));
}
}

182
Form/TypeGuesser.php Normal file
View file

@ -0,0 +1,182 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form;
use Propel\Runtime\Map\RelationMap;
use Propel\Generator\Model\PropelTypes;
use Symfony\Component\Form\FormTypeGuesserInterface;
use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\TypeGuess;
use Symfony\Component\Form\Guess\ValueGuess;
/**
* Propel Type guesser.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TypeGuesser implements FormTypeGuesserInterface
{
private $cache = array();
/**
* {@inheritDoc}
*/
public function guessType($class, $property)
{
if (!$table = $this->getTable($class)) {
return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE);
}
foreach ($table->getRelations() as $relation) {
if ($relation->getType() === RelationMap::MANY_TO_ONE) {
if (strtolower($property) === strtolower($relation->getName())) {
return new TypeGuess('model', array(
'class' => $relation->getForeignTable()->getClassName(),
'multiple' => false,
), Guess::HIGH_CONFIDENCE);
}
} elseif ($relation->getType() === RelationMap::ONE_TO_MANY) {
if (strtolower($property) === strtolower($relation->getPluralName())) {
return new TypeGuess('model', array(
'class' => $relation->getForeignTable()->getClassName(),
'multiple' => true,
), Guess::HIGH_CONFIDENCE);
}
} elseif ($relation->getType() === RelationMap::MANY_TO_MANY) {
if (strtolower($property) == strtolower($relation->getPluralName())) {
return new TypeGuess('model', array(
'class' => $relation->getLocalTable()->getClassName(),
'multiple' => true,
), Guess::HIGH_CONFIDENCE);
}
}
}
if (!$column = $this->getColumn($class, $property)) {
return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE);
}
switch ($column->getType()) {
case PropelTypes::BOOLEAN:
case PropelTypes::BOOLEAN_EMU:
return new TypeGuess('checkbox', array(), Guess::HIGH_CONFIDENCE);
case PropelTypes::TIMESTAMP:
case PropelTypes::BU_TIMESTAMP:
return new TypeGuess('datetime', array(), Guess::HIGH_CONFIDENCE);
case PropelTypes::DATE:
case PropelTypes::BU_DATE:
return new TypeGuess('date', array(), Guess::HIGH_CONFIDENCE);
case PropelTypes::TIME:
return new TypeGuess('time', array(), Guess::HIGH_CONFIDENCE);
case PropelTypes::FLOAT:
case PropelTypes::REAL:
case PropelTypes::DOUBLE:
case PropelTypes::DECIMAL:
return new TypeGuess('number', array(), Guess::MEDIUM_CONFIDENCE);
case PropelTypes::TINYINT:
case PropelTypes::SMALLINT:
case PropelTypes::INTEGER:
case PropelTypes::BIGINT:
case PropelTypes::NUMERIC:
return new TypeGuess('integer', array(), Guess::MEDIUM_CONFIDENCE);
case PropelTypes::ENUM:
case PropelTypes::CHAR:
if ($column->getValueSet()) {
//check if this is mysql enum
$choices = $column->getValueSet();
$labels = array_map('ucfirst', $choices);
return new TypeGuess('choice', array('choices' => array_combine($choices, $labels)), Guess::MEDIUM_CONFIDENCE);
}
case PropelTypes::VARCHAR:
return new TypeGuess('text', array(), Guess::MEDIUM_CONFIDENCE);
case PropelTypes::LONGVARCHAR:
case PropelTypes::BLOB:
case PropelTypes::CLOB:
case PropelTypes::CLOB_EMU:
return new TypeGuess('textarea', array(), Guess::MEDIUM_CONFIDENCE);
default:
return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE);
}
}
/**
* {@inheritDoc}
*/
public function guessRequired($class, $property)
{
if ($column = $this->getColumn($class, $property)) {
return new ValueGuess($column->isNotNull(), Guess::HIGH_CONFIDENCE);
}
}
/**
* {@inheritDoc}
*/
public function guessMaxLength($class, $property)
{
if ($column = $this->getColumn($class, $property)) {
if ($column->isText()) {
return new ValueGuess($column->getSize(), Guess::HIGH_CONFIDENCE);
}
switch ($column->getType()) {
case PropelTypes::FLOAT:
case PropelTypes::REAL:
case PropelTypes::DOUBLE:
case PropelTypes::DECIMAL:
return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE);
}
}
}
/**
* {@inheritDoc}
*/
public function guessPattern($class, $property)
{
if ($column = $this->getColumn($class, $property)) {
switch ($column->getType()) {
case PropelTypes::FLOAT:
case PropelTypes::REAL:
case PropelTypes::DOUBLE:
case PropelTypes::DECIMAL:
return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE);
}
}
}
protected function getTable($class)
{
if (isset($this->cache[$class])) {
return $this->cache[$class];
}
if (class_exists($queryClass = $class.'Query')) {
$query = new $queryClass();
return $this->cache[$class] = $query->getTableMap();
}
}
protected function getColumn($class, $property)
{
if (isset($this->cache[$class.'::'.$property])) {
return $this->cache[$class.'::'.$property];
}
$table = $this->getTable($class);
if ($table && $table->hasColumn($property)) {
return $this->cache[$class.'::'.$property] = $table->getColumn($property);
}
}
}

103
Logger/PropelLogger.php Normal file
View file

@ -0,0 +1,103 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Logger;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class PropelLogger implements LoggerInterface
{
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @var array
*/
protected $queries = array();
/**
* @var Stopwatch
*/
protected $stopwatch;
use LoggerTrait;
/**
* Constructor.
*
* @param LoggerInterface $logger A LoggerInterface instance
* @param Stopwatch $stopwatch A Stopwatch instance
*/
public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch = null)
{
$this->logger = $logger;
$this->stopwatch = $stopwatch;
$this->isPrepared = false;
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log($level, $message, array $context = array())
{
if (null === $this->logger) {
return;
}
$add = true;
if (null !== $this->stopwatch) {
$trace = debug_backtrace();
$method = $trace[3]['function'];
$watch = 'Propel Query '.(count($this->queries)+1);
if ('prepare' === $method) {
$this->isPrepared = true;
$this->stopwatch->start($watch, 'propel');
$add = false;
} elseif ($this->isPrepared) {
$this->isPrepared = false;
$event = $this->stopwatch->stop($watch);
}
}
// $trace[2] has no 'object' key if an exception is thrown while executing a query
if ($add && isset($event) && isset($trace[2]['object'])) {
$connection = $trace[2]['object'];
$this->queries[] = array(
'sql' => $message,
'connection' => $connection->getName(),
'time' => $event->getDuration() / 1000,
'memory' => $event->getMemory(),
);
}
$this->logger->log($level, $message, $context);
}
public function getQueries()
{
return $this->queries;
}
}

43
Model/Acl/AclClass.php Normal file
View file

@ -0,0 +1,43 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\AclClass as BaseAclClass;
use Propel\Runtime\Connection\ConnectionInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
class AclClass extends BaseAclClass
{
/**
* Return an AclClass for the given ACL ObjectIdentity.
*
* If none can be found, a new one will be saved.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\AclClass
*/
public static function fromAclObjectIdentity(ObjectIdentityInterface $objectIdentity, ConnectionInterface $con = null)
{
$obj = AclClassQuery::create()
->filterByType($objectIdentity->getType())
->findOneOrCreate($con)
;
if ($obj->isNew()) {
$obj->save($con);
}
return $obj;
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\AclClassQuery as BaseAclClassQuery;
class AclClassQuery extends BaseAclClassQuery
{
}

80
Model/Acl/Entry.php Normal file
View file

@ -0,0 +1,80 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\Entry as BaseEntry;
use Propel\PropelBundle\Security\Acl\Domain\Entry as AclEntry;
use Propel\PropelBundle\Security\Acl\Domain\FieldEntry as AclFieldEntry;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
use Symfony\Component\Security\Acl\Model\FieldEntryInterface;
class Entry extends BaseEntry
{
/**
* Transform a given ACL entry into a Entry model.
*
* The entry will not be persisted!
*
* @param \Symfony\Component\Security\Acl\Model\EntryInterface $aclEntry
*
* @return \Propel\PropelBundle\Model\Acl\Entry
*/
public static function fromAclEntry(EntryInterface $aclEntry)
{
$entry = new self();
// Already persisted before?
if ($aclEntry->getId()) {
$entry->setId($aclEntry->getId());
}
$entry
->setMask($aclEntry->getMask())
->setGranting($aclEntry->isGranting())
->setGrantingStrategy($aclEntry->getStrategy())
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($aclEntry->getSecurityIdentity()))
;
if ($aclEntry instanceof FieldEntryInterface) {
$entry->setFieldName($aclEntry->getField());
}
if ($aclEntry instanceof AuditableEntryInterface) {
$entry
->setAuditFailure($aclEntry->isAuditFailure())
->setAuditSuccess($aclEntry->isAuditSuccess())
;
}
return $entry;
}
/**
* Transform a given model entry into an ACL related Entry (ACE).
*
* @param \Propel\PropelBundle\Model\Acl\Entry $modelEntry
* @param \Symfony\Component\Security\Acl\Model\AclInterface $acl
*
* @return \Symfony\Component\Security\Acl\Model\EntryInterface
*/
public static function toAclEntry(Entry $modelEntry, AclInterface $acl)
{
if (null === $modelEntry->getFieldName()) {
return new AclEntry($modelEntry, $acl);
}
return new AclFieldEntry($modelEntry, $acl);
}
}

70
Model/Acl/EntryQuery.php Normal file
View file

@ -0,0 +1,70 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\EntryQuery as BaseEntryQuery;
use Propel\PropelBundle\Model\Acl\Map\EntryTableMap;
use Propel\PropelBundle\Model\Acl\Map\ObjectIdentityTableMap;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Connection\ConnectionInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
class EntryQuery extends BaseEntryQuery
{
/**
* Return Entry objects filtered by an ACL related ObjectIdentity.
*
* @see find()
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity An ACL related ObjectIdentity.
* @param array $securityIdentities A list of SecurityIdentity to filter by.
* @param \ConnectionInterface $con
*
* @return \PropelObjectCollection
*/
public function findByAclIdentity(ObjectIdentityInterface $objectIdentity, array $securityIdentities = array(), ConnectionInterface $con = null)
{
$securityIds = array();
foreach ($securityIdentities as $eachIdentity) {
if (!$eachIdentity instanceof SecurityIdentityInterface) {
if (is_object($eachIdentity)) {
$errorMessage = sprintf('The list of security identities contains at least one invalid entry of class "%s". Please provide objects of classes implementing "Symfony\Component\Security\Acl\Model\SecurityIdentityInterface" only.', get_class($eachIdentity));
} else {
$errorMessage = sprintf('The list of security identities contains at least one invalid entry "%s". Please provide objects of classes implementing "Symfony\Component\Security\Acl\Model\SecurityIdentityInterface" only.', $eachIdentity);
}
throw new \InvalidArgumentException($errorMessage);
}
if ($securityIdentity = SecurityIdentity::fromAclIdentity($eachIdentity)) {
$securityIds[$securityIdentity->getId()] = $securityIdentity->getId();
}
}
$this
->useAclClassQuery(null, Criteria::INNER_JOIN)
->filterByType((string) $objectIdentity->getType())
->endUse()
->leftJoinObjectIdentity()
->add(ObjectIdentityTableMap::COL_OBJECT_IDENTIFIER, (string) $objectIdentity->getIdentifier(), Criteria::EQUAL)
->addOr(EntryTableMap::COL_OBJECT_IDENTITY_ID, null, Criteria::ISNULL)
;
if (!empty($securityIdentities)) {
$this->filterBySecurityIdentityId($securityIds);
}
return $this->find($con);
}
}

View file

@ -0,0 +1,141 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\ObjectIdentity as BaseObjectIdentity;
use Propel\PropelBundle\Model\Acl\Map\ObjectIdentityTableMap;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Connection\ConnectionInterface;
class ObjectIdentity extends BaseObjectIdentity
{
public function preInsert(ConnectionInterface $con = null)
{
// Compatibility with default implementation.
$ancestor = new ObjectIdentityAncestor();
$ancestor->setObjectIdentityRelatedByObjectIdentityId($this);
$ancestor->setObjectIdentityRelatedByAncestorId($this);
$this->addObjectIdentityAncestorRelatedByAncestorId($ancestor);
if ($this->getParentObjectIdentityId()) {
$this->updateAncestorsTree($con);
}
return true;
}
public function preUpdate(ConnectionInterface $con = null)
{
if ($this->isColumnModified(ObjectIdentityTableMap::COL_PARENT_OBJECT_IDENTITY_ID)) {
$this->updateAncestorsTree($con);
}
return true;
}
public function preDelete(ConnectionInterface $con = null)
{
// Only retrieve direct children, it's faster and grand children will be retrieved recursively.
$children = ObjectIdentityQuery::create()->findChildren($this, $con);
$objIds = $children->getPrimaryKeys(false);
$objIds[] = $this->getId();
$children->delete($con);
// Manually delete those for DBAdapter not capable of cascading the DELETE.
ObjectIdentityAncestorQuery::create()
->filterByObjectIdentityId($objIds, Criteria::IN)
->delete($con)
;
return true;
}
/**
* Update all ancestor entries to reflect changes on this instance.
*
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\ObjectIdentity $this
*/
protected function updateAncestorsTree(ConnectionInterface $con = null)
{
$con->beginTransaction();
$oldAncestors = ObjectIdentityQuery::create()->findAncestors($this, $con);
$children = ObjectIdentityQuery::create()->findGrandChildren($this, $con);
$children->append($this);
if (count($oldAncestors)) {
foreach ($children as $eachChild) {
/*
* Delete only those entries, that are ancestors based on the parent relation.
* Ancestors of grand children up to the current node will be kept.
*/
$query = ObjectIdentityAncestorQuery::create()
->filterByObjectIdentityId($eachChild->getId())
->filterByObjectIdentityRelatedByAncestorId($oldAncestors, Criteria::IN)
;
if ($eachChild->getId() !== $this->getId()) {
$query->filterByAncestorId(array($eachChild->getId(), $this->getId()), Criteria::NOT_IN);
} else {
$query->filterByAncestorId($this->getId(), Criteria::NOT_EQUAL);
}
$query->delete($con);
}
}
// This is the new parent object identity!
$parent = $this->getObjectIdentityRelatedByParentObjectIdentityId($con);
if (null !== $parent) {
$newAncestors = ObjectIdentityQuery::create()->findAncestors($parent, $con);
$newAncestors->append($parent);
foreach ($newAncestors as $eachAncestor) {
// This collection contains the current object identity!
foreach ($children as $eachChild) {
$ancestor = ObjectIdentityAncestorQuery::create()
->filterByObjectIdentityId($eachChild->getId())
->filterByAncestorId($eachAncestor->getId())
->findOneOrCreate($con)
;
// If the entry already exists, next please.
if (!$ancestor->isNew()) {
continue;
}
if ($eachChild->getId() === $this->getId()) {
// Do not save() here, as it would result in an infinite recursion loop!
$this->addObjectIdentityAncestorRelatedByObjectIdentityId($ancestor);
} else {
// Save the new ancestor to avoid integrity constraint violation.
$ancestor->save($con);
$eachChild
->addObjectIdentityAncestorRelatedByObjectIdentityId($ancestor)
->save($con)
;
}
}
}
}
$con->commit();
return $this;
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\ObjectIdentityAncestor as BaseObjectIdentityAncestor;
class ObjectIdentityAncestor extends BaseObjectIdentityAncestor
{
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\ObjectIdentityAncestorQuery as BaseObjectIdentityAncestorQuery;
class ObjectIdentityAncestorQuery extends BaseObjectIdentityAncestorQuery
{
}

View file

@ -0,0 +1,114 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\ObjectIdentityQuery as BaseObjectIdentityQuery;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Connection\ConnectionInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
class ObjectIdentityQuery extends BaseObjectIdentityQuery
{
/**
* Filter by an ObjectIdentity object belonging to the given ACL related ObjectIdentity.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\ObjectIdentityQuery $this
*/
public function filterByAclObjectIdentity(ObjectIdentityInterface $objectIdentity, ConnectionInterface $con = null)
{
/*
* Not using a JOIN here, because the filter may be applied on 'findOneOrCreate',
* which is currently (Propel 1.6.4-dev) not working.
*/
$aclClass = AclClass::fromAclObjectIdentity($objectIdentity, $con);
$this
->filterByClassId($aclClass->getId())
->filterByIdentifier($objectIdentity->getIdentifier())
;
return $this;
}
/**
* Return an ObjectIdentity object belonging to the given ACL related ObjectIdentity.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\ObjectIdentity
*/
public function findOneByAclObjectIdentity(ObjectIdentityInterface $objectIdentity, ConnectionInterface $con = null)
{
return $this
->filterByAclObjectIdentity($objectIdentity, $con)
->findOne($con)
;
}
/**
* Return all children of the given object identity.
*
* @param \Propel\PropelBundle\Model\Acl\ObjectIdentity $objectIdentity
* @param ConnectionInterface $con
*
* @return \PropelObjectCollection
*/
public function findChildren(ObjectIdentity $objectIdentity, ConnectionInterface $con = null)
{
return $this
->filterByObjectIdentityRelatedByParentObjectIdentityId($objectIdentity)
->find($con)
;
}
/**
* Return all children and grand-children of the given object identity.
*
* @param \Propel\PropelBundle\Model\Acl\ObjectIdentity $objectIdentity
* @param ConnectionInterface $con
*
* @return \PropelObjectCollection
*/
public function findGrandChildren(ObjectIdentity $objectIdentity, ConnectionInterface $con = null)
{
return $this
->useObjectIdentityAncestorRelatedByObjectIdentityIdQuery()
->filterByObjectIdentityRelatedByAncestorId($objectIdentity)
->filterByObjectIdentityRelatedByObjectIdentityId($objectIdentity, Criteria::NOT_EQUAL)
->endUse()
->find($con)
;
}
/**
* Return all ancestors of the given object identity.
*
* @param ObjectIdentity $objectIdentity
* @param ConnectionInterface $con
*
* @return \PropelObjectCollection
*/
public function findAncestors(ObjectIdentity $objectIdentity, ConnectionInterface $con = null)
{
return $this
->useObjectIdentityAncestorRelatedByAncestorIdQuery()
->filterByObjectIdentityRelatedByObjectIdentityId($objectIdentity)
->filterByObjectIdentityRelatedByAncestorId($objectIdentity, Criteria::NOT_EQUAL)
->endUse()
->find($con)
;
}
}

View file

@ -0,0 +1,87 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\SecurityIdentity as BaseSecurityIdentity;
use Propel\Runtime\Connection\ConnectionInterface;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
class SecurityIdentity extends BaseSecurityIdentity
{
/**
* Transform a given mode security identity into an ACL related SecurityIdentity.
*
* @param \Propel\PropelBundle\Model\Acl\SecurityIdentity $securityIdentity
*
* @return \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface
*/
public static function toAclIdentity(SecurityIdentity $securityIdentity)
{
$identifier = $securityIdentity->getIdentifier();
if ($securityIdentity->getUsername()) {
if (false === strpos($identifier, '-')) {
throw new \InvalidArgumentException('The given identifier does not resolve to a UserSecurityIdentity.');
}
list($class, $username) = explode('-', $identifier, 2);
return new UserSecurityIdentity($username, $class);
}
if (0 === strpos($identifier, 'ROLE_') or 0 === strpos($identifier, 'IS_AUTHENTICATED_')) {
return new RoleSecurityIdentity($identifier);
}
throw new \InvalidArgumentException('The security identity does not resolve to either UserSecurityIdentity or RoleSecurityIdentity.');
}
/**
* Transform a given ACL security identity into a SecurityIdentity model.
*
* If there is no model entry given, a new one will be created and saved to the database.
*
* @throws \InvalidArgumentException
*
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $aclIdentity
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\SecurityIdentity
*/
public static function fromAclIdentity(SecurityIdentityInterface $aclIdentity, ConnectionInterface $con = null)
{
if ($aclIdentity instanceof UserSecurityIdentity) {
$identifier = $aclIdentity->getClass().'-'.$aclIdentity->getUsername();
$username = true;
} elseif ($aclIdentity instanceof RoleSecurityIdentity) {
$identifier = $aclIdentity->getRole();
$username = false;
} else {
throw new \InvalidArgumentException('The ACL identity must either be an instance of UserSecurityIdentity or RoleSecurityIdentity.');
}
$obj = SecurityIdentityQuery::create()
->filterByIdentifier($identifier)
->filterByUsername($username)
->findOneOrCreate($con)
;
if ($obj->isNew()) {
$obj->save($con);
}
return $obj;
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\SecurityIdentityQuery as BaseSecurityIdentityQuery;
class SecurityIdentityQuery extends BaseSecurityIdentityQuery
{
}

View file

@ -10,6 +10,10 @@
namespace Propel\PropelBundle;
use Propel\Runtime\Propel;
use Propel\Runtime\Connection\ConnectionManagerSingle;
use Propel\Runtime\Connection\ConnectionManagerMasterSlave;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -20,11 +24,63 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
*/
class PropelBundle extends Bundle
{
/**
* {@inheritdoc}
*/
public function boot()
{
try {
$this->configureConnections();
if ($this->container->getParameter('propel.logging')) {
$this->configureLogging();
}
} catch (\Exception $e) {}
}
/**
* {@inheritdoc}
*/
public function build(ContainerBuilder $container)
{
}
protected function configureConnections()
{
$config = $this->container->getParameter('propel.configuration');
$serviceContainer = Propel::getServiceContainer();
$serviceContainer->setDefaultDatasource($config['runtime']['defaultConnection']);
foreach ($config['database']['connections'] as $name => $config) {
if (!empty($config['slaves'])) {
$manager = new ConnectionManagerMasterSlave();
// configure the master (write) connection
$manager->setWriteConfiguration($config['connection']);
// configure the slave (read) connections
$manager->setReadConfiguration($config['slaves']);
} else {
$manager = new ConnectionManagerSingle();
$manager->setConfiguration($config);
}
$serviceContainer->setAdapterClass($name, $config['adapter']);
$serviceContainer->setConnectionManager($name, $manager);
}
}
protected function configureLogging()
{
$serviceContainer = Propel::getServiceContainer();
$serviceContainer->setLogger('defaultLogger', $this->container->get('propel.logger'));
foreach ($serviceContainer->getConnectionManagers() as $manager) {
$connection = $manager->getReadConnection($serviceContainer->getAdapter($manager->getName()));
$connection->setLogMethods(array_merge($connection->getLogMethods(), array('prepare')));
$connection = $manager->getWriteConnection();
$connection->setLogMethods(array_merge($connection->getLogMethods(), array('prepare')));
}
}
}

View file

@ -0,0 +1,284 @@
<?php
namespace Propel\PropelBundle\Request\ParamConverter;
use Propel\PropelBundle\Util\PropelInflector;
use Propel\Runtime\ActiveQuery\Criteria;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
/**
* PropelParamConverter
*
* This convert action parameter to a Propel Object
* there is two option for this converter:
*
* mapping : take an array of routeParam => column
* exclude : take an array of routeParam to exclude from the conversion process
*
*
* @author Jérémie Augustin <jeremie.augustin@pixel-cookers.com>
*/
class PropelParamConverter implements ParamConverterInterface
{
/**
* the pk column (e.g. id)
* @var string
*/
protected $pk;
/**
* list of column/value to use with filterBy
* @var array
*/
protected $filters = array();
/**
* list of route parameters to exclude from the conversion process
* @var array
*/
protected $exclude = array();
/**
* list of with option use to hydrate related object
* @var array
*/
protected $withs;
/**
* @var bool
*/
protected $hasWith = false;
/**
* @var RouterInterface
*/
protected $router;
public function setRouter(RouterInterface $router = null)
{
$this->router = $router;
}
/**
* @param Request $request
* @param ParamConverter $configuration
*
* @return bool
*
* @throws \LogicException
* @throws NotFoundHttpException
* @throws \Exception
*/
public function apply(Request $request, ParamConverter $configuration)
{
$class = $configuration->getClass();
$classQuery = $class . 'Query';
$classTableMap = $class::TABLE_MAP;
$this->filters = array();
$this->exclude = array();
if (!class_exists($classQuery)) {
throw new \Exception(sprintf('The %s Query class does not exist', $classQuery));
}
$tableMap = new $classTableMap();
$pkColumns = $tableMap->getPrimaryKeys();
if (count($pkColumns) === 1) {
$pk = array_pop($pkColumns);
$this->pk = strtolower($pk->getName());
}
$options = $configuration->getOptions();
// Check route options for converter options, if there are non provided.
if (empty($options) && $request->attributes->has('_route') && $this->router && $configuration instanceof ParamConverter) {
$converterOption = $this->router->getRouteCollection()->get($request->attributes->get('_route'))->getOption('propel_converter');
if (!empty($converterOption[$configuration->getName()])) {
$options = $converterOption[$configuration->getName()];
}
}
if (isset($options['mapping'])) {
// We use the mapping for calling findPk or filterBy
foreach ($options['mapping'] as $routeParam => $column) {
if ($request->attributes->has($routeParam)) {
if ($this->pk === $column) {
$this->pk = $routeParam;
} else {
$this->filters[$column] = $request->attributes->get($routeParam);
}
}
}
} else {
$this->exclude = isset($options['exclude']) ? $options['exclude'] : array();
$this->filters = $request->attributes->all();
}
if (array_key_exists($configuration->getName(), $this->filters)) {
unset($this->filters[$configuration->getName()]);
}
$this->withs = isset($options['with']) ? is_array($options['with']) ? $options['with'] : array($options['with']) : array();
// find by Pk
if (false === $object = $this->findPk($classQuery, $request)) {
// find by criteria
if (false === $object = $this->findOneBy($classQuery, $request)) {
if ($configuration->isOptional()) {
//we find nothing but the object is optional
$object = null;
} else {
throw new \LogicException('Unable to guess how to get a Propel object from the request information.');
}
}
}
if (null === $object && false === $configuration->isOptional()) {
throw new NotFoundHttpException(sprintf('%s object not found.', $configuration->getClass()));
}
$request->attributes->set($configuration->getName(), $object);
return true;
}
/**
* @param ParamConverter $configuration
*
* @return bool
*/
public function supports(ParamConverter $configuration)
{
if (null === ($classname = $configuration->getClass())) {
return false;
}
if (!class_exists($classname)) {
return false;
}
// Propel Class?
$class = new \ReflectionClass($configuration->getClass());
if ($class->implementsInterface('\Propel\Runtime\ActiveRecord\ActiveRecordInterface')) {
return true;
}
return false;
}
/**
* Try to find the object with the id
*
* @param string $classQuery the query class
* @param Request $request
*
* @return mixed
*/
protected function findPk($classQuery, Request $request)
{
if (in_array($this->pk, $this->exclude) || !$request->attributes->has($this->pk)) {
return false;
}
$query = $this->getQuery($classQuery);
if (!$this->hasWith) {
return $query->findPk($request->attributes->get($this->pk));
} else {
return $query->filterByPrimaryKey($request->attributes->get($this->pk))->find()->getFirst();
}
}
/**
* Try to find the object with all params from the $request
*
* @param string $classQuery the query class
* @param Request $request
*
* @return mixed
*/
protected function findOneBy($classQuery, Request $request)
{
$query = $this->getQuery($classQuery);
$hasCriteria = false;
foreach ($this->filters as $column => $value) {
if (!in_array($column, $this->exclude)) {
try {
$query->{'filterBy' . PropelInflector::camelize($column)}($value);
$hasCriteria = true;
} catch (\PropelException $e) { }
}
}
if (!$hasCriteria) {
return false;
}
if (!$this->hasWith) {
return $query->findOne();
} else {
return $query->find()->getFirst();
}
}
/**
* Init the query class with optional joinWith
*
* @param string $classQuery
*
* @return \ModelCriteria
*
* @throws \Exception
*/
protected function getQuery($classQuery)
{
$query = $classQuery::create();
foreach ($this->withs as $with) {
if (is_array($with)) {
if (2 == count($with)) {
$query->joinWith($with[0], $this->getValidJoin($with));
$this->hasWith = true;
} else {
throw new \Exception(sprintf('ParamConverter : "with" parameter "%s" is invalid,
only string relation name (e.g. "Book") or an array with two keys (e.g. {"Book", "LEFT_JOIN"}) are allowed',
var_export($with, true)));
}
} else {
$query->joinWith($with);
$this->hasWith = true;
}
}
return $query;
}
/**
* Return the valid join Criteria base on the with parameter
*
* @param array $with
*
* @return string
*
* @throws \Exception
*/
protected function getValidJoin($with)
{
switch (trim(str_replace(array('_', 'JOIN'), '', strtoupper($with[1])))) {
case 'LEFT':
return Criteria::LEFT_JOIN;
case 'RIGHT':
return Criteria::RIGHT_JOIN;
case 'INNER':
return Criteria::INNER_JOIN;
}
throw new \Exception(sprintf('ParamConverter : "with" parameter "%s" is invalid,
only "left", "right" or "inner" are allowed for join option',
var_export($with, true)));
}
}

104
Resources/acl_schema.xml Normal file
View file

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<database name="default" namespace="Propel\PropelBundle\Model\Acl" defaultIdMethod="native" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://xsd.propelorm.org/1.6/database.xsd">
<table name="acl_classes" phpName="AclClass">
<column name="id" type="integer" autoIncrement="true" primaryKey="true" />
<column name="class_type" type="varchar" size="200" required="true" phpName="Type" />
<unique>
<unique-column name="class_type" />
</unique>
</table>
<table name="acl_security_identities" phpName="SecurityIdentity">
<column name="id" type="integer" autoIncrement="true" primaryKey="true" />
<column name="identifier" type="varchar" size="200" required="true" />
<column name="username" type="boolean" required="true" />
<unique>
<unique-column name="identifier" />
<unique-column name="username" />
</unique>
</table>
<table name="acl_object_identities" phpName="ObjectIdentity">
<column name="id" type="integer" autoIncrement="true" primaryKey="true" />
<column name="class_id" type="integer" required="true" />
<column name="object_identifier" type="varchar" size="200" required="true" phpName="Identifier" />
<column name="parent_object_identity_id" type="integer" required="false" defaultValue="null" />
<column name="entries_inheriting" type="boolean" required="true" defaultValue="true" />
<unique>
<unique-column name="class_id" />
<unique-column name="object_identifier" />
</unique>
<index>
<index-column name="parent_object_identity_id" />
</index>
<foreign-key foreignTable="acl_classes" onDelete="RESTRICT" onUpdate="CASCADE">
<reference local="class_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="acl_object_identities" onDelete="RESTRICT" onUpdate="CASCADE">
<reference local="parent_object_identity_id" foreign="id" />
</foreign-key>
</table>
<table name="acl_object_identity_ancestors" phpName="ObjectIdentityAncestor" heavyIndexing="true">
<column name="object_identity_id" type="integer" primaryKey="true" />
<column name="ancestor_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="acl_object_identities" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="object_identity_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="acl_object_identities" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="ancestor_id" foreign="id" />
</foreign-key>
</table>
<table name="acl_entries" phpName="Entry">
<column name="id" type="integer" autoIncrement="true" primaryKey="true" />
<column name="class_id" type="integer" required="true" />
<column name="object_identity_id" type="integer" required="false" defaultValue="null" />
<column name="security_identity_id" type="integer" required="true" />
<column name="field_name" type="varchar" size="50" />
<column name="ace_order" type="integer" required="true" />
<column name="mask" type="integer" required="true" />
<column name="granting" type="boolean" required="true" />
<column name="granting_strategy" type="varchar" size="30" required="true" />
<column name="audit_success" type="boolean" required="true" defaultValue="false" />
<column name="audit_failure" type="boolean" required="true" defaultValue="true" />
<unique>
<unique-column name="class_id" />
<unique-column name="object_identity_id" />
<unique-column name="field_name" />
<unique-column name="ace_order" />
</unique>
<index>
<index-column name="class_id" />
<index-column name="object_identity_id" />
<index-column name="security_identity_id" />
</index>
<index>
<index-column name="class_id" />
</index>
<index>
<index-column name="object_identity_id" />
</index>
<index>
<index-column name="security_identity_id" />
</index>
<foreign-key foreignTable="acl_classes" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="class_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="acl_object_identities" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="object_identity_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="acl_security_identities" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="security_identity_id" foreign="id" />
</foreign-key>
</table>
</database>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="propel.converter.propel.class">Propel\PropelBundle\Request\ParamConverter\PropelParamConverter</parameter>
</parameters>
<services>
<service id="propel.converter.propel.orm" class="%propel.converter.propel.class%">
<tag name="request.param_converter" converter="propel" priority="1" />
<call method="setRouter">
<argument type="service" id="router" on-invalid="null" />
</call>
</service>
</services>
</container>

View file

@ -0,0 +1,64 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="propel.dbal.default_connection">default</parameter>
<parameter key="propel.schema_locator.class">Propel\PropelBundle\Service\SchemaLocator</parameter>
<parameter key="propel.data_collector.class">Propel\PropelBundle\DataCollector\PropelDataCollector</parameter>
<parameter key="propel.logger.class">Propel\PropelBundle\Logger\PropelLogger</parameter>
<parameter key="propel.twig.extension.syntax.class">Propel\PropelBundle\Twig\Extension\SyntaxExtension</parameter>
<parameter key="form.type_guesser.propel.class">Propel\PropelBundle\Form\TypeGuesser</parameter>
<parameter key="propel.form.type.model.class">Propel\PropelBundle\Form\Type\ModelType</parameter>
<parameter key="propel.dumper.yaml.class">Propel\PropelBundle\DataFixtures\Dumper\YamlDataDumper</parameter>
<parameter key="propel.loader.yaml.class">Propel\PropelBundle\DataFixtures\Loader\YamlDataLoader</parameter>
<parameter key="propel.loader.xml.class">Propel\PropelBundle\DataFixtures\Loader\XmlDataLoader</parameter>
</parameters>
<services>
<service id="propel.schema_locator" class="%propel.schema_locator.class%">
<argument type="service" id="file_locator" />
</service>
<service id="propel.logger" class="%propel.logger.class%">
<argument type="service" id="logger" on-invalid="null" />
<argument type="service" id="debug.stopwatch" on-invalid="null" />
</service>
<service id="propel.data_collector" class="%propel.data_collector.class%" public="false">
<argument type="service" id="propel.logger" />
<tag name="data_collector" template="PropelBundle:Collector:propel" id="propel" />
</service>
<service id="propel.twig.extension.syntax" class="%propel.twig.extension.syntax.class%">
<tag name="twig.extension" />
</service>
<service id="form.type_guesser.propel" class="%form.type_guesser.propel.class%">
<tag name="form.type_guesser" />
</service>
<service id="propel.form.type.model" class="%propel.form.type.model.class%">
<tag name="form.type" alias="model" />
</service>
<service id="propel.dumper.yaml" class="%propel.dumper.yaml.class%">
<argument>%kernel.root_dir%</argument>
<argument>%propel.configuration%</argument>
</service>
<service id="propel.loader.yaml" class="%propel.loader.yaml.class%">
<argument>%kernel.root_dir%</argument>
<argument>%propel.configuration%</argument>
<argument type="service" id="faker.generator" on-invalid="null" />
</service>
<service id="propel.loader.xml" class="%propel.loader.xml.class%">
<argument>%kernel.root_dir%</argument>
<argument>%propel.configuration%</argument>
</service>
</services>
</container>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="propel.security.acl.provider.model.class">Propel\PropelBundle\Security\Acl\AuditableAclProvider</parameter>
<parameter key="propel.security.user.provider.class">Propel\PropelBundle\Security\User\PropelUserProvider</parameter>
</parameters>
<services>
<service id="propel.security.acl.provider" class="%propel.security.acl.provider.model.class%" public="false">
<argument type="service" id="security.acl.permission_granting_strategy" />
<argument type="service" id="propel.security.acl.connection" on-invalid="null" />
<argument type="service" id="security.acl.cache" on-invalid="null" />
</service>
<service id="propel.security.user.provider" class="%propel.security.user.provider.class%" abstract="true" public="false" />
</services>
</container>

View file

@ -0,0 +1,21 @@
<?php
namespace ##NAMESPACE##;
use Propel\PropelBundle\Form\BaseAbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ##CLASS## extends BaseAbstractType
{
protected $options = array(
'data_class' => '##FQCN##',
'name' => '##TYPE_NAME##',
);
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{##BUILD_CODE##
}
}

View file

@ -0,0 +1,111 @@
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
{% block toolbar %}
{# the web debug toolbar content #}
{% set icon %}
<img alt="Propel" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAcCAYAAAB2+A+pAAACzmlDQ1BJQ0MgUHJvZmlsZQAAeNqNk8trFFkUh7/q3KCQIAy0r14Ml1lIkCSUDzQiPtJJbKKxbcpEkyBIp/p2d5mb6ppb1XEUEcnGpc4we/GxcOEf4MKFK90oEXwhiHsVRRTcqLSL6nRX8HlWX/3Oub9zzi0udNrFINApCXN+ZJxcVk5OTcsVz0ixni4ydBXdMBgsFMYAikGg+SY+PsECeNj3/fxPo6sUunNgrYTU+5IKXej4DNQqk1PTIDSQPhkFEYhzQNrE+v9Aeibm60DajDtDIG4Bq9zARCDuAQNutViCTgH0VhI1Mwme03W3Oc8fQLfyJw4DGyB1VoUjTbYWSsXhA0A/WK9KangE6AXretnbNwr0AM/LZt9EzNZGLxodjzl1xNf5sSav82fyh5qeIoiyzpJ/OH94ZEk/UdxfADJgObO1Aw6wBlJ7T1fHj8Zs6dPVoXyTH5m6MwH8BalrgS6MxbOl7jCFRuHho/CROOTI0keAoUYZDw+NRw6Fj8LgETL73UpNIcGSHC/xeYnB42/qKCQOR8jmWehtOUj7qf3Gfmxftq/Zry9m6j3tzII57rmLF95RQGFavs1sc6bY36XGIBpNBcVca6cwMWliurJ/MdN2chcvvFPn8x8TW6pEpz5mUITMYvCYR6EJUQwmuv3o9hT67plb69q9Houbxx523z2z7K5q32ylWlst/27XJc8r8afYJEbFgNiBFHvEXrFbDIsBsVOMtU5M4ONxEoUhpIjG5xRy2f9bqiV+awCkc8pXxnOlk8vKgqmVPa0ST/QX6d+MyalpGdN0HW6EsHZrW/vgYAHWmsW2Fh2EXW+h40Fb68nA6ktwc5tbN/NNa8u6D5H6JwIYqgWnjFepRnKzbW+Xg0GglRz13f5eWdRaGq9SjUJpVKjMvCr1E5a3bI5durPQ+aLR+LABVvwHX/5tND5daTS+XIWO53BbfwXAvP1FP6ZP5AAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAG9gAABvYBDBXjEwAAAAd0SU1FB9sEBhQVGSw3+igAAAWFSURBVEjH3VdfTFtVGP+d09v2jna0pYzC7EbpmsBguAw6+kCydJJh4nhYiMaYKInbNMvigpo49+CDL0bji89LlhgWH8TFaII004VIMmDAKGF/Ojr5t1YYpfS2pZTb295/vgACg4Hb9MHzdr/zfd/v/r7zO+d8B/i/jWQyueXcvXv3/h3QyclJspm9p6eHjIyMUABgXgTQ4OAg6urqcPv2bQ3HcXA6nTIAjI+PV8disUZVVc+m0+mKxcXFrMlkegfANfK8oAMDA/B4POtsIyMjJwVBOJtKpV4VBGGXoigghMBoNM6Wl5cfstvt8WdmnEgkcOXKlXWgfr//eCKR+DwSiRzL5XKQZRmEEAUAZVkWJSUlZ+12e3x4eFjzTMAPHjzQtLS0KB0dHery2p3I5XJfRSKRGlmWIUkSCCGglEKn01FKqWgymV6vrKz0VVZWoqamRib/RKVms3nj2r6RSqXO53I5bzabBQBoNBqwLAtK6RTDMCMGgyFotVp/dDqd/uWqoLa2dntxhcNhDAwMrAO9f//+W3Nzc5disdjLoigCACwWCzQaTS+ltJ1SOujxeMYppdyaLUSMRqNaVlYGAHgqY1VVQcjfLkNDQ69KknQ1nU4XZbNZ6PX6P81m87DJZPoulUpdr62tTa/4jo2NFTAMc0Kv15v37t17eWOuLRlnMplVxzt37rwmiuK5paWlGlEUg2az+arNZvt+3759/g0/ag0EAqclSWpJJBKHeJ6HVqudEUWxkxAyvdZ3O8a6aDTanEwma+bn5wMOh6Pfbrc/3Og3OzvriUajrTzP10uStD+Xy0FVVeh0Olgslm+qq6s/7u3t1dTX18s7BaahUMjicDi4zeYnJibqFhYWPuV5vlmWZSiKAkVRBAAswzDIz8/3HTly5CQAxGIxFBYWbl9qnudX9uAToKOjow1LS0vnHz9+3CxJElRVhaqqAACWZVlKKQwGw0B1dfV5AAgEAutAt2UcDAZRUVGx+j09PX18Zmbmg1wu1yyK4qoGWJYFwzAhrVY7RCm9vnv37kmHw9FLCMkCgCAIYFl2XW5mi0Mera2tq6Acx3nC4fDXU1NTxwBAlmWwLBs3GAwhlmU7M5nM5cOHD0cIIdLaPGNjY9Tlcilr1bwl40gkQoqLi9XlwKp4PP4RIeTM8vr5jUbjWH5+/g+lpaW/EkL4NXEv+f3+g4uLi2U2my3t9Xp/IoQIW1VzHeO5uTlis9lUjuOq4vH4Z6IoHmMYZpQQ8mFxcfHdkpKSHkKIuDbm1q1b9clk8mJPT0+NLMv2bDaLTCbzB4BfAOwM2Gazqel0Ok8QhFcopdd4nv/E7XZPbwwSRdHQ19f3Nsdx56ampg7KsqxfEVleXl5mz5497xNCFp+mnydKHYvFSGFhobrFeW3p7+9/M51OX8pkMqUralYURQFA9Xo9bDbb6YaGhm/dbjd8Ph+Kioq2B15YWIDJZNpsP7M+n+8Cz/NnBEEolyQJlFKoqgpK6Yqqx3Q63RdNTU1tO7l0nmDc1dVFGhoa1GVAY1dX13vz8/MXRFEsWwFavoGiOp2uW6/XX1dVtdflcnFOp5MDgM7OTmi1WjQ2Nm4NHAgEUFVVheHhYRIOh8mpU6cUALh582ZrNBr9kuf5XQzDgBAyYzQapzUazc9VVVUD+/fv/32zhN3d3fB6vdsz5jgOVqt11XDjxo13U6nURUpphSRJI3l5eX0ajab76NGjd61W68PNkrS3t8PtduPAgQM7bibI0NAQ3G43gsFgUywWuygIgqooym9arbbD6/VOEkJWr7qysjLS1tZG7Xa74nQ61efuEkOhUOHExMSJ/v5+22bz0Wj0xffAoVBoU3tBQcFTm/LnHQQAHj16BEVRiKIoqsvl+k9eGn8BMMeiAFTierUAAAAASUVORK5CYII=" />
<span class="sf-toolbar-status">{{ collector.querycount }}</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>DB Queries</b>
<span>{{ collector.querycount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Query time</b>
<span>{{ '%0.2f'|format(collector.time * 1000) }} ms</span>
</div>
{% endset %}
{% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %}
{% endblock %}
{% block menu %}
{# the menu content #}
<span class="label">
<span class="icon"><img src="{{ asset('bundles/propel/images/profiler/propel.png') }}" alt="" /></span>
<strong>Propel</strong>
<span class="count">
<span>{{ collector.querycount }}</span>
<span>{{ '%0.0f'|format(collector.time * 1000) }} ms</span>
</span>
</span>
{% endblock %}
{% block panel %}
{# the panel content #}
<style type="text/css">
.SQLKeyword {
color: blue;
white-space: nowrap;
}
.SQLName {
color: #464646;
white-space: nowrap;
}
.SQLInfo, .SQLComment {
color: gray;
display: block;
font-size: 0.9em;
margin: 3px 0;
}
.SQLExplain {
margin: 5px;
}
.SQLExplain .error {
background-color: #F2DEDE;
border-color: #EED3D7;
color: #B94A48;
padding: 8px 35px 8px 14px;
font-weight: bold;
}
#content .SQLExplain h2 {
font-size: 17px;
margin-bottom: 0;
}
</style>
<h2>Queries</h2>
<table summary="Show logged queries">
<thead>
<tr>
<th>SQL queries</th>
</tr>
</thead>
<tbody>
{% if not collector.querycount %}
<tr><td>No queries.</td></tr>
{% else %}
{% for i, query in collector.queries %}
<tr>
<td>
<a name="propel-query-{{ i }}" ></a>
<code>{{ query.sql|format_sql }}</code>
{% if app.request.query.has('query') and app.request.query.get('query') == i %}
<div class="SQLExplain">
{% render controller('PropelBundle:Panel:explain', {
'token': token,
'panel': 'propel',
'query': app.request.query.get('query'),
'connection': app.request.query.get('connection')
}) %}
</div>
{% endif %}
<div class="SQLInfo">
Time: {{ query.time }} - Memory: {{ query.memory|format_memory }} - Connection: {{ query.connection }}
{% if app.request.query.get('query', -1) != i %}
- <a href="{{ path('_profiler', {'panel': 'propel', 'token': token, 'connection': query.connection, 'query': i}) }}#propel-query-{{ i }}">Explain the query</a>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
{% render controller('PropelBundle:Panel:configuration') %}
{% endblock %}

View file

@ -0,0 +1,69 @@
<h2>Propel configuration</h2>
<table summary="Current Propel configuration">
<thead>
</thead>
<tbody>
<tr>
<th>Propel version</th>
<td>{{ propel_version }}</td>
</tr>
<tr>
<th>Default connection</th>
<td>{{ configuration.runtime.defaultConnection }}</td>
<tr>
<th>Logging</th>
<td>{{ logging ? 'enabled' : 'disabled' }}</td>
</tr>
</tbody>
</table>
<h2>Propel connections</h2>
<table summary="Current Propel connections">
<thead>
<tr>
<th>Connection name</th>
<th colspan="2" style="text-align: center;">Configuration parameters</th>
</tr>
</thead>
<tbody>
{% for name, config in configuration.database.connections %}
<tr>
<th rowspan="5" style="vertical-align: top;">
{{ name }}
</th>
<th>Adapter</th>
<td>{{ config.adapter }}</td>
</tr>
<tr>
<th>DSN</th>
<td>{{ config.dsn }}</td>
</tr>
<tr>
<th>Class</th>
<td>{{ config.classname }}</td>
</tr>
<tr>
<th>Options</th>
<td>
<ul>
{% for key, value in config.options %}
<li>{{ key }} : {{ value }}</li>
{% endfor %}
</ul>
</td>
</tr>
<tr>
<th>Attributes</th>
<td>
<ul>
{% for key, value in config.attributes %}
<li>{{ key }} : {{ value }}</li>
{% endfor %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View file

@ -0,0 +1,16 @@
<h2>Explanation</h2>
<table>
<tr>
{% for label in data[0]|keys %}
<th>{{ label }}</th>
{% endfor %}
</tr>
{% for row in data %}
<tr>
{% for item in row %}
<td>{{ item }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>

View file

@ -0,0 +1,181 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\PropelBundle\Model\Acl\EntryQuery;
use Propel\PropelBundle\Model\Acl\ObjectIdentityQuery;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Propel\PropelBundle\Security\Acl\Domain\Acl;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\AclCacheInterface;
use Symfony\Component\Security\Acl\Model\AclProviderInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
/**
* An implementation of the AclProviderInterface using Propel ORM.
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AclProvider implements AclProviderInterface
{
protected $permissionGrantingStrategy;
protected $connection;
protected $cache;
/**
* Constructor.
*
* @param \Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param ConnectionInterface $con
* @param \Symfony\Component\Security\Acl\Model\AclCacheInterface $cache
*/
public function __construct(PermissionGrantingStrategyInterface $permissionGrantingStrategy, ConnectionInterface $connection = null, AclCacheInterface $cache = null)
{
$this->permissionGrantingStrategy = $permissionGrantingStrategy;
$this->connection = $connection;
$this->cache = $cache;
}
/**
* Retrieves all child object identities from the database.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $parentObjectIdentity
* @param bool $directChildrenOnly
*
* @return array
*/
public function findChildren(ObjectIdentityInterface $parentObjectIdentity, $directChildrenOnly = false)
{
$modelIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($parentObjectIdentity, $this->connection);
if (empty($modelIdentity)) {
return array();
}
if ($directChildrenOnly) {
$collection = ObjectIdentityQuery::create()->findChildren($modelIdentity, $this->connection);
} else {
$collection = ObjectIdentityQuery::create()->findGrandChildren($modelIdentity, $this->connection);
}
$children = array();
foreach ($collection as $eachChild) {
$children[] = new ObjectIdentity($eachChild->getIdentifier(), $eachChild->getAclClass($this->connection)->getType());
}
return $children;
}
/**
* Returns the ACL that belongs to the given object identity
*
* @throws \Symfony\Component\Security\Acl\Exception\AclNotFoundException
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param array $securityIdentities
*
* @return \Symfony\Component\Security\Acl\Model\AclInterface
*/
public function findAcl(ObjectIdentityInterface $objectIdentity, array $securityIdentities = array())
{
$modelObj = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($objectIdentity, $this->connection);
if (null !== $this->cache and null !== $modelObj) {
$cachedAcl = $this->cache->getFromCacheById($modelObj->getId());
if ($cachedAcl instanceof AclInterface) {
return $cachedAcl;
}
}
$collection = EntryQuery::create()->findByAclIdentity($objectIdentity, $securityIdentities, $this->connection);
if (0 === count($collection)) {
if (empty($securityIdentities)) {
$errorMessage = 'There is no ACL available for this object identity. Please create one using the MutableAclProvider.';
} else {
$errorMessage = 'There is at least no ACL for this object identity and the given security identities. Try retrieving the ACL without security identity filter and add ACEs for the security identities.';
}
throw new AclNotFoundException($errorMessage);
}
$loadedSecurityIdentities = array();
foreach ($collection as $eachEntry) {
if (!isset($loadedSecurityIdentities[$eachEntry->getSecurityIdentity()->getId()])) {
$loadedSecurityIdentities[$eachEntry->getSecurityIdentity()->getId()] = SecurityIdentity::toAclIdentity($eachEntry->getSecurityIdentity());
}
}
$parentAcl = null;
$entriesInherited = true;
if (null !== $modelObj) {
$entriesInherited = $modelObj->getEntriesInheriting();
if (null !== $modelObj->getParentObjectIdentityId()) {
$parentObj = $modelObj->getObjectIdentityRelatedByParentObjectIdentityId($this->connection);
try {
$parentAcl = $this->findAcl(new ObjectIdentity($parentObj->getIdentifier(), $parentObj->getAclClass($this->connection)->getType()));
} catch (AclNotFoundException $e) {
/*
* This happens e.g. if the parent ACL is created, but does not contain any ACE by now.
* The ACEs may be applied later on.
*/
}
}
}
return $this->getAcl($collection, $objectIdentity, $loadedSecurityIdentities, $parentAcl, $entriesInherited);
}
/**
* Returns the ACLs that belong to the given object identities
*
* @throws \Symfony\Component\Security\Acl\Exception\AclNotFoundException When at least one object identity is missing its ACL.
*
* @param array $objectIdentities an array of ObjectIdentityInterface implementations
* @param array $securityIdentities an array of SecurityIdentityInterface implementations
*
* @return \SplObjectStorage mapping the passed object identities to ACLs
*/
public function findAcls(array $objectIdentities, array $securityIdentities = array())
{
$result = new \SplObjectStorage();
foreach ($objectIdentities as $eachIdentity) {
$result[$eachIdentity] = $this->findAcl($eachIdentity, $securityIdentities);
}
return $result;
}
/**
* Create an ACL.
*
* @param ObjectCollection $collection
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
*
* @return \Propel\PropelBundle\Security\Acl\Domain\Acl
*/
protected function getAcl(ObjectCollection $collection, ObjectIdentityInterface $objectIdentity, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true)
{
return new Acl($collection, $objectIdentity, $this->permissionGrantingStrategy, $loadedSecurityIdentities, $parentAcl, $inherited);
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\PropelBundle\Security\Acl\Domain\AuditableAcl;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AuditableAclProvider extends MutableAclProvider
{
/**
* Get an ACL for this provider.
*
* @param Propel\Runtime\Collection\ObjectCollection $collection
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
*
* @return \Propel\PropelBundle\Security\Acl\Domain\AuditableAcl
*/
protected function getAcl(ObjectCollection $collection, ObjectIdentityInterface $objectIdentity, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true)
{
return new AuditableAcl($collection, $objectIdentity, $this->permissionGrantingStrategy, $loadedSecurityIdentities, $parentAcl, $inherited, $this->connection);
}
}

316
Security/Acl/Domain/Acl.php Normal file
View file

@ -0,0 +1,316 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\Runtime\Collection\ObjectCollection;
use Symfony\Component\Security\Acl\Exception\Exception as AclException;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/**
* An ACL implementation that is immutable based on data from a ObjectCollection of Propel\PropelBundle\Model\Acl\Entry.
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class Acl implements AclInterface
{
protected $model = '\Propel\PropelBundle\Model\Acl\Entry';
protected $classAces = array();
protected $classFieldAces = array();
protected $objectAces = array();
protected $objectFieldAces = array();
protected $objectIdentity;
protected $parentAcl;
protected $permissionGrantingStrategy;
protected $inherited;
protected $loadedSecurityIdentities = array();
/**
* A list of known associated fields on this ACL.
*
* @var array
*/
protected $fields = array();
/**
* Constructor.
*
* @param ObjectCollection $entries
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param \Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
*/
public function __construct(ObjectCollection $entries, ObjectIdentityInterface $objectIdentity, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true)
{
if ($entries->getFullyQualifiedModel() !== $this->model) {
throw new AclException(sprintf('The given collection does not contain models of class "%s" but of class "%s".', $this->model, $entries->getFullyQualifiedModel()));
}
foreach ($entries as $eachEntry) {
if (null === $eachEntry->getFieldName() and null === $eachEntry->getObjectIdentityId()) {
$this->classAces[] = new Entry($eachEntry, $this);
}
if (null !== $eachEntry->getFieldName() and null === $eachEntry->getObjectIdentityId()) {
if (empty($this->classFieldAces[$eachEntry->getFieldName()])) {
$this->classFieldAces[$eachEntry->getFieldName()] = array();
$this->updateFields($eachEntry->getFieldName());
}
$this->classFieldAces[$eachEntry->getFieldName()][] = new FieldEntry($eachEntry, $this);
}
if (null === $eachEntry->getFieldName() and null !== $eachEntry->getObjectIdentityId()) {
$this->objectAces[] = new Entry($eachEntry, $this);
}
if (null !== $eachEntry->getFieldName() and null !== $eachEntry->getObjectIdentityId()) {
if (empty($this->objectFieldAces[$eachEntry->getFieldName()])) {
$this->objectFieldAces[$eachEntry->getFieldName()] = array();
$this->updateFields($eachEntry->getFieldName());
}
$this->objectFieldAces[$eachEntry->getFieldName()][] = new FieldEntry($eachEntry, $this);
}
}
$this->objectIdentity = $objectIdentity;
$this->permissionGrantingStrategy = $permissionGrantingStrategy;
$this->parentAcl = $parentAcl;
$this->inherited = $inherited;
$this->loadedSecurityIdentities = $loadedSecurityIdentities;
$this->fields = array_unique($this->fields);
}
/**
* Returns all class-based ACEs associated with this ACL
*
* @return array
*/
public function getClassAces()
{
return $this->classAces;
}
/**
* Returns all class-field-based ACEs associated with this ACL
*
* @param string $field
*
* @return array
*/
public function getClassFieldAces($field)
{
return isset($this->classFieldAces[$field]) ? $this->classFieldAces[$field] : array();
}
/**
* Returns all object-based ACEs associated with this ACL
*
* @return array
*/
public function getObjectAces()
{
return $this->objectAces;
}
/**
* Returns all object-field-based ACEs associated with this ACL
*
* @param string $field
*
* @return array
*/
public function getObjectFieldAces($field)
{
return isset($this->objectFieldAces[$field]) ? $this->objectFieldAces[$field] : array();
}
/**
* Returns the object identity associated with this ACL
*
* @return \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface
*/
public function getObjectIdentity()
{
return $this->objectIdentity;
}
/**
* Returns the parent ACL, or null if there is none.
*
* @return \Symfony\Component\Security\Acl\Model\AclInterface|null
*/
public function getParentAcl()
{
return $this->parentAcl;
}
/**
* Whether this ACL is inheriting ACEs from a parent ACL.
*
* @return bool
*/
public function isEntriesInheriting()
{
return $this->inherited;
}
/**
* Determines whether field access is granted
*
* @param string $field
* @param array $masks
* @param array $securityIdentities
* @param bool $administrativeMode
*
* @return bool
*/
public function isFieldGranted($field, array $masks, array $securityIdentities, $administrativeMode = false)
{
return $this->permissionGrantingStrategy->isFieldGranted($this, $field, $masks, $securityIdentities, $administrativeMode);
}
/**
* Determines whether access is granted
*
* @throws \Symfony\Component\Security\Acl\Exception\NoAceFoundException when no ACE was applicable for this request
*
* @param array $masks
* @param array $securityIdentities
* @param bool $administrativeMode
*
* @return bool
*/
public function isGranted(array $masks, array $securityIdentities, $administrativeMode = false)
{
return $this->permissionGrantingStrategy->isGranted($this, $masks, $securityIdentities, $administrativeMode);
}
/**
* Whether the ACL has loaded ACEs for all of the passed security identities
*
* @throws \InvalidArgumentException
*
* @param mixed $securityIdentities an implementation of SecurityIdentityInterface, or an array thereof
*
* @return bool
*/
public function isSidLoaded($securityIdentities)
{
if (!is_array($securityIdentities)) {
$securityIdentities = array($securityIdentities);
}
$found = 0;
foreach ($securityIdentities as $eachSecurityIdentity) {
if (!$eachSecurityIdentity instanceof SecurityIdentityInterface) {
throw new \InvalidArgumentException('At least one entry of the given list is not implementing the "SecurityIdentityInterface".');
}
foreach ($this->loadedSecurityIdentities as $eachLoadedIdentity) {
if ($eachSecurityIdentity->equals($eachLoadedIdentity)) {
$found++;
break;
}
}
}
return ($found === count($securityIdentities));
}
/**
* String representation of object
*
* @link http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or &null;
*/
public function serialize()
{
return serialize(array(
$this->model,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->objectIdentity,
$this->parentAcl,
$this->permissionGrantingStrategy,
$this->inherited,
$this->loadedSecurityIdentities,
));
}
/**
* Constructs the object
*
* @link http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized
*
* @return mixed the original value unserialized.
*/
public function unserialize($serialized)
{
list(
$this->model,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->objectIdentity,
$this->parentAcl,
$this->permissionGrantingStrategy,
$this->inherited,
$this->loadedSecurityIdentities,
) = unserialize($serialized);
return $this;
}
/**
* Returns a list of associated fields on this ACL.
*
* @return array
*/
public function getFields()
{
return $this->fields;
}
/**
* Update the internal list of associated fields on this ACL.
*
* @param string $field
*
* @return \Propel\PropelBundle\Security\Acl\Domain\Acl $this
*/
protected function updateFields($field)
{
if (!in_array($field, $this->fields)) {
$this->fields[] = $field;
}
return $this;
}
}

View file

@ -0,0 +1,103 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Symfony\Component\Security\Acl\Model\AuditableAclInterface;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AuditableAcl extends MutableAcl implements AuditableAclInterface
{
/**
* Updates auditing for class-based ACE
*
* @param integer $index
* @param bool $auditSuccess
* @param bool $auditFailure
*/
public function updateClassAuditing($index, $auditSuccess, $auditFailure)
{
$this->updateAuditing($this->classAces, $index, $auditSuccess, $auditFailure);
}
/**
* Updates auditing for class-field-based ACE
*
* @param integer $index
* @param string $field
* @param bool $auditSuccess
* @param bool $auditFailure
*/
public function updateClassFieldAuditing($index, $field, $auditSuccess, $auditFailure)
{
$this->validateField($this->classFieldAces, $field);
$this->updateAuditing($this->classFieldAces[$field], $index, $auditSuccess, $auditFailure);
}
/**
* Updates auditing for object-based ACE
*
* @param integer $index
* @param bool $auditSuccess
* @param bool $auditFailure
*/
public function updateObjectAuditing($index, $auditSuccess, $auditFailure)
{
$this->updateAuditing($this->objectAces, $index, $auditSuccess, $auditFailure);
}
/**
* Updates auditing for object-field-based ACE
*
* @param integer $index
* @param string $field
* @param bool $auditSuccess
* @param bool $auditFailure
*/
public function updateObjectFieldAuditing($index, $field, $auditSuccess, $auditFailure)
{
$this->validateField($this->objectFieldAces, $field);
$this->updateAuditing($this->objectFieldAces[$field], $index, $auditSuccess, $auditFailure);
}
/**
* Update auditing on a single ACE.
*
* @throws \InvalidArgumentException
*
* @param array $list
* @param int $index
* @param bool $auditSuccess
* @param bool $auditFailure
*
* @return \Propel\PropelBundle\Security\Acl\Domain\AuditableAcl $this
*/
protected function updateAuditing(array &$list, $index, $auditSuccess, $auditFailure)
{
if (!is_bool($auditSuccess) or !is_bool($auditFailure)) {
throw new \InvalidArgumentException('The given auditing flags are invalid. Please provide boolean only.');
}
$this->validateIndex($list, $index);
$entry = ModelEntry::fromAclEntry($list[$index])
->setAuditSuccess($auditSuccess)
->setAuditFailure($auditFailure)
;
$list[$index] = ModelEntry::toAclEntry($entry, $this);
return $this;
}
}

View file

@ -0,0 +1,192 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/**
* An ACE implementation retrieving data from a given Propel\PropelBundle\Model\Acl\Entry.
*
* The entry is only used to grab a "snapshot" of its data as an EntryInterface is immutable!
*
* @see \Symfony\Component\Security\Acl\Model\EntryInterface
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class Entry implements AuditableEntryInterface
{
protected $acl;
protected $id;
protected $securityIdentity;
protected $mask;
protected $isGranting;
protected $strategy;
protected $auditSuccess;
protected $auditFailure;
/**
* Constructor.
*
* @param \Propel\PropelBundle\Model\Acl\Entry $entry
* @param \Symfony\Component\Security\Acl\Model\AclInterface $acl
*/
public function __construct(ModelEntry $entry, AclInterface $acl)
{
$this->acl = $acl;
$this->securityIdentity = SecurityIdentity::toAclIdentity($entry->getSecurityIdentity());
/*
* A new ACE (from a MutableAcl) does not have an ID,
* but will be persisted by the MutableAclProvider afterwards, if issued.
*/
if ($entry->getId()) {
$this->id = $entry->getId();
}
$this->mask = $entry->getMask();
$this->isGranting = $entry->getGranting();
$this->strategy = $entry->getGrantingStrategy();
$this->auditFailure = $entry->getAuditFailure();
$this->auditSuccess = $entry->getAuditSuccess();
}
/**
* String representation of object
*
* @link http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or &null;
*/
public function serialize()
{
return serialize(array(
$this->acl,
$this->securityIdentity,
$this->id,
$this->mask,
$this->isGranting,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
));
}
/**
* Constructs the object
*
* @link http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized
*
* @return mixed the original value unserialized.
*/
public function unserialize($serialized)
{
list(
$this->acl,
$this->securityIdentity,
$this->id,
$this->mask,
$this->isGranting,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
) = unserialize($serialized);
return $this;
}
/**
* The ACL this ACE is associated with.
*
* @return \Symfony\Component\Security\Acl\Model\AclInterface
*/
public function getAcl()
{
return $this->acl;
}
/**
* The security identity associated with this ACE
*
* @return \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface
*/
public function getSecurityIdentity()
{
return $this->securityIdentity;
}
/**
* The primary key of this ACE
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* The permission mask of this ACE
*
* @return integer
*/
public function getMask()
{
return $this->mask;
}
/**
* The strategy for comparing masks
*
* @return string
*/
public function getStrategy()
{
return $this->strategy;
}
/**
* Returns whether this ACE is granting, or denying
*
* @return bool
*/
public function isGranting()
{
return $this->isGranting;
}
/**
* Whether auditing for successful grants is turned on
*
* @return bool
*/
public function isAuditFailure()
{
return $this->auditFailure;
}
/**
* Whether auditing for successful denies is turned on
*
* @return bool
*/
public function isAuditSuccess()
{
return $this->auditSuccess;
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\FieldEntryInterface;
/**
* An ACE implementation retrieving data from a given \Propel\PropelBundle\Model\Acl\Entry.
*
* The entry is only used to grab a "snapshot" of its data as an \Symfony\Component\Security\Acl\Model\EntryInterface is immutable!
*
* @see \Symfony\Component\Security\Acl\Model\EntryInterface
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class FieldEntry extends Entry implements FieldEntryInterface
{
protected $field;
/**
* Constructor.
*
* @param \Propel\PropelBundle\Model\Acl\Entry $entry
* @param \Symfony\Component\Security\Acl\Model\AclInterface $acl
*/
public function __construct(ModelEntry $entry, AclInterface $acl)
{
$this->field = $entry->getFieldName();
parent::__construct($entry, $acl);
}
/**
* Returns the field used for this entry.
*
* @return string
*/
public function getField()
{
return $this->field;
}
/**
* String representation of object
*
* @link http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or &null;
*/
public function serialize()
{
return serialize(array(
$this->acl,
$this->securityIdentity,
$this->id,
$this->mask,
$this->isGranting,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
$this->field,
));
}
/**
* Constructs the object
*
* @link http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized
*
* @return mixed the original value unserialized.
*/
public function unserialize($serialized)
{
list(
$this->acl,
$this->securityIdentity,
$this->id,
$this->mask,
$this->isGranting,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
$this->field,
) = unserialize($serialized);
return $this;
}
}

View file

@ -0,0 +1,531 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Propel\PropelBundle\Model\Acl\ObjectIdentity;
use Propel\PropelBundle\Model\Acl\ObjectIdentityQuery;
use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\MutableAclInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class MutableAcl extends Acl implements MutableAclInterface
{
/**
* The id of the current ACL.
*
* It's the id of the ObjectIdentity model.
*
* @var int
*/
protected $id;
/**
* A reference to the ObjectIdentity this ACL is mapped to.
*
* @var \Propel\PropelBundle\Model\Acl\ObjectIdentity
*/
protected $modelObjectIdentity;
/**
* A connection to be used for all changes on the ACL.
*
* @var ConnectionInterface
*/
protected $con;
/**
* Constructor.
*
* @param ObjectCollection $entries
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param \Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
* @param ConnectionInterface $con
*/
public function __construct(ObjectCollection $entries, ObjectIdentityInterface $objectIdentity, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true, ConnectionInterface $con = null)
{
parent::__construct($entries, $objectIdentity, $permissionGrantingStrategy, $loadedSecurityIdentities, $parentAcl, $inherited);
$this->modelObjectIdentity = ObjectIdentityQuery::create()
->filterByAclObjectIdentity($objectIdentity, $con)
->findOneOrCreate($con)
;
if ($this->modelObjectIdentity->isNew()) {
$this->modelObjectIdentity->save($con);
}
$this->id = $this->modelObjectIdentity->getId();
$this->con = $con;
}
/**
* Returns the primary key of this ACL
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Sets whether entries are inherited
*
* @param bool $boolean
*/
public function setEntriesInheriting($boolean)
{
$this->inherited = $boolean;
}
/**
* Sets the parent ACL
*
* @param \Symfony\Component\Security\Acl\Model\AclInterface|null $acl
*/
public function setParentAcl(AclInterface $acl = null)
{
$this->parentAcl = $acl;
}
/**
* Deletes a class-based ACE
*
* @param integer $index
*/
public function deleteClassAce($index)
{
$this->deleteIndex($this->classAces, $index);
}
/**
* Deletes a class-field-based ACE
*
* @param integer $index
* @param string $field
*/
public function deleteClassFieldAce($index, $field)
{
$this
->validateField($this->classFieldAces, $field)
->deleteIndex($this->classFieldAces[$field], $index)
;
}
/**
* Deletes an object-based ACE
*
* @param integer $index
*/
public function deleteObjectAce($index)
{
$this->deleteIndex($this->objectAces, $index);
}
/**
* Deletes an object-field-based ACE
*
* @param integer $index
* @param string $field
*/
public function deleteObjectFieldAce($index, $field)
{
$this
->validateField($this->objectFieldAces, $field)
->deleteIndex($this->objectFieldAces[$field], $index)
;
}
/**
* Inserts a class-based ACE
*
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param integer $mask
* @param integer $index
* @param bool $granting
* @param string $strategy
*/
public function insertClassAce(SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null)
{
$this->insertToList($this->classAces, $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting));
}
/**
* Inserts a class-field-based ACE
*
* @param string $field
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param integer $mask
* @param integer $index
* @param boolean $granting
* @param string $strategy
*/
public function insertClassFieldAce($field, SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null)
{
if (!isset($this->classFieldAces[$field])) {
$this->classFieldAces[$field] = array();
}
$this->insertToList($this->classFieldAces[$field], $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting, $field));
}
/**
* Inserts an object-based ACE
*
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param integer $mask
* @param integer $index
* @param boolean $granting
* @param string $strategy
*/
public function insertObjectAce(SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null)
{
$this->insertToList($this->objectAces, $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting));
}
/**
* Inserts an object-field-based ACE
*
* @param string $field
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param integer $mask
* @param integer $index
* @param boolean $granting
* @param string $strategy
*/
public function insertObjectFieldAce($field, SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null)
{
if (!isset($this->objectFieldAces[$field])) {
$this->objectFieldAces[$field] = array();
}
$this->insertToList($this->objectFieldAces[$field], $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting, $field));
}
/**
* Updates a class-based ACE
*
* @param integer $index
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
*/
public function updateClassAce($index, $mask, $strategy = null)
{
$this->updateAce($this->classAces, $index, $mask, $strategy);
}
/**
* Updates a class-field-based ACE
*
* @param integer $index
* @param string $field
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
*/
public function updateClassFieldAce($index, $field, $mask, $strategy = null)
{
$this
->validateField($this->classFieldAces, $field)
->updateAce($this->classFieldAces[$field], $index, $mask, $strategy)
;
}
/**
* Updates an object-based ACE
*
* @param integer $index
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
*/
public function updateObjectAce($index, $mask, $strategy = null)
{
$this->updateAce($this->objectAces, $index, $mask, $strategy);
}
/**
* Updates an object-field-based ACE
*
* @param integer $index
* @param string $field
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
*/
public function updateObjectFieldAce($index, $field, $mask, $strategy = null)
{
$this->validateField($this->objectFieldAces, $field);
$this->updateAce($this->objectFieldAces[$field], $index, $mask, $strategy);
}
/**
* String representation of object
*
* @link http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or &null;
*/
public function serialize()
{
return serialize(array(
$this->id,
$this->modelObjectIdentity,
$this->model,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->objectIdentity,
$this->parentAcl,
$this->permissionGrantingStrategy,
$this->inherited,
$this->loadedSecurityIdentities,
));
}
/**
* Constructs the object
*
* @link http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized
*
* @return mixed the original value unserialized.
*/
public function unserialize($serialized)
{
list(
$this->id,
$this->modelObjectIdentity,
$this->model,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->objectIdentity,
$this->parentAcl,
$this->permissionGrantingStrategy,
$this->inherited,
$this->loadedSecurityIdentities,
) = unserialize($serialized);
return $this;
}
/**
* Insert a given entry into the list on the given index by shifting all others.
*
* @param array $list
* @param int $index
* @param \Propel\PropelBundle\Model\Acl\Entry\Entry $entry
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function insertToList(array &$list, $index, Entry $entry)
{
$this->isWithinBounds($list, $index);
if ($entry instanceof FieldEntry) {
$this->updateFields($entry->getField());
}
$list = array_merge(
array_slice($list, 0, $index),
array($entry),
array_splice($list, $index)
);
return $this;
}
/**
* Update a single ACE of this ACL.
*
* @param array $list
* @param int $index
* @param int $mask
* @param string $strategy
* @param string $field
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function updateAce(array &$list, $index, $mask, $strategy = null)
{
$this->validateIndex($list, $index);
$entry = ModelEntry::fromAclEntry($list[$index]);
// Apply updates
$entry->setMask($mask);
if (null !== $strategy) {
$entry->setGrantingStrategy($strategy);
}
$list[$index] = ModelEntry::toAclEntry($entry, $this);
return $this;
}
/**
* Delete the ACE of the given list and index.
*
* The list will be re-ordered to have a valid 0..x list.
*
* @param array $list
* @param $index
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function deleteIndex(array &$list, $index)
{
$this->validateIndex($list, $index);
unset($list[$index]);
$this->reorderList($list, $index-1);
return $this;
}
/**
* Validate the index on the given list of ACEs.
*
* @throws \OutOfBoundsException
*
* @param array $list
* @param int $index
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function isWithinBounds(array &$list, $index)
{
// No count()-1, the count is one ahead of index, and could create the next valid entry!
if ($index < 0 or $index > count($list)) {
throw new \OutOfBoundsException(sprintf('The index must be in the interval [0, %d].', count($list)));
}
return $this;
}
/**
* Check the index for existence in the given list.
*
* @throws \OutOfBoundsException
*
* @param array $list
* @param $index
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function validateIndex(array &$list, $index)
{
if (!isset($list[$index])) {
throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
}
return $this;
}
/**
* Validate the given field to be present.
*
* @throws \InvalidArgumentException
*
* @param array $list
* @param string $field
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function validateField(array &$list, $field)
{
if (!isset($list[$field])) {
throw new \InvalidArgumentException(sprintf('The given field "%s" does not exist.', $field));
}
return $this;
}
/**
* Order the given list to have numeric indexes from 0..x
*
* @param array $list
* @param int $index The right boundary to which the list is valid.
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function reorderList(array &$list, $index)
{
$list = array_merge(
array_slice($list, 0, $index+1), // +1 to get length
array_splice($list, $index+1) // +1 to get first index to re-order
);
return $this;
}
/**
* Create a new ACL Entry.
*
* @param int $mask
* @param int $index
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param string $strategy
* @param bool $granting
* @param string $field
*
* @return \Propel\PropelBundle\Security\Acl\Domain\Entry|\Propel\PropelBundle\Security\Acl\Domain\FieldEntry
*/
protected function createAce($mask, $index, SecurityIdentityInterface $securityIdentity, $strategy = null, $granting = true, $field = null)
{
if (!is_int($mask)) {
throw new \InvalidArgumentException('The given mask is not valid. Please provide an integer.');
}
// Compatibility with default implementation
if (null === $strategy) {
if (true === $granting) {
$strategy = PermissionGrantingStrategy::ALL;
} else {
$strategy = PermissionGrantingStrategy::ANY;
}
}
$model = new ModelEntry();
$model
->setAceOrder($index)
->setMask($mask)
->setGrantingStrategy($strategy)
->setGranting($granting)
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($securityIdentity))
;
if (null !== $field) {
$model->setFieldName($field);
return new FieldEntry($model, $this);
}
return new Entry($model, $this);
}
}

View file

@ -0,0 +1,340 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\Propel;
use Propel\Runtime\ServiceContainer\ServiceContainerInterface;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Propel\PropelBundle\Model\Acl\Map\EntryTableMap;
use Propel\PropelBundle\Model\Acl\EntryQuery;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Propel\PropelBundle\Model\Acl\ObjectIdentity;
use Propel\PropelBundle\Model\Acl\ObjectIdentityQuery;
use Propel\PropelBundle\Security\Acl\Domain\Acl;
use Propel\PropelBundle\Security\Acl\Domain\MutableAcl;
use Propel\PropelBundle\Security\Acl\Domain\Entry;
use Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException;
use Symfony\Component\Security\Acl\Exception\Exception as AclException;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
use Symfony\Component\Security\Acl\Model\FieldEntryInterface;
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
use Symfony\Component\Security\Acl\Model\AclCacheInterface;
use Symfony\Component\Security\Acl\Model\MutableAclInterface;
use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
/**
* An implementation of the MutableAclProviderInterface using Propel ORM.
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class MutableAclProvider extends AclProvider implements MutableAclProviderInterface
{
/**
* Constructor.
*
* @param \Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param ConnectionInterface $connection
* @param \Symfony\Component\Security\Acl\Model\AclCacheInterface $cache
*/
public function __construct(PermissionGrantingStrategyInterface $permissionGrantingStrategy, ConnectionInterface $connection = null, AclCacheInterface $cache = null)
{
// @codeCoverageIgnoreStart
if (null === $connection) {
$connection = Propel::getConnection(EntryTableMap::DATABASE_NAME, ServiceContainerInterface::CONNECTION_WRITE);
}
// @codeCoverageIgnoreEnd
parent::__construct($permissionGrantingStrategy, $connection, $cache);
}
/**
* Creates a new ACL for the given object identity.
*
* @throws \Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException When there already is an ACL for the given object identity.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl
*/
public function createAcl(ObjectIdentityInterface $objectIdentity)
{
$entries = EntryQuery::create()->findByAclIdentity($objectIdentity, array(), $this->connection);
if (count($entries)) {
throw new AclAlreadyExistsException('An ACL for the given object identity already exists, find and update that one.');
}
$objIdentity = ObjectIdentityQuery::create()
->filterByAclObjectIdentity($objectIdentity, $this->connection)
->findOneOrCreate($this->connection)
;
if ($objIdentity->isNew()) {
// This is safe to do, it makes the ID available and does not affect changes to any ACL.
$objIdentity->save($this->connection);
}
return $this->getAcl($entries, $objectIdentity, array(), null, false);
}
/**
* Deletes the ACL for a given object identity.
*
* This will automatically trigger a delete for any child ACLs. If you don't
* want child ACLs to be deleted, you will have to set their parent ACL to null.
*
* @throws \Symfony\Component\Security\Acl\Exception\Exception
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
*
* @return bool
*/
public function deleteAcl(ObjectIdentityInterface $objectIdentity)
{
try {
$objIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($objectIdentity, $this->connection);
if (null === $objIdentity) {
// No object identity, no ACL, so deletion is successful (expected result is given).
return true;
}
$this->connection->beginTransaction();
// Retrieve all class and class-field ACEs, if any.
$aces = EntryQuery::create()->findByAclIdentity($objectIdentity, array(), $this->connection);
if (count($aces)) {
// In case this is the last of its kind, delete the class and class-field ACEs.
$count = ObjectIdentityQuery::create()->filterByClassId($objIdentity->getClassId())->count($this->connection);
if (1 === $count) {
$aces->delete($this->connection);
}
}
/*
* If caching is enabled, retrieve the (grand-)children of this ACL.
* Those will be removed from the cache as well, as their parents do not exist anymore.
*/
if (null !== $this->cache) {
$children = ObjectIdentityQuery::create()->findGrandChildren($objIdentity, $this->connection);
}
// This deletes all object and object-field ACEs, too.
$objIdentity->delete($this->connection);
$this->connection->commit();
if (null !== $this->cache) {
$this->cache->evictFromCacheById($objIdentity->getId());
foreach ($children as $eachChild) {
$this->cache->evictFromCacheById($eachChild->getId());
}
}
return true;
// @codeCoverageIgnoreStart
} catch (Exception $e) {
throw new AclException('An error occurred while deleting the ACL.', 1, $e);
}
// @codeCoverageIgnoreEnd
}
/**
* Persists any changes which were made to the ACL, or any associated access control entries.
*
* Changes to parent ACLs are not persisted.
*
* @throws \Symfony\Component\Security\Acl\Exception\Exception
*
* @param \Symfony\Component\Security\Acl\Model\MutableAclInterface $acl
*
* @return bool
*/
public function updateAcl(MutableAclInterface $acl)
{
if (!$acl instanceof MutableAcl) {
throw new \InvalidArgumentException('The given ACL is not tracked by this provider. Please provide \Propel\PropelBundle\Security\Acl\Domain\MutableAcl only.');
}
try {
$modelEntries = EntryQuery::create()->findByAclIdentity($acl->getObjectIdentity(), array(), $this->connection);
$objectIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($acl->getObjectIdentity(), $this->connection);
$this->connection->beginTransaction();
$keepEntries = array_merge(
$this->persistAcl($acl->getClassAces(), $objectIdentity),
$this->persistAcl($acl->getObjectAces(), $objectIdentity, true)
);
foreach ($acl->getFields() as $eachField) {
$keepEntries = array_merge($keepEntries,
$this->persistAcl($acl->getClassFieldAces($eachField), $objectIdentity),
$this->persistAcl($acl->getObjectFieldAces($eachField), $objectIdentity, true)
);
}
foreach ($modelEntries as $eachEntry) {
if (!in_array($eachEntry->getId(), $keepEntries)) {
$eachEntry->delete($this->connection);
}
}
if (null === $acl->getParentAcl()) {
$objectIdentity
->setParentObjectIdentityId(null)
->save($this->connection)
;
} else {
$objectIdentity
->setParentObjectIdentityId($acl->getParentAcl()->getId())
->save($this->connection)
;
}
$this->connection->commit();
// After successfully committing the transaction, we are good to update the cache.
if (null !== $this->cache) {
$this->cache->evictFromCacheById($objectIdentity->getId());
$this->cache->putInCache($acl);
}
return true;
// @codeCoverageIgnoreStart
} catch (Exception $e) {
$this->connection->rollBack();
throw new AclException('An error occurred while updating the ACL.', 0, $e);
}
// @codeCoverageIgnoreEnd
}
/**
* Persist the given ACEs.
*
* @param array $accessControlEntries
* @param \Propel\PropelBundle\Model\Acl\ObjectIdentity $objectIdentity
* @param bool $object
*
* @return array The IDs of the persisted ACEs.
*/
protected function persistAcl(array $accessControlEntries, ObjectIdentity $objectIdentity, $object = false)
{
$entries = array();
/* @var $eachAce \Symfony\Component\Security\Acl\Model\EntryInterface */
foreach ($accessControlEntries as $order => $eachAce) {
// If the given ACE has never been persisted, create a new one.
if (null === $entry = $this->getPersistedAce($eachAce, $objectIdentity, $object)) {
$entry = ModelEntry::fromAclEntry($eachAce);
}
if (in_array($entry->getId(), $entries)) {
$entry = ModelEntry::fromAclEntry($eachAce);
}
// Apply possible changes from local ACE.
$entry
->setAceOrder($order)
->setAclClass($objectIdentity->getAclClass())
->setMask($eachAce->getMask())
;
if ($eachAce instanceof AuditableEntryInterface) {
if (is_bool($eachAce->isAuditSuccess())) {
$entry->setAuditSuccess($eachAce->isAuditSuccess());
}
if (is_bool($eachAce->isAuditFailure())) {
$entry->setAuditFailure($eachAce->isAuditFailure());
}
}
if (true === $object) {
$entry->setObjectIdentity($objectIdentity);
}
$entry->save($this->connection);
$entries[] = $entry->getId();
}
return $entries;
}
/**
* Retrieve the persisted model for the given ACE.
*
* If none is given, null is returned.
*
* @param \Symfony\Component\Security\Acl\Model\EntryInterface $ace
*
* @return \Propel\PropelBundle\Model\Acl\Entry|null
*/
protected function getPersistedAce(EntryInterface $ace, ObjectIdentity $objectIdentity, $object = false)
{
if (null !== $ace->getId() and null !== $entry = EntryQuery::create()->findPk($ace->getId(), $this->connection)) {
$entry->reload(true, $this->connection);
return $entry;
}
/*
* The id is not set, but there may be an ACE in the database.
*
* This happens if the ACL has created new ACEs, but was not reloaded.
* We try to retrieve one by the unique key.
*/
$ukQuery = EntryQuery::create()
->filterByAclClass($objectIdentity->getAclClass($this->connection))
->filterBySecurityIdentity(SecurityIdentity::fromAclIdentity($ace->getSecurityIdentity(), $this->connection))
;
if (true === $object) {
$ukQuery->filterByObjectIdentity($objectIdentity);
} else {
$ukQuery->filterByObjectIdentityId(null, Criteria::ISNULL);
}
if ($ace instanceof FieldEntryInterface) {
$ukQuery->filterByFieldName($ace->getField());
} else {
$ukQuery->filterByFieldName(null, Criteria::ISNULL);
}
return $ukQuery->findOne($this->connection);
}
/**
* Get an ACL for this provider.
*
* @param ObjectCollection $collection
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl
*/
protected function getAcl(ObjectCollection $collection, ObjectIdentityInterface $objectIdentity, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true)
{
return new MutableAcl($collection, $objectIdentity, $this->permissionGrantingStrategy, $loadedSecurityIdentities, $parentAcl, $inherited, $this->connection);
}
}

View file

@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
/**
* Provides easy to use provisioning for Propel model users.
*
* @author William DURAND <william.durand1@gmail.com>
*/
class PropelUserProvider implements UserProviderInterface
{
/**
* A Model class name.
*
* @var string
*/
protected $class;
/**
* A Query class name.
*
* @var string
*/
protected $queryClass;
/**
* A property to use to retrieve the user.
*
* @var string
*/
protected $property;
/**
* Default constructor
*
* @param string $class The User model class.
* @param string|null $property The property to use to retrieve a user.
*/
public function __construct($class, $property = null)
{
$this->class = $class;
$this->queryClass = $class.'Query';
$this->property = $property;
}
/**
* {@inheritdoc}
*/
public function loadUserByUsername($username)
{
$queryClass = $this->queryClass;
$query = $queryClass::create();
if (null !== $this->property) {
$filter = 'filterBy'.ucfirst($this->property);
$query->$filter($username);
} else {
$query->filterByUsername($username);
}
if (null === $user = $query->findOne()) {
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
}
return $user;
}
/**
* {@inheritdoc}
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof $this->class) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
$queryClass = $this->queryClass;
return $queryClass::create()->findPk($user->getPrimaryKey());
}
/**
* {@inheritdoc}
*/
public function supportsClass($class)
{
return $class === $this->class;
}
}

75
Service/SchemaLocator.php Normal file
View file

@ -0,0 +1,75 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Service;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
class SchemaLocator
{
protected $fileLocator;
public function __construct(FileLocatorInterface $fileLocator)
{
$this->fileLocator = $fileLocator;
}
public function locateFromBundles(array $bundles)
{
$schemas = array();
foreach ($bundles as $bundle) {
$schemas = array_merge($schemas, $this->locateFromBundle($bundle));
}
return $schemas;
}
/**
* @param \Symfony\Component\HttpKernel\Bundle\BundleInterface
*/
public function locateFromBundle(BundleInterface $bundle)
{
$finalSchemas = array();
if (is_dir($dir = $bundle->getPath().'/Resources/config')) {
$finder = new Finder();
$schemas = $finder->files()->name('*schema.xml')->followLinks()->in($dir);
if (iterator_count($schemas)) {
foreach ($schemas as $schema) {
$logicalName = $this->transformToLogicalName($schema, $bundle);
$finalSchema = new \SplFileInfo($this->fileLocator->locate($logicalName));
$finalSchemas[(string) $finalSchema] = array($bundle, $finalSchema);
}
}
}
return $finalSchemas;
}
/**
* @param \SplFileInfo $schema
* @param BundleInterface $bundle
* @return string
*/
protected function transformToLogicalName(\SplFileInfo $schema, BundleInterface $bundle)
{
$schemaPath = str_replace(
$bundle->getPath(). DIRECTORY_SEPARATOR . 'Resources' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR,
'',
$schema->getRealPath()
);
return sprintf('@%s/Resources/config/%s', $bundle->getName(), $schemaPath);
}
}

104
Tests/AclTestCase.php Normal file
View file

@ -0,0 +1,104 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests;
use Propel\Generator\Util\QuickBuilder;
use Propel\PropelBundle\Model\Acl\AclClass;
use Propel\PropelBundle\Model\Acl\Entry;
use Propel\PropelBundle\Model\Acl\ObjectIdentity as ModelObjectIdentity;
use Propel\PropelBundle\Security\Acl\MutableAclProvider;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
/**
* AclTestCase
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AclTestCase extends TestCase
{
protected $con = null;
protected $cache = null;
public function setUp()
{
parent::setUp();
$schema = file_get_contents(__DIR__.'/../Resources/acl_schema.xml');
if (!class_exists('Propel\PropelBundle\Model\Acl\Map\AclClassTableMap')) {
$classTargets = array('tablemap', 'object', 'query');
} else {
$classTargets = array();
}
$builder = new QuickBuilder();
$builder->setSchema($schema);
$this->con = $builder->build($dsn = null, $user = null, $pass = null, $adapter = null, $classTargets);
}
/**
* @return \Propel\PropelBundle\Model\Acl\ObjectIdentity
*/
protected function createModelObjectIdentity($identifier)
{
$aclClass = $this->getAclClass();
$objIdentity = new ModelObjectIdentity();
$this->assertTrue((bool) $objIdentity
->setAclClass($aclClass)
->setIdentifier($identifier)
->save($this->con)
);
return $objIdentity;
}
protected function createEntry()
{
$entry = new Entry();
$entry
->setAuditSuccess(false)
->setAuditFailure(false)
->setMask(64)
->setGranting(true)
->setGrantingStrategy('all')
->setAceOrder(0)
;
return $entry;
}
protected function getAclClass()
{
return AclClass::fromAclObjectIdentity($this->getAclObjectIdentity(), $this->con);
}
protected function getAclProvider()
{
return new MutableAclProvider(new PermissionGrantingStrategy(), $this->con, $this->cache);
}
protected function getAclObjectIdentity($identifier = 1)
{
return new ObjectIdentity($identifier, 'Propel\PropelBundle\Tests\Fixtures\Model\Book');
}
protected function getRoleSecurityIdentity($role = 'ROLE_USER')
{
return new RoleSecurityIdentity(new Role($role));
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests\DataFixtures\Dumper;
use Propel\Runtime\Propel;
use Propel\PropelBundle\Tests\DataFixtures\TestCase;
use Propel\PropelBundle\DataFixtures\Dumper\YamlDataDumper;
/**
* @author William Durand <william.durand1@gmail.com>
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class YamlDataDumperTest extends TestCase
{
public function testYamlDump()
{
$author = new \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthor();
$author->setName('A famous one')->save($this->con);
$complementary = new \stdClass();
$complementary->first_word_date = '2012-01-01';
$book = new \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBook();
$book
->setName('An important one')
->setAuthorId(1)
->setComplementaryInfos($complementary)
->save($this->con)
;
$filename = $this->getTempFile();
$loader = new YamlDataDumper(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->dump($filename);
$expected = <<<YAML
\Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthor:
CoolBookAuthor_1:
id: '1'
name: 'A famous one'
\Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBook:
CoolBook_1:
id: '1'
name: 'An important one'
author_id: CoolBookAuthor_1
complementary_infos: !!php/object:O:8:"stdClass":1:{s:15:"first_word_date";s:10:"2012-01-01";}
YAML;
$result = file_get_contents($filename);
$this->assertEquals($expected, $result);
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests\DataFixtures\Loader;
use Propel\Runtime\Propel;
use Propel\PropelBundle\Tests\DataFixtures\TestCase;
use Propel\PropelBundle\DataFixtures\Loader\XmlDataLoader;
/**
* @author William Durand <william.durand1@gmail.com>
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class XmlDataLoaderTest extends TestCase
{
public function testXmlLoad()
{
$fixtures = <<<XML
<Fixtures>
<CoolBookAuthor Namespace="Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader">
<CoolBookAuthor_1 id="1" name="A famous one" />
</CoolBookAuthor>
<CoolBook Namespace="Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader">
<CoolBook_1 id="1" name="An important one" author_id="CoolBookAuthor_1" />
</CoolBook>
</Fixtures>
XML;
$filename = $this->getTempFile($fixtures);
$loader = new XmlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename), 'default');
$books = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookQuery::create()->find($this->con);
$this->assertCount(1, $books);
$book = $books[0];
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthor', $book->getCoolBookAuthor());
}
}

View file

@ -0,0 +1,467 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests\DataFixtures\Loader;
use Propel\Generator\Util\QuickBuilder;
use Propel\Runtime\Propel;
use Propel\PropelBundle\Tests\DataFixtures\TestCase;
use Propel\PropelBundle\DataFixtures\Loader\YamlDataLoader;
/**
* @author William Durand <william.durand1@gmail.com>
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class YamlDataLoaderTest extends TestCase
{
public function testYamlLoadOneToMany()
{
$fixtures = <<<YAML
\Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthor:
CoolBookAuthor_1:
id: '1'
name: 'A famous one'
\Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBook:
CoolBook_1:
id: '1'
name: 'An important one'
author_id: CoolBookAuthor_1
YAML;
$filename = $this->getTempFile($fixtures);
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename), 'default');
$books = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookQuery::create()->find($this->con);
$this->assertCount(1, $books);
$book = $books[0];
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthor', $book->getCoolBookAuthor());
}
public function testLoadSelfReferencing()
{
$fixtures = <<<YAML
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthor:
CoolBookAuthor_1:
id: '1'
name: 'to be announced'
CoolBookAuthor_2:
id: CoolBookAuthor_1
name: 'A famous one'
YAML;
$filename = $this->getTempFile($fixtures);
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename), 'default');
$books = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookQuery::create()->find($this->con);
$this->assertCount(0, $books);
$authors = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthorQuery::create()->find($this->con);
$this->assertCount(1, $authors);
$author = $authors[0];
$this->assertEquals('A famous one', $author->getName());
}
public function testLoaderWithPhp()
{
$fixtures = <<<YAML
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthor:
CoolBookAuthor_1:
id: '1'
name: <?php echo "to be announced"; ?>
YAML;
$filename = $this->getTempFile($fixtures);
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename), 'default');
$books = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookQuery::create()->find($this->con);
$this->assertCount(0, $books);
$authors = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthorQuery::create()->find($this->con);
$this->assertCount(1, $authors);
$author = $authors[0];
$this->assertEquals('to be announced', $author->getName());
}
public function testLoadWithoutFaker()
{
$fixtures = <<<YAML
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthor:
CoolBookAuthor_1:
id: '1'
name: <?php echo \$faker('word'); ?>
YAML;
$filename = $this->getTempFile($fixtures);
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename), 'default');
$books = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookQuery::create()->find($this->con);
$this->assertCount(0, $books);
$authors = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookAuthorQuery::create()->find($this->con);
$this->assertCount(1, $authors);
$author = $authors[0];
$this->assertEquals('word', $author->getName());
}
public function testLoadWithFaker()
{
if (!class_exists('Faker\Factory')) {
$this->markTestSkipped('Faker is mandatory');
}
$fixtures = <<<YAML
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBook:
CoolBook_1:
id: '1'
name: <?php \$faker('word'); ?>
description: <?php \$faker('sentence'); ?>
YAML;
$filename = $this->getTempFile($fixtures);
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array(), \Faker\Factory::create());
$loader->load(array($filename), 'default');
$books = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBookQuery::create()->find($this->con);
$this->assertCount(1, $books);
$book = $books[0];
$this->assertNotNull($book->getName());
$this->assertNotEquals('null', strtolower($book->getName()));
$this->assertRegexp('#[a-z]+#', $book->getName());
$this->assertNotNull($book->getDescription());
$this->assertNotEquals('null', strtolower($book->getDescription()));
$this->assertRegexp('#[\w ]+#', $book->getDescription());
}
public function testYamlLoadManyToMany()
{
$schema = <<<XML
<database name="default" package="vendor.bundles.Propel.PropelBundle.Tests.Fixtures.DataFixtures.Loader" namespace="Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader" defaultIdMethod="native">
<table name="table_book" phpName="YamlManyToManyBook">
<column name="id" type="integer" primaryKey="true" />
<column name="name" type="varchar" size="255" />
</table>
<table name="table_author" phpName="YamlManyToManyAuthor">
<column name="id" type="integer" primaryKey="true" />
<column name="name" type="varchar" size="255" />
</table>
<table name="table_book_author" phpName="YamlManyToManyBookAuthor" isCrossRef="true">
<column name="book_id" type="integer" required="true" primaryKey="true" />
<column name="author_id" type="integer" required="true" primaryKey="true" />
<foreign-key foreignTable="table_book" phpName="Book" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="book_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="table_author" phpName="Author" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="author_id" foreign="id" />
</foreign-key>
</table>
</database>
XML;
$fixtures = <<<YAML
\Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyBook:
Book_1:
id: 1
name: 'An important one'
Book_2:
id: 2
name: 'Les misérables'
\Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyAuthor:
Author_1:
id: 1
name: 'A famous one'
Author_2:
id: 2
name: 'Victor Hugo'
table_book_authors: [ Book_2 ]
\Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyBookAuthor:
BookAuthor_1:
book_id: Book_1
author_id: Author_1
YAML;
$filename = $this->getTempFile($fixtures);
$builder = new QuickBuilder();
$builder->setSchema($schema);
$con = $builder->build();
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename), 'default');
$books = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyBookQuery::create()->find($con);
$this->assertCount(2, $books);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyBook', $books[0]);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyBook', $books[1]);
$authors = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyAuthorQuery::create()->find($con);;
$this->assertCount(2, $authors);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyAuthor', $authors[0]);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyAuthor', $authors[1]);
$bookAuthors = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyBookAuthorQuery::create()->find($con);;
$this->assertCount(2, $bookAuthors);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyBookAuthor', $bookAuthors[0]);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyBookAuthor', $bookAuthors[1]);
$this->assertEquals('Victor Hugo', $authors[1]->getName());
$this->assertTrue($authors[1]->getBooks()->contains($books[1]));
$this->assertEquals('Les misérables', $authors[1]->getBooks()->get(0)->getName());
}
public function testYamlLoadManyToManyMultipleFiles()
{
$schema = <<<XML
<database name="default" package="vendor.bundles.Propel.PropelBundle.Tests.Fixtures.DataFixtures.Loader" namespace="Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader" defaultIdMethod="native">
<table name="table_book_multiple" phpName="YamlManyToManyMultipleFilesBook">
<column name="id" type="integer" primaryKey="true" />
<column name="name" type="varchar" size="255" />
</table>
<table name="table_author_multiple" phpName="YamlManyToManyMultipleFilesAuthor">
<column name="id" type="integer" primaryKey="true" />
<column name="name" type="varchar" size="255" />
</table>
<table name="table_book_author_multiple" phpName="YamlManyToManyMultipleFilesBookAuthor" isCrossRef="true">
<column name="book_id" type="integer" required="true" primaryKey="true" />
<column name="author_id" type="integer" required="true" primaryKey="true" />
<foreign-key foreignTable="table_book_multiple" phpName="Book" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="book_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="table_author_multiple" phpName="Author" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="author_id" foreign="id" />
</foreign-key>
</table>
</database>
XML;
$fixtures1 = <<<YAML
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesBook:
Book_2:
id: 2
name: 'Les misérables'
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesAuthor:
Author_1:
id: 1
name: 'A famous one'
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesBookAuthor:
BookAuthor_1:
book_id: Book_1
author_id: Author_1
YAML;
$fixtures2 = <<<YAML
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesBook:
Book_1:
id: 1
name: 'An important one'
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesAuthor:
Author_2:
id: 2
name: 'Victor Hugo'
table_book_author_multiples: [ Book_2 ]
YAML;
$filename1 = $this->getTempFile($fixtures1);
$filename2 = $this->getTempFile($fixtures2);
$builder = new QuickBuilder();
$builder->setSchema($schema);
$con = $builder->build();
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename1, $filename2), 'default');
$books = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesBookQuery::create()->find($con);
$this->assertCount(2, $books);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesBook', $books[0]);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesBook', $books[1]);
$authors = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesAuthorQuery::create()->find($con);
$this->assertCount(2, $authors);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesAuthor', $authors[0]);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesAuthor', $authors[1]);
$bookAuthors = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesBookAuthorQuery::create()->find($con);
$this->assertCount(2, $bookAuthors);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesBookAuthor', $bookAuthors[0]);
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlManyToManyMultipleFilesBookAuthor', $bookAuthors[1]);
$this->assertEquals('Victor Hugo', $authors[1]->getName());
$this->assertTrue($authors[1]->getBooks()->contains($books[1]));
$this->assertEquals('Les misérables', $authors[1]->getBooks()->get(0)->getName());
}
public function testLoadWithInheritedRelationship()
{
$schema = <<<XML
<database name="default" package="vendor.bundles.Propel.PropelBundle.Tests.Fixtures.DataFixtures.Loader" namespace="Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader" defaultIdMethod="native">
<table name="table_book_inherited_relationship" phpName="YamlInheritedRelationshipBook">
<column name="id" type="integer" primaryKey="true" autoIncrement="true" />
<column name="name" type="varchar" size="255" />
<column name="author_id" type="integer" required="true" />
<foreign-key foreignTable="table_author_inherited_relationship" phpName="Author">
<reference local="author_id" foreign="id" />
</foreign-key>
</table>
<table name="table_author_inherited_relationship" phpName="YamlInheritedRelationshipAuthor">
<column name="id" type="integer" primaryKey="true" autoIncrement="true" />
<column name="name" type="varchar" size="255" />
</table>
<table name="table_nobelized_author_inherited_relationship" phpName="YamlInheritedRelationshipNobelizedAuthor">
<column name="nobel_year" type="integer" />
<behavior name="concrete_inheritance">
<parameter name="extends" value="table_author_inherited_relationship" />
</behavior>
</table>
</database>
XML;
$fixtures = <<<YAML
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlInheritedRelationshipNobelizedAuthor:
NobelizedAuthor_1:
nobel_year: 2012
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlInheritedRelationshipBook:
Book_1:
name: 'Supplice du santal'
author_id: NobelizedAuthor_1
YAML;
$filename = $this->getTempFile($fixtures);
$builder = new QuickBuilder();
$builder->setSchema($schema);
$con = $builder->build();
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename), 'default');
$books = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlInheritedRelationshipBookQuery::create()->find($con);
$this->assertCount(1, $books);
$book = $books[0];
$author = $book->getAuthor();
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlInheritedRelationshipAuthor', $author);
}
public function testLoadArrayToObjectType()
{
$schema = <<<XML
<database name="default" package="vendor.bundles.Propel.PropelBundle.Tests.Fixtures.DataFixtures.Loader" namespace="Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader" defaultIdMethod="native">
<table name="table_book_with_object" phpName="YamlBookWithObject">
<column name="id" type="integer" primaryKey="true" />
<column name="name" type="varchar" size="255" />
<column name="options" type="object" />
</table>
</database>
XML;
$fixtures = <<<YAML
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlBookWithObject:
book1:
name: my book
options: {opt1: 2012, opt2: 140, inner: {subOpt: 123}}
YAML;
$filename = $this->getTempFile($fixtures);
$builder = new QuickBuilder();
$builder->setSchema($schema);
$con = $builder->build();
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename), 'default');
$book = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlBookWithObjectQuery::create(null)->findOne($con);
$this->assertInstanceOf('\Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlBookWithObject', $book);
$this->assertEquals(array('opt1' => 2012, 'opt2' => 140, 'inner' => array('subOpt' => 123)), $book->getOptions());
}
public function testLoadDelegatedOnPrimaryKey()
{
$schema = <<<XML
<database name="default" package="vendor.bundles.Propel.PropelBundle.Tests.Fixtures.DataFixtures.Loader" namespace="Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader" defaultIdMethod="native">
<table name="yaml_delegate_on_primary_key_person" phpName="YamlDelegateOnPrimaryKeyPerson">
<column name="id" type="integer" primaryKey="true" autoIncrement="true" />
<column name="name" type="varchar" size="255" />
</table>
<table name="yaml_delegate_on_primary_key_author" phpName="YamlDelegateOnPrimaryKeyAuthor">
<column name="id" type="integer" primaryKey="true" autoIncrement="false" />
<column name="count_books" type="integer" defaultValue="0" required="true" />
<behavior name="delegate">
<parameter name="to" value="yaml_delegate_on_primary_key_person" />
</behavior>
<foreign-key foreignTable="yaml_delegate_on_primary_key_person" onDelete="RESTRICT" onUpdate="CASCADE">
<reference local="id" foreign="id" />
</foreign-key>
</table>
</database>
XML;
$fixtures = <<<YAML
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlDelegateOnPrimaryKeyPerson:
yaml_delegate_on_primary_key_person_1:
name: "Some Persons Name"
Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlDelegateOnPrimaryKeyAuthor:
yaml_delegate_on_primary_key_author_1:
id: yaml_delegate_on_primary_key_person_1
count_books: 7
YAML;
$filename = $this->getTempFile($fixtures);
$builder = new QuickBuilder();
$builder->setSchema($schema);
$con = $builder->build();
$loader = new YamlDataLoader(__DIR__.'/../../Fixtures/DataFixtures/Loader', array());
$loader->load(array($filename), 'default');
$authors = \Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlDelegateOnPrimaryKeyAuthorQuery::create()->find($con);
$this->assertCount(1, $authors);
$author = $authors[0];
$person = $author->getYamlDelegateOnPrimaryKeyPerson();
$this->assertInstanceOf('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\YamlDelegateOnPrimaryKeyPerson', $person);
}
}

View file

@ -0,0 +1,106 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests\DataFixtures;
use Propel\Generator\Util\QuickBuilder;
use Propel\Runtime\Propel;
use Propel\PropelBundle\Tests\TestCase as BaseTestCase;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class TestCase extends BaseTestCase
{
/**
* @var \PropelPDO
*/
protected $con;
/**
* The list of created temp files to be removed.
*
* @var array
*/
protected $tmpFiles = array();
protected function setUp()
{
parent::setUp();
if (!class_exists('Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader\CoolBook')) {
$schema = <<<XML
<database name="default" package="vendor.bundles.Propel.PropelBundle.Tests.Fixtures.DataFixtures.Loader" namespace="Propel\PropelBundle\Tests\Fixtures\DataFixtures\Loader" defaultIdMethod="native">
<table name="cool_book">
<column name="id" type="integer" primaryKey="true" />
<column name="name" type="varchar" size="255" />
<column name="description" type="varchar" />
<column name="author_id" type="integer" required="false" defaultValue="null" />
<column name="complementary_infos" required="false" type="object" description="An object column" />
<foreign-key foreignTable="cool_book_author" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="author_id" foreign="id" />
</foreign-key>
</table>
<table name="cool_book_author">
<column name="id" type="integer" primaryKey="true" />
<column name="name" type="varchar" size="255" />
</table>
</database>
XML;
QuickBuilder::buildSchema($schema);
}
$this->con = Propel::getServiceContainer()->getConnection('default');
$this->con->beginTransaction();
}
protected function tearDown()
{
foreach ($this->tmpFiles as $eachFile) {
@unlink($eachFile);
}
$this->tmpFiles = array();
// Only commit if the transaction hasn't failed.
// This is because tearDown() is also executed on a failed tests,
// and we don't want to call ConnectionInterface::commit() in that case
// since it will trigger an exception on its own
// ('Cannot commit because a nested transaction was rolled back')
if (null !== $this->con) {
if ($this->con->isCommitable()) {
$this->con->commit();
}
$this->con = null;
}
}
/**
* Return the name of a created temporary file containing the given content.
*
* @param string $content
*
* @return string
*/
protected function getTempFile($content = '')
{
$filename = tempnam(sys_get_temp_dir(), 'propelbundle-datafixtures-test');
@unlink($filename);
file_put_contents($filename, $content);
$this->tmpFiles[] = $filename;
return $filename;
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests\Fixtures\Acl;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\AclCacheInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
class ArrayCache implements AclCacheInterface
{
public $content = array();
public function evictFromCacheById($primaryKey)
{
if (isset($this->content[$primaryKey])) {
unset($this->content[$primaryKey]);
}
}
public function evictFromCacheByIdentity(ObjectIdentityInterface $oid)
{
// Propel ACL does not make use of those.
}
public function getFromCacheById($primaryKey)
{
if (isset($this->content[$primaryKey])) {
return $this->content[$primaryKey];
}
return null;
}
public function getFromCacheByIdentity(ObjectIdentityInterface $oid)
{
// Propel ACL does not make use of those.
}
public function putInCache(AclInterface $acl)
{
if (null === $acl->getId()) {
throw new \InvalidArgumentException('The given ACL does not have an ID.');
}
$this->content[$acl->getId()] = $acl;
}
public function clearCache()
{
$this->content = array();
}
}

61
Tests/Fixtures/Column.php Normal file
View file

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Generator\Model\PropelTypes;
class Column
{
private $name;
private $type;
public function __construct($name, $type)
{
$this->name = $name;
$this->type = $type;
}
public function getType()
{
return $this->type;
}
public function isText()
{
if (!$this->type) {
return false;
}
switch ($this->type) {
case PropelTypes::CHAR:
case PropelTypes::VARCHAR:
case PropelTypes::LONGVARCHAR:
case PropelTypes::BLOB:
case PropelTypes::CLOB:
case PropelTypes::CLOB_EMU:
return true;
}
return false;
}
public function getSize()
{
return $this->isText() ? 255 : 0;
}
public function isNotNull()
{
return ('id' === $this->name);
}
}

109
Tests/Fixtures/Item.php Normal file
View file

@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Propel\Runtime\Connection\ConnectionInterface;
class Item implements ActiveRecordInterface
{
private $id;
private $value;
private $groupName;
private $price;
public function __construct($id = null, $value = null, $groupName = null, $price = null)
{
$this->id = $id;
$this->value = $value;
$this->groupName = $groupName;
$this->price = $price;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getValue()
{
return $this->value;
}
public function getGroupName()
{
return $this->groupName;
}
public function getPrice()
{
return $this->price;
}
public function getPrimaryKey()
{
return $this->getId();
}
public function setPrimaryKey($primaryKey)
{
$this->setId($primaryKey);
}
public function isModified()
{
return false;
}
public function isColumnModified($col)
{
return false;
}
public function isNew()
{
return false;
}
public function setNew($b)
{
}
public function resetModified()
{
}
public function isDeleted()
{
return false;
}
public function setDeleted($b)
{
}
public function delete(ConnectionInterface $con = null)
{
}
public function save(ConnectionInterface $con = null)
{
}
}

View file

@ -0,0 +1,100 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Runtime\Map\ColumnMap;
use Propel\Runtime\Map\RelationMap;
use Propel\Runtime\Map\TableMap;
use Propel\Generator\Model\PropelTypes;
class ItemQuery
{
private $map = array(
'id' => PropelTypes::INTEGER,
'value' => PropelTypes::VARCHAR,
'price' => PropelTypes::FLOAT,
'is_active' => PropelTypes::BOOLEAN,
'enabled' => PropelTypes::BOOLEAN_EMU,
'updated_at' => PropelTypes::TIMESTAMP,
'updated_at' => PropelTypes::TIMESTAMP,
'updated_at' => PropelTypes::TIMESTAMP,
'updated_at' => PropelTypes::TIMESTAMP,
);
public function getTableMap()
{
// Allows to define methods in this class
// to avoid a lot of mock classes
return $this;
}
public function getPrimaryKeys()
{
$cm = new ColumnMap('id', new TableMap());
$cm->setType('INTEGER');
return array('id' => $cm);
}
/**
* Method from the TableMap API
*/
public function hasColumn($column)
{
return in_array($column, array_keys($this->map));
}
/**
* Method from the TableMap API
*/
public function getColumn($column)
{
if ($this->hasColumn($column)) {
return new Column($column, $this->map[$column]);
}
return null;
}
/**
* Method from the TableMap API
*/
public function getRelations()
{
// table maps
$authorTable = new TableMap();
$authorTable->setClassName('\Foo\Author');
$resellerTable = new TableMap();
$resellerTable->setClassName('\Foo\Reseller');
// relations
$mainAuthorRelation = new RelationMap('MainAuthor');
$mainAuthorRelation->setType(RelationMap::MANY_TO_ONE);
$mainAuthorRelation->setForeignTable($authorTable);
$authorRelation = new RelationMap('Author');
$authorRelation->setType(RelationMap::ONE_TO_MANY);
$authorRelation->setForeignTable($authorTable);
$resellerRelation = new RelationMap('Reseller');
$resellerRelation->setType(RelationMap::MANY_TO_MANY);
$resellerRelation->setLocalTable($resellerTable);
return array(
$mainAuthorRelation,
$authorRelation,
$resellerRelation
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,489 @@
<?php
namespace Propel\PropelBundle\Tests\Fixtures\Model\Base;
use \Exception;
use \PDO;
use Propel\PropelBundle\Tests\Fixtures\Model\Book as ChildBook;
use Propel\PropelBundle\Tests\Fixtures\Model\BookQuery as ChildBookQuery;
use Propel\PropelBundle\Tests\Fixtures\Model\Map\BookTableMap;
use Propel\Runtime\Propel;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Propel\Runtime\ActiveQuery\ModelJoin;
use Propel\Runtime\Collection\Collection;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\Exception\PropelException;
/**
* Base class that represents a query for the 'book' table.
*
*
*
* @method ChildBookQuery orderById($order = Criteria::ASC) Order by the id column
* @method ChildBookQuery orderByTitle($order = Criteria::ASC) Order by the title column
* @method ChildBookQuery orderByIsbn($order = Criteria::ASC) Order by the ISBN column
* @method ChildBookQuery orderByAuthorId($order = Criteria::ASC) Order by the author_id column
*
* @method ChildBookQuery groupById() Group by the id column
* @method ChildBookQuery groupByTitle() Group by the title column
* @method ChildBookQuery groupByIsbn() Group by the ISBN column
* @method ChildBookQuery groupByAuthorId() Group by the author_id column
*
* @method ChildBookQuery leftJoin($relation) Adds a LEFT JOIN clause to the query
* @method ChildBookQuery rightJoin($relation) Adds a RIGHT JOIN clause to the query
* @method ChildBookQuery innerJoin($relation) Adds a INNER JOIN clause to the query
*
* @method ChildBook findOne(ConnectionInterface $con = null) Return the first ChildBook matching the query
* @method ChildBook findOneOrCreate(ConnectionInterface $con = null) Return the first ChildBook matching the query, or a new ChildBook object populated from the query conditions when no match is found
*
* @method ChildBook findOneById(int $id) Return the first ChildBook filtered by the id column
* @method ChildBook findOneByTitle(string $title) Return the first ChildBook filtered by the title column
* @method ChildBook findOneByIsbn(string $ISBN) Return the first ChildBook filtered by the ISBN column
* @method ChildBook findOneByAuthorId(int $author_id) Return the first ChildBook filtered by the author_id column
*
* @method array findById(int $id) Return ChildBook objects filtered by the id column
* @method array findByTitle(string $title) Return ChildBook objects filtered by the title column
* @method array findByIsbn(string $ISBN) Return ChildBook objects filtered by the ISBN column
* @method array findByAuthorId(int $author_id) Return ChildBook objects filtered by the author_id column
*
*/
abstract class BookQuery extends ModelCriteria
{
/**
* Initializes internal state of \Acme\DemoBundle\Model\Base\BookQuery object.
*
* @param string $dbName The database name
* @param string $modelName The phpName of a model, e.g. 'Book'
* @param string $modelAlias The alias for the model in this query, e.g. 'b'
*/
public function __construct($dbName = 'default', $modelName = '\\Propel\\PropelBundle\\Tests\\Fixtures\\Model\\Book', $modelAlias = null)
{
parent::__construct($dbName, $modelName, $modelAlias);
}
/**
* Returns a new ChildBookQuery object.
*
* @param string $modelAlias The alias of a model in the query
* @param Criteria $criteria Optional Criteria to build the query from
*
* @return ChildBookQuery
*/
public static function create($modelAlias = null, $criteria = null)
{
if ($criteria instanceof \Propel\PropelBundle\Tests\Fixtures\Model\BookQuery) {
return $criteria;
}
$query = new \Propel\PropelBundle\Tests\Fixtures\Model\BookQuery();
if (null !== $modelAlias) {
$query->setModelAlias($modelAlias);
}
if ($criteria instanceof Criteria) {
$query->mergeWith($criteria);
}
return $query;
}
/**
* Find object by primary key.
* Propel uses the instance pool to skip the database if the object exists.
* Go fast if the query is untouched.
*
* <code>
* $obj = $c->findPk(12, $con);
* </code>
*
* @param mixed $key Primary key to use for the query
* @param ConnectionInterface $con an optional connection object
*
* @return ChildBook|array|mixed the result, formatted by the current formatter
*/
public function findPk($key, ConnectionInterface $con = null)
{
if ($key === null) {
return null;
}
if ((null !== ($obj = BookTableMap::getInstanceFromPool((string) $key))) && !$this->formatter) {
// the object is already in the instance pool
return $obj;
}
if ($con === null) {
$con = Propel::getServiceContainer()->getReadConnection(BookTableMap::DATABASE_NAME);
}
$this->basePreSelect($con);
if ($this->formatter || $this->modelAlias || $this->with || $this->select
|| $this->selectColumns || $this->asColumns || $this->selectModifiers
|| $this->map || $this->having || $this->joins) {
return $this->findPkComplex($key, $con);
} else {
return $this->findPkSimple($key, $con);
}
}
/**
* Find object by primary key using raw SQL to go fast.
* Bypass doSelect() and the object formatter by using generated code.
*
* @param mixed $key Primary key to use for the query
* @param ConnectionInterface $con A connection object
*
* @return ChildBook A model object, or null if the key is not found
*/
protected function findPkSimple($key, $con)
{
$sql = 'SELECT ID, TITLE, ISBN, AUTHOR_ID FROM book WHERE ID = :p0';
try {
$stmt = $con->prepare($sql);
$stmt->bindValue(':p0', $key, PDO::PARAM_INT);
$stmt->execute();
} catch (Exception $e) {
Propel::log($e->getMessage(), Propel::LOG_ERR);
throw new PropelException(sprintf('Unable to execute SELECT statement [%s]', $sql), 0, $e);
}
$obj = null;
if ($row = $stmt->fetch(\PDO::FETCH_NUM)) {
$obj = new ChildBook();
$obj->hydrate($row);
BookTableMap::addInstanceToPool($obj, (string) $key);
}
$stmt->closeCursor();
return $obj;
}
/**
* Find object by primary key.
*
* @param mixed $key Primary key to use for the query
* @param ConnectionInterface $con A connection object
*
* @return ChildBook|array|mixed the result, formatted by the current formatter
*/
protected function findPkComplex($key, $con)
{
// As the query uses a PK condition, no limit(1) is necessary.
$criteria = $this->isKeepQuery() ? clone $this : $this;
$dataFetcher = $criteria
->filterByPrimaryKey($key)
->doSelect($con);
return $criteria->getFormatter()->init($criteria)->formatOne($dataFetcher);
}
/**
* Find objects by primary key
* <code>
* $objs = $c->findPks(array(12, 56, 832), $con);
* </code>
* @param array $keys Primary keys to use for the query
* @param ConnectionInterface $con an optional connection object
*
* @return ObjectCollection|array|mixed the list of results, formatted by the current formatter
*/
public function findPks($keys, ConnectionInterface $con = null)
{
if (null === $con) {
$con = Propel::getServiceContainer()->getReadConnection($this->getDbName());
}
$this->basePreSelect($con);
$criteria = $this->isKeepQuery() ? clone $this : $this;
$dataFetcher = $criteria
->filterByPrimaryKeys($keys)
->doSelect($con);
return $criteria->getFormatter()->init($criteria)->format($dataFetcher);
}
/**
* Filter the query by primary key
*
* @param mixed $key Primary key to use for the query
*
* @return ChildBookQuery The current query, for fluid interface
*/
public function filterByPrimaryKey($key)
{
return $this->addUsingAlias(BookTableMap::ID, $key, Criteria::EQUAL);
}
/**
* Filter the query by a list of primary keys
*
* @param array $keys The list of primary key to use for the query
*
* @return ChildBookQuery The current query, for fluid interface
*/
public function filterByPrimaryKeys($keys)
{
return $this->addUsingAlias(BookTableMap::ID, $keys, Criteria::IN);
}
/**
* Filter the query on the id column
*
* Example usage:
* <code>
* $query->filterById(1234); // WHERE id = 1234
* $query->filterById(array(12, 34)); // WHERE id IN (12, 34)
* $query->filterById(array('min' => 12)); // WHERE id > 12
* </code>
*
* @param mixed $id The value to use as filter.
* Use scalar values for equality.
* Use array values for in_array() equivalent.
* Use associative array('min' => $minValue, 'max' => $maxValue) for intervals.
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return ChildBookQuery The current query, for fluid interface
*/
public function filterById($id = null, $comparison = null)
{
if (is_array($id)) {
$useMinMax = false;
if (isset($id['min'])) {
$this->addUsingAlias(BookTableMap::ID, $id['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($id['max'])) {
$this->addUsingAlias(BookTableMap::ID, $id['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
$comparison = Criteria::IN;
}
}
return $this->addUsingAlias(BookTableMap::ID, $id, $comparison);
}
/**
* Filter the query on the title column
*
* Example usage:
* <code>
* $query->filterByTitle('fooValue'); // WHERE title = 'fooValue'
* $query->filterByTitle('%fooValue%'); // WHERE title LIKE '%fooValue%'
* </code>
*
* @param string $title The value to use as filter.
* Accepts wildcards (* and % trigger a LIKE)
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return ChildBookQuery The current query, for fluid interface
*/
public function filterByTitle($title = null, $comparison = null)
{
if (null === $comparison) {
if (is_array($title)) {
$comparison = Criteria::IN;
} elseif (preg_match('/[\%\*]/', $title)) {
$title = str_replace('*', '%', $title);
$comparison = Criteria::LIKE;
}
}
return $this->addUsingAlias(BookTableMap::TITLE, $title, $comparison);
}
/**
* Filter the query on the ISBN column
*
* Example usage:
* <code>
* $query->filterByIsbn('fooValue'); // WHERE ISBN = 'fooValue'
* $query->filterByIsbn('%fooValue%'); // WHERE ISBN LIKE '%fooValue%'
* </code>
*
* @param string $isbn The value to use as filter.
* Accepts wildcards (* and % trigger a LIKE)
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return ChildBookQuery The current query, for fluid interface
*/
public function filterByIsbn($isbn = null, $comparison = null)
{
if (null === $comparison) {
if (is_array($isbn)) {
$comparison = Criteria::IN;
} elseif (preg_match('/[\%\*]/', $isbn)) {
$isbn = str_replace('*', '%', $isbn);
$comparison = Criteria::LIKE;
}
}
return $this->addUsingAlias(BookTableMap::ISBN, $isbn, $comparison);
}
/**
* Filter the query on the author_id column
*
* Example usage:
* <code>
* $query->filterByAuthorId(1234); // WHERE author_id = 1234
* $query->filterByAuthorId(array(12, 34)); // WHERE author_id IN (12, 34)
* $query->filterByAuthorId(array('min' => 12)); // WHERE author_id > 12
* </code>
*
* @see filterByAuthor()
*
* @param mixed $authorId The value to use as filter.
* Use scalar values for equality.
* Use array values for in_array() equivalent.
* Use associative array('min' => $minValue, 'max' => $maxValue) for intervals.
* @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL
*
* @return ChildBookQuery The current query, for fluid interface
*/
public function filterByAuthorId($authorId = null, $comparison = null)
{
if (is_array($authorId)) {
$useMinMax = false;
if (isset($authorId['min'])) {
$this->addUsingAlias(BookTableMap::AUTHOR_ID, $authorId['min'], Criteria::GREATER_EQUAL);
$useMinMax = true;
}
if (isset($authorId['max'])) {
$this->addUsingAlias(BookTableMap::AUTHOR_ID, $authorId['max'], Criteria::LESS_EQUAL);
$useMinMax = true;
}
if ($useMinMax) {
return $this;
}
if (null === $comparison) {
$comparison = Criteria::IN;
}
}
return $this->addUsingAlias(BookTableMap::AUTHOR_ID, $authorId, $comparison);
}
/**
* Adds a JOIN clause to the query using the Author relation
*
* @param string $relationAlias optional alias for the relation
* @param string $joinType Accepted values are null, 'left join', 'right join', 'inner join'
*
* @return ChildBookQuery The current query, for fluid interface
*/
public function joinAuthor($relationAlias = null, $joinType = Criteria::LEFT_JOIN)
{
$tableMap = $this->getTableMap();
$relationMap = $tableMap->getRelation('Author');
// create a ModelJoin object for this join
$join = new ModelJoin();
$join->setJoinType($joinType);
$join->setRelationMap($relationMap, $this->useAliasInSQL ? $this->getModelAlias() : null, $relationAlias);
if ($previousJoin = $this->getPreviousJoin()) {
$join->setPreviousJoin($previousJoin);
}
// add the ModelJoin to the current object
if ($relationAlias) {
$this->addAlias($relationAlias, $relationMap->getRightTable()->getName());
$this->addJoinObject($join, $relationAlias);
} else {
$this->addJoinObject($join, 'Author');
}
return $this;
}
/**
* Exclude object from result
*
* @param ChildBook $book Object to remove from the list of results
*
* @return ChildBookQuery The current query, for fluid interface
*/
public function prune($book = null)
{
if ($book) {
$this->addUsingAlias(BookTableMap::ID, $book->getId(), Criteria::NOT_EQUAL);
}
return $this;
}
/**
* Deletes all rows from the book table.
*
* @param ConnectionInterface $con the connection to use
* @return int The number of affected rows (if supported by underlying database driver).
*/
public function doDeleteAll(ConnectionInterface $con = null)
{
if (null === $con) {
$con = Propel::getServiceContainer()->getWriteConnection(BookTableMap::DATABASE_NAME);
}
$affectedRows = 0; // initialize var to track total num of affected rows
try {
// use transaction because $criteria could contain info
// for more than one table or we could emulating ON DELETE CASCADE, etc.
$con->beginTransaction();
$affectedRows += parent::doDeleteAll($con);
// Because this db requires some delete cascade/set null emulation, we have to
// clear the cached instance *after* the emulation has happened (since
// instances get re-added by the select statement contained therein).
BookTableMap::clearInstancePool();
BookTableMap::clearRelatedInstancePool();
$con->commit();
} catch (PropelException $e) {
$con->rollBack();
throw $e;
}
return $affectedRows;
}
/**
* Performs a DELETE on the database, given a ChildBook or Criteria object OR a primary key value.
*
* @param mixed $values Criteria or ChildBook object or primary key or array of primary keys
* which is used to create the DELETE statement
* @param ConnectionInterface $con the connection to use
* @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows
* if supported by native driver or if emulated using Propel.
* @throws PropelException Any exceptions caught during processing will be
* rethrown wrapped into a PropelException.
*/
public function delete(ConnectionInterface $con = null)
{
if (null === $con) {
$con = Propel::getServiceContainer()->getWriteConnection(BookTableMap::DATABASE_NAME);
}
$criteria = $this;
// Set the correct dbName
$criteria->setDbName(BookTableMap::DATABASE_NAME);
$affectedRows = 0; // initialize var to track total num of affected rows
try {
// use transaction because $criteria could contain info
// for more than one table or we could emulating ON DELETE CASCADE, etc.
$con->beginTransaction();
BookTableMap::removeInstanceFromPool($criteria);
$affectedRows += ModelCriteria::delete($con);
BookTableMap::clearRelatedInstancePool();
$con->commit();
return $affectedRows;
} catch (PropelException $e) {
$con->rollBack();
throw $e;
}
}
} // BookQuery

View file

@ -0,0 +1,10 @@
<?php
namespace Propel\PropelBundle\Tests\Fixtures\Model;
use Propel\PropelBundle\Tests\Fixtures\Model\Base\Book as BaseBook;
class Book extends BaseBook
{
}

View file

@ -0,0 +1,95 @@
<?php
namespace Propel\PropelBundle\Tests\Fixtures\Model;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\PropelBundle\Tests\Fixtures\Model\Base\BookQuery as BaseBookQuery;
use Propel\PropelBundle\Tests\Fixtures\Model\Book;
/**
* Skeleton subclass for performing query and update operations on the 'book' table.
*
*
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*
*/
class BookQuery extends BaseBookQuery
{
private $bySlug = false;
private $byAuthorSlug = false;
/**
* fake for test
*/
public function findPk($key, ConnectionInterface $con = null)
{
if (1 === $key) {
$book = new Book();
$book->setId(1);
return $book;
}
return null;
}
/**
* fake for test
*/
public function filterByAuthorSlug($slug = null, $comparison = null)
{
if ('my-author' === $slug) {
$this->byAuthorSlug = true;
}
return $this;
}
/**
* fake for test
*/
public function filterBySlug($slug = null, $comparison = null)
{
if ('my-book' == $slug) {
$this->bySlug = true;
}
return $this;
}
/**
* fake for test
*/
public function filterByName($name = null, $comparison = null)
{
throw new \Exception('Test should never call this method');
}
/**
* fake for test
*/
public function findOne(ConnectionInterface $con = null)
{
if (true === $this->bySlug) {
$book = new Book();
$book->setId(1);
$book->setName('My Book');
$book->setSlug('my-book');
return $book;
} elseif (true === $this->byAuthorSlug) {
$book = new Book();
$book->setId(2);
$book->setName('My Kewl Book');
$book->setSlug('my-kewl-book');
return $book;
}
return null;
}
} // BookQuery

View file

@ -0,0 +1,430 @@
<?php
namespace Propel\PropelBundle\Tests\Fixtures\Model\Map;
use Acme\DemoBundle\Model\Book;
use Acme\DemoBundle\Model\BookQuery;
use Propel\Runtime\Propel;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\ActiveQuery\InstancePoolTrait;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\Exception\PropelException;
use Propel\Runtime\Map\RelationMap;
use Propel\Runtime\Map\TableMap;
use Propel\Runtime\Map\TableMapTrait;
/**
* This class defines the structure of the 'book' table.
*
*
*
* This map class is used by Propel to do runtime db structure discovery.
* For example, the createSelectSql() method checks the type of a given column used in an
* ORDER BY clause to know whether it needs to apply SQL to make the ORDER BY case-insensitive
* (i.e. if it's a text column type).
*
*/
class BookTableMap extends TableMap
{
use InstancePoolTrait;
use TableMapTrait;
/**
* The (dot-path) name of this class
*/
const CLASS_NAME = 'src.Acme.DemoBundle.Model.Map.BookTableMap';
/**
* The default database name for this class
*/
const DATABASE_NAME = 'default';
/**
* The table name for this class
*/
const TABLE_NAME = 'book';
/**
* The related Propel class for this table
*/
const OM_CLASS = '\\Propel\\PropelBundle\\Tests\\Fixtures\\Model\\Book';
/**
* A class that can be returned by this tableMap
*/
const CLASS_DEFAULT = 'src.Acme.DemoBundle.Model.Book';
/**
* The total number of columns
*/
const NUM_COLUMNS = 5;
/**
* The number of lazy-loaded columns
*/
const NUM_LAZY_LOAD_COLUMNS = 0;
/**
* The number of columns to hydrate (NUM_COLUMNS - NUM_LAZY_LOAD_COLUMNS)
*/
const NUM_HYDRATE_COLUMNS = 5;
/**
* the column name for the ID field
*/
const ID = 'book.ID';
/**
* the column name for the NAME field
*/
const NAME = 'book.NAME';
/**
* the column name for the SLUG field
*/
const SLUG = 'book.SLUG';
/**
* the column name for the ISBN field
*/
const ISBN = 'book.ISBN';
/**
* the column name for the AUTHOR_ID field
*/
const AUTHOR_ID = 'book.AUTHOR_ID';
/**
* The default string format for model objects of the related table
*/
const DEFAULT_STRING_FORMAT = 'YAML';
/**
* holds an array of fieldnames
*
* first dimension keys are the type constants
* e.g. self::$fieldNames[self::TYPE_PHPNAME][0] = 'Id'
*/
protected static $fieldNames = array (
self::TYPE_PHPNAME => array('Id', 'Name', 'Slug', 'Isbn', 'AuthorId', ),
self::TYPE_CAMELNAME => array('id', 'name', 'slug', 'isbn', 'authorId', ),
self::TYPE_COLNAME => array(BookTableMap::ID, BookTableMap::NAME, BookTableMap::SLUG, BookTableMap::ISBN, BookTableMap::AUTHOR_ID, ),
self::TYPE_RAW_COLNAME => array('ID', 'NAME', 'SLUG', 'ISBN', 'AUTHOR_ID', ),
self::TYPE_FIELDNAME => array('id', 'name', 'slug', 'ISBN', 'author_id', ),
self::TYPE_NUM => array(0, 1, 2, 3, 4, )
);
/**
* holds an array of keys for quick access to the fieldnames array
*
* first dimension keys are the type constants
* e.g. self::$fieldKeys[self::TYPE_PHPNAME]['Id'] = 0
*/
protected static $fieldKeys = array (
self::TYPE_PHPNAME => array('Id' => 0, 'Name' => 1, 'Slug' => 2, 'Isbn' => 3, 'AuthorId' => 4, ),
self::TYPE_CAMELNAME => array('id' => 0, 'name' => 1, 'slug' => 2, 'isbn' => 3, 'authorId' => 4, ),
self::TYPE_COLNAME => array(BookTableMap::ID => 0, BookTableMap::NAME => 1, BookTableMap::SLUG => 2, BookTableMap::ISBN => 3, BookTableMap::AUTHOR_ID => 4, ),
self::TYPE_RAW_COLNAME => array('ID' => 0, 'NAME' => 1, 'SLUG' => 2, 'ISBN' => 3, 'AUTHOR_ID' => 4, ),
self::TYPE_FIELDNAME => array('id' => 0, 'name' => 1, 'slug' => 2, 'ISBN' => 3, 'author_id' => 4, ),
self::TYPE_NUM => array(0, 1, 2, 3, 4, )
);
/**
* Initialize the table attributes and columns
* Relations are not initialized by this method since they are lazy loaded
*
* @return void
* @throws PropelException
*/
public function initialize()
{
// attributes
$this->setName('book');
$this->setPhpName('Book');
$this->setClassName('\\Propel\\PropelBundle\\Tests\\Fixtures\\Model\\Book');
$this->setPackage('src.Acme.DemoBundle.Model');
$this->setUseIdGenerator(true);
// columns
$this->addPrimaryKey('ID', 'Id', 'INTEGER', true, null, null);
$this->addColumn('NAME', 'Name', 'VARCHAR', false, 100, null);
$this->addColumn('SLUG', 'Slug', 'VARCHAR', false, 100, null);
$this->getColumn('NAME', false)->setPrimaryString(true);
$this->addColumn('ISBN', 'Isbn', 'VARCHAR', false, 20, null);
} // initialize()
/**
* Build the RelationMap objects for this table relationships
*/
public function buildRelations()
{
} // buildRelations()
/**
* Retrieves a string version of the primary key from the DB resultset row that can be used to uniquely identify a row in this table.
*
* For tables with a single-column primary key, that simple pkey value will be returned. For tables with
* a multi-column primary key, a serialize()d version of the primary key will be returned.
*
* @param array $row resultset row.
* @param int $offset The 0-based offset for reading from the resultset row.
* @param string $indexType One of the class type constants TableMap::TYPE_PHPNAME, TableMap::TYPE_CAMELNAME
* TableMap::TYPE_COLNAME, TableMap::TYPE_FIELDNAME, TableMap::TYPE_NUM
*/
public static function getPrimaryKeyHashFromRow($row, $offset = 0, $indexType = TableMap::TYPE_NUM)
{
// If the PK cannot be derived from the row, return NULL.
if ($row[TableMap::TYPE_NUM == $indexType ? 0 + $offset : static::translateFieldName('Id', TableMap::TYPE_PHPNAME, $indexType)] === null) {
return null;
}
return (string) $row[TableMap::TYPE_NUM == $indexType ? 0 + $offset : static::translateFieldName('Id', TableMap::TYPE_PHPNAME, $indexType)];
}
/**
* Retrieves the primary key from the DB resultset row
* For tables with a single-column primary key, that simple pkey value will be returned. For tables with
* a multi-column primary key, an array of the primary key columns will be returned.
*
* @param array $row resultset row.
* @param int $offset The 0-based offset for reading from the resultset row.
* @param string $indexType One of the class type constants TableMap::TYPE_PHPNAME, TableMap::TYPE_CAMELNAME
* TableMap::TYPE_COLNAME, TableMap::TYPE_FIELDNAME, TableMap::TYPE_NUM
*
* @return mixed The primary key of the row
*/
public static function getPrimaryKeyFromRow($row, $offset = 0, $indexType = TableMap::TYPE_NUM)
{
return (int) $row[
$indexType == TableMap::TYPE_NUM
? 0 + $offset
: self::translateFieldName('Id', TableMap::TYPE_PHPNAME, $indexType)
];
}
/**
* The class that the tableMap will make instances of.
*
* If $withPrefix is true, the returned path
* uses a dot-path notation which is translated into a path
* relative to a location on the PHP include_path.
* (e.g. path.to.MyClass -> 'path/to/MyClass.php')
*
* @param boolean $withPrefix Whether or not to return the path with the class name
* @return string path.to.ClassName
*/
public static function getOMClass($withPrefix = true)
{
return $withPrefix ? BookTableMap::CLASS_DEFAULT : BookTableMap::OM_CLASS;
}
/**
* Populates an object of the default type or an object that inherit from the default.
*
* @param array $row row returned by DataFetcher->fetch().
* @param int $offset The 0-based offset for reading from the resultset row.
* @param string $indexType The index type of $row. Mostly DataFetcher->getIndexType().
One of the class type constants TableMap::TYPE_PHPNAME, TableMap::TYPE_CAMELNAME
* TableMap::TYPE_COLNAME, TableMap::TYPE_FIELDNAME, TableMap::TYPE_NUM.
*
* @throws PropelException Any exceptions caught during processing will be
* rethrown wrapped into a PropelException.
* @return array (Book object, last column rank)
*/
public static function populateObject($row, $offset = 0, $indexType = TableMap::TYPE_NUM)
{
$key = BookTableMap::getPrimaryKeyHashFromRow($row, $offset, $indexType);
if (null !== ($obj = BookTableMap::getInstanceFromPool($key))) {
// We no longer rehydrate the object, since this can cause data loss.
// See http://www.propelorm.org/ticket/509
// $obj->hydrate($row, $offset, true); // rehydrate
$col = $offset + BookTableMap::NUM_HYDRATE_COLUMNS;
} else {
$cls = BookTableMap::OM_CLASS;
$obj = new $cls();
$col = $obj->hydrate($row, $offset, false, $indexType);
BookTableMap::addInstanceToPool($obj, $key);
}
return array($obj, $col);
}
/**
* The returned array will contain objects of the default type or
* objects that inherit from the default.
*
* @param DataFetcherInterface $dataFetcher
* @return array
* @throws PropelException Any exceptions caught during processing will be
* rethrown wrapped into a PropelException.
*/
public static function populateObjects(DataFetcherInterface $dataFetcher)
{
$results = array();
// set the class once to avoid overhead in the loop
$cls = static::getOMClass(false);
// populate the object(s)
while ($row = $dataFetcher->fetch()) {
$key = BookTableMap::getPrimaryKeyHashFromRow($row, 0, $dataFetcher->getIndexType());
if (null !== ($obj = BookTableMap::getInstanceFromPool($key))) {
// We no longer rehydrate the object, since this can cause data loss.
// See http://www.propelorm.org/ticket/509
// $obj->hydrate($row, 0, true); // rehydrate
$results[] = $obj;
} else {
$obj = new $cls();
$obj->hydrate($row);
$results[] = $obj;
BookTableMap::addInstanceToPool($obj, $key);
} // if key exists
}
return $results;
}
/**
* Add all the columns needed to create a new object.
*
* Note: any columns that were marked with lazyLoad="true" in the
* XML schema will not be added to the select list and only loaded
* on demand.
*
* @param Criteria $criteria object containing the columns to add.
* @param string $alias optional table alias
* @throws PropelException Any exceptions caught during processing will be
* rethrown wrapped into a PropelException.
*/
public static function addSelectColumns(Criteria $criteria, $alias = null)
{
if (null === $alias) {
$criteria->addSelectColumn(BookTableMap::ID);
$criteria->addSelectColumn(BookTableMap::NAME);
$criteria->addSelectColumn(BookTableMap::SLUG);
$criteria->addSelectColumn(BookTableMap::ISBN);
$criteria->addSelectColumn(BookTableMap::AUTHOR_ID);
} else {
$criteria->addSelectColumn($alias . '.ID');
$criteria->addSelectColumn($alias . '.NAME');
$criteria->addSelectColumn($alias . '.SLUG');
$criteria->addSelectColumn($alias . '.ISBN');
$criteria->addSelectColumn($alias . '.AUTHOR_ID');
}
}
/**
* Returns the TableMap related to this object.
* This method is not needed for general use but a specific application could have a need.
* @return TableMap
* @throws PropelException Any exceptions caught during processing will be
* rethrown wrapped into a PropelException.
*/
public static function getTableMap()
{
return Propel::getServiceContainer()->getDatabaseMap(BookTableMap::DATABASE_NAME)->getTable(BookTableMap::TABLE_NAME);
}
/**
* Add a TableMap instance to the database for this tableMap class.
*/
public static function buildTableMap()
{
$dbMap = Propel::getServiceContainer()->getDatabaseMap(BookTableMap::DATABASE_NAME);
if (!$dbMap->hasTable(BookTableMap::TABLE_NAME)) {
$dbMap->addTableObject(new BookTableMap());
}
}
/**
* Performs a DELETE on the database, given a Book or Criteria object OR a primary key value.
*
* @param mixed $values Criteria or Book object or primary key or array of primary keys
* which is used to create the DELETE statement
* @param ConnectionInterface $con the connection to use
* @return int The number of affected rows (if supported by underlying database driver). This includes CASCADE-related rows
* if supported by native driver or if emulated using Propel.
* @throws PropelException Any exceptions caught during processing will be
* rethrown wrapped into a PropelException.
*/
public static function doDelete($values, ConnectionInterface $con = null)
{
if (null === $con) {
$con = Propel::getServiceContainer()->getWriteConnection(BookTableMap::DATABASE_NAME);
}
if ($values instanceof Criteria) {
// rename for clarity
$criteria = $values;
} elseif ($values instanceof \Acme\DemoBundle\Model\Book) { // it's a model object
// create criteria based on pk values
$criteria = $values->buildPkeyCriteria();
} else { // it's a primary key, or an array of pks
$criteria = new Criteria(BookTableMap::DATABASE_NAME);
$criteria->add(BookTableMap::ID, (array) $values, Criteria::IN);
}
$query = BookQuery::create()->mergeWith($criteria);
if ($values instanceof Criteria) { BookTableMap::clearInstancePool();
} elseif (!is_object($values)) { // it's a primary key, or an array of pks
foreach ((array) $values as $singleval) { BookTableMap::removeInstanceFromPool($singleval);
}
}
return $query->delete($con);
}
/**
* Deletes all rows from the book table.
*
* @param ConnectionInterface $con the connection to use
* @return int The number of affected rows (if supported by underlying database driver).
*/
public static function doDeleteAll(ConnectionInterface $con = null)
{
return BookQuery::create()->doDeleteAll($con);
}
/**
* Performs an INSERT on the database, given a Book or Criteria object.
*
* @param mixed $criteria Criteria or Book object containing data that is used to create the INSERT statement.
* @param ConnectionInterface $con the ConnectionInterface connection to use
* @return mixed The new primary key.
* @throws PropelException Any exceptions caught during processing will be
* rethrown wrapped into a PropelException.
*/
public static function doInsert($criteria, ConnectionInterface $con = null)
{
if (null === $con) {
$con = Propel::getServiceContainer()->getWriteConnection(BookTableMap::DATABASE_NAME);
}
if ($criteria instanceof Criteria) {
$criteria = clone $criteria; // rename for clarity
} else {
$criteria = $criteria->buildCriteria(); // build Criteria from Book object
}
if ($criteria->containsKey(BookTableMap::ID) && $criteria->keyContainsValue(BookTableMap::ID) ) {
throw new PropelException('Cannot insert a value for auto-increment primary key ('.BookTableMap::ID.')');
}
// Set the correct dbName
$query = BookQuery::create()->mergeWith($criteria);
try {
// use transaction because $criteria could contain info
// for more than one table (I guess, conceivably)
$con->beginTransaction();
$pk = $query->doInsert($con);
$con->commit();
} catch (PropelException $e) {
$con->rollBack();
throw $e;
}
return $pk;
}
} // BookTableMap
// This is the static code needed to register the TableMap for this table with the main Propel class.
//
BookTableMap::buildTableMap();

View file

@ -0,0 +1,13 @@
<?php
namespace Propel\PropelBundle\Tests\Fixtures\Model;
use Propel\PropelBundle\Tests\Fixtures\Model\Base\User as BaseUser;
use Symfony\Component\Security\Core\User\UserInterface;
class User extends BaseUser implements UserInterface
{
public function eraseCredentials()
{
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
class ReadOnlyItem implements ActiveRecordInterface
{
public function getName()
{
return 'Marvin';
}
public function getPrimaryKey()
{
return 42;
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Runtime\Map\ColumnMap;
use Propel\Runtime\Map\TableMap;
class ReadOnlyItemQuery
{
public function getTableMap()
{
// Allows to define methods in this class
// to avoid a lot of mock classes
return $this;
}
public function getPrimaryKeys()
{
$cm = new ColumnMap('id', new TableMap());
$cm->setType('INTEGER');
return array('id' => $cm);
}
}

View file

@ -0,0 +1,131 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Propel\Runtime\Connection\ConnectionInterface;
class TranslatableItem implements ActiveRecordInterface
{
private $id;
private $currentTranslations;
private $groupName;
private $price;
public function __construct($id = null, $translations = array())
{
$this->id = $id;
$this->currentTranslations = $translations;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getGroupName()
{
return $this->groupName;
}
public function getPrice()
{
return $this->price;
}
public function getPrimaryKey()
{
return $this->getId();
}
public function setPrimaryKey($primaryKey)
{
$this->setId($primaryKey);
}
public function isModified()
{
return false;
}
public function isColumnModified($col)
{
return false;
}
public function isNew()
{
return false;
}
public function setNew($b)
{
}
public function resetModified()
{
}
public function isDeleted()
{
return false;
}
public function setDeleted($b)
{
}
public function delete(ConnectionInterface $con = null)
{
}
public function save(ConnectionInterface $con = null)
{
}
public function getTranslation($locale = 'de', ConnectionInterface $con = null)
{
if (!isset($this->currentTranslations[$locale])) {
$translation = new TranslatableItemI18n();
$translation->setLocale($locale);
$this->currentTranslations[$locale] = $translation;
}
return $this->currentTranslations[$locale];
}
public function addTranslatableItemI18n(TranslatableItemI18n $i)
{
if (!in_array($i, $this->currentTranslations)) {
$this->currentTranslations[$i->getLocale()] = $i;
$i->setItem($this);
}
}
public function removeTranslatableItemI18n(TranslatableItemI18n $i)
{
unset($this->currentTranslations[$i->getLocale()]);
}
public function getTranslatableItemI18ns()
{
return $this->currentTranslations;
}
}

View file

@ -0,0 +1,138 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Propel\Runtime\Connection\ConnectionInterface;
class TranslatableItemI18n implements ActiveRecordInterface
{
private $id;
private $locale;
private $value;
private $value2;
private $item;
public function __construct($id = null, $locale = null, $value = null)
{
$this->id = $id;
$this->locale = $locale;
$this->value = $value;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getPrimaryKey()
{
return $this->getId();
}
public function setPrimaryKey($primaryKey)
{
$this->setId($primaryKey);
}
public function isModified()
{
return false;
}
public function isColumnModified($col)
{
return false;
}
public function isNew()
{
return false;
}
public function setNew($b)
{
}
public function resetModified()
{
}
public function isDeleted()
{
return false;
}
public function setDeleted($b)
{
}
public function delete(ConnectionInterface $con = null)
{
}
public function save(ConnectionInterface $con = null)
{
}
public function setLocale($locale)
{
$this->locale = $locale;
}
public function getLocale()
{
return $this->locale;
}
public function getItem()
{
return $this->item;
}
public function setItem($item)
{
$this->item = $item;
}
public function setValue($value)
{
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
public function setValue2($value2)
{
$this->value2 = $value2;
}
public function getValue2()
{
return $this->value2;
}
}

View file

@ -0,0 +1,215 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Form\ChoiceList;
use Propel\PropelBundle\Form\ChoiceList\ModelChoiceList;
use Propel\PropelBundle\Tests\Fixtures\Item;
use Propel\PropelBundle\Tests\Fixtures\ReadOnlyItem;
use Propel\PropelBundle\Tests\TestCase;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
class ModelChoiceListTest extends TestCase
{
const ITEM_CLASS = '\Propel\PropelBundle\Tests\Fixtures\Item';
protected function setUp()
{
if (!class_exists('Symfony\Component\Form\Form')) {
$this->markTestSkipped('The "Form" component is not available');
}
if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccessor')) {
$this->markTestSkipped('The "PropertyAccessor" component is not available');
}
}
public function testEmptyChoicesReturnsEmpty()
{
$choiceList = new ModelChoiceList(
self::ITEM_CLASS,
'value',
array()
);
$this->assertSame(array(), $choiceList->getChoices());
}
public function testReadOnlyIsValidChoice()
{
$item = new ReadOnlyItem();
$choiceList = new ModelChoiceList(
'\Propel\PropelBundle\Tests\Fixtures\ReadOnlyItem',
'name',
array(
$item,
)
);
$this->assertSame(array(42 => $item), $choiceList->getChoices());
}
public function testFlattenedChoices()
{
$item1 = new Item(1, 'Foo');
$item2 = new Item(2, 'Bar');
$choiceList = new ModelChoiceList(
self::ITEM_CLASS,
'value',
array(
$item1,
$item2,
)
);
$this->assertSame(array(1 => $item1, 2 => $item2), $choiceList->getChoices());
}
public function testFlattenedPreferredChoices()
{
$item1 = new Item(1, 'Foo');
$item2 = new Item(2, 'Bar');
$choiceList = new ModelChoiceList(
self::ITEM_CLASS,
'value',
array(
$item1,
$item2,
),
null,
null,
array(
$item1
)
);
$this->assertSame(array(1 => $item1, 2 => $item2), $choiceList->getChoices());
$this->assertEquals(array(1 => new ChoiceView($item1, '1', 'Foo')), $choiceList->getPreferredViews());
}
public function testNestedChoices()
{
$item1 = new Item(1, 'Foo');
$item2 = new Item(2, 'Bar');
$choiceList = new ModelChoiceList(
self::ITEM_CLASS,
'value',
array(
'group1' => array($item1),
'group2' => array($item2),
)
);
$this->assertSame(array(1 => $item1, 2 => $item2), $choiceList->getChoices());
$this->assertEquals(array(
'group1' => array(1 => new ChoiceView($item1, '1', 'Foo')),
'group2' => array(2 => new ChoiceView($item2, '2', 'Bar'))
), $choiceList->getRemainingViews());
}
public function testGroupBySupportsString()
{
$item1 = new Item(1, 'Foo', 'Group1');
$item2 = new Item(2, 'Bar', 'Group1');
$item3 = new Item(3, 'Baz', 'Group2');
$item4 = new Item(4, 'Boo!', null);
$choiceList = new ModelChoiceList(
self::ITEM_CLASS,
'value',
array(
$item1,
$item2,
$item3,
$item4,
),
null,
'groupName'
);
$this->assertEquals(array(1 => $item1, 2 => $item2, 3 => $item3, 4 => $item4), $choiceList->getChoices());
$this->assertEquals(array(
'Group1' => array(1 => new ChoiceView($item1, '1', 'Foo'), 2 => new ChoiceView($item2, '2', 'Bar')),
'Group2' => array(3 => new ChoiceView($item3, '3', 'Baz')),
4 => new ChoiceView($item4, '4', 'Boo!')
), $choiceList->getRemainingViews());
}
public function testGroupByInvalidPropertyPathReturnsFlatChoices()
{
$item1 = new Item(1, 'Foo', 'Group1');
$item2 = new Item(2, 'Bar', 'Group1');
$choiceList = new ModelChoiceList(
self::ITEM_CLASS,
'value',
array(
$item1,
$item2,
),
null,
'child.that.does.not.exist'
);
$this->assertEquals(array(
1 => $item1,
2 => $item2
), $choiceList->getChoices());
}
public function testGetValuesForChoices()
{
$item1 = new Item(1, 'Foo');
$item2 = new Item(2, 'Bar');
$choiceList = new ModelChoiceList(
self::ITEM_CLASS,
'value',
null,
null,
null,
null
);
$this->assertEquals(array(1, 2), $choiceList->getValuesForChoices(array($item1, $item2)));
$this->assertEquals(array(1, 2), $choiceList->getIndicesForChoices(array($item1, $item2)));
}
public function testDifferentEqualObjectsAreChoosen()
{
$item = new Item(1, 'Foo');
$choiceList = new ModelChoiceList(
self::ITEM_CLASS,
'value',
array($item)
);
$choosenItem = new Item(1, 'Foo');
$this->assertEquals(array(1), $choiceList->getIndicesForChoices(array($choosenItem)));
}
public function testGetIndicesForNullChoices()
{
$item = new Item(1, 'Foo');
$choiceList = new ModelChoiceList(
self::ITEM_CLASS,
'value',
array($item)
);
$this->assertEquals(array(), $choiceList->getIndicesForChoices(array(null)));
}
}

View file

@ -0,0 +1,111 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Form\Form\DataTransformer;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\PropelBundle\Tests\TestCase;
use Propel\PropelBundle\Form\DataTransformer\CollectionToArrayTransformer;
class CollectionToArrayTransformerTest extends TestCase
{
private $transformer;
protected function setUp()
{
if (!class_exists('Symfony\Component\Form\Form')) {
$this->markTestSkipped('The "Form" component is not available');
}
parent::setUp();
$this->transformer = new CollectionToArrayTransformer();
}
public function testTransform()
{
$result = $this->transformer->transform(new ObjectCollection());
$this->assertTrue(is_array($result));
$this->assertEquals(0, count($result));
}
public function testTransformWithNull()
{
$result = $this->transformer->transform(null);
$this->assertTrue(is_array($result));
$this->assertEquals(0, count($result));
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testTransformThrowsExceptionIfNotObjectCollection()
{
$this->transformer->transform(new DummyObject());
}
public function testTransformWithData()
{
$coll = new ObjectCollection();
$coll->setData(array('foo', 'bar'));
$result = $this->transformer->transform($coll);
$this->assertTrue(is_array($result));
$this->assertEquals(2, count($result));
$this->assertEquals('foo', $result[0]);
$this->assertEquals('bar', $result[1]);
}
public function testReverseTransformWithNull()
{
$result = $this->transformer->reverseTransform(null);
$this->assertInstanceOf('\Propel\Runtime\Collection\ObjectCollection', $result);
$this->assertEquals(0, count($result->getData()));
}
public function testReverseTransformWithEmptyString()
{
$result = $this->transformer->reverseTransform('');
$this->assertInstanceOf('\Propel\Runtime\Collection\ObjectCollection', $result);
$this->assertEquals(0, count($result->getData()));
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testReverseTransformThrowsExceptionIfNotArray()
{
$this->transformer->reverseTransform(new DummyObject());
}
public function testReverseTransformWithData()
{
$inputData = array('foo', 'bar');
$result = $this->transformer->reverseTransform($inputData);
$data = $result->getData();
$this->assertInstanceOf('\Propel\Runtime\Collection\ObjectCollection', $result);
$this->assertTrue(is_array($data));
$this->assertEquals(2, count($data));
$this->assertEquals('foo', $data[0]);
$this->assertEquals('bar', $data[1]);
$this->assertsame($inputData, $data);
}
}
class DummyObject {}

View file

@ -0,0 +1,158 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Form\Form\Type;
use Propel\PropelBundle\Tests\Fixtures\Item;
use Propel\PropelBundle\Form\PropelExtension;
use Propel\PropelBundle\Tests\Fixtures\TranslatableItemI18n;
use Propel\PropelBundle\Tests\Fixtures\TranslatableItem;
use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase;
class TranslationCollectionTypeTest extends TypeTestCase
{
const TRANSLATION_CLASS = 'Propel\PropelBundle\Tests\Fixtures\TranslatableItem';
const TRANSLATABLE_I18N_CLASS = 'Propel\PropelBundle\Tests\Fixtures\TranslatableItemI18n';
const NON_TRANSLATION_CLASS = 'Propel\PropelBundle\Tests\Fixtures\Item';
protected function setUp()
{
parent::setUp();
}
protected function getExtensions()
{
return array_merge(parent::getExtensions(), array(
new PropelExtension(),
));
}
public function testTranslationsAdded()
{
$item = new TranslatableItem();
$item->addTranslatableItemI18n(new TranslatableItemI18n(1, 'fr', 'val1'));
$item->addTranslatableItemI18n(new TranslatableItemI18n(2, 'en', 'val2'));
$builder = $this->factory->createBuilder('form', null, array(
'data_class' => self::TRANSLATION_CLASS
));
$builder->add('translatableItemI18ns', 'propel_translation_collection', array(
'languages' => array('en', 'fr'),
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS,
'columns' => array('value', 'value2' => array('label' => 'Label', 'type' => 'textarea'))
)
));
$form = $builder->getForm();
$form->setData($item);
$translations = $form->get('translatableItemI18ns');
$this->assertCount(2, $translations);
$this->assertInstanceOf('Symfony\Component\Form\Form', $translations['en']);
$this->assertInstanceOf('Symfony\Component\Form\Form', $translations['fr']);
$this->assertInstanceOf(self::TRANSLATABLE_I18N_CLASS, $translations['en']->getData());
$this->assertInstanceOf(self::TRANSLATABLE_I18N_CLASS, $translations['fr']->getData());
$this->assertEquals($item->getTranslation('en'), $translations['en']->getData());
$this->assertEquals($item->getTranslation('fr'), $translations['fr']->getData());
$columnOptions = $translations['fr']->getConfig()->getOption('columns');
$this->assertEquals('value', $columnOptions[0]);
$this->assertEquals('textarea', $columnOptions['value2']['type']);
$this->assertEquals('Label', $columnOptions['value2']['label']);
}
public function testNotPresentTranslationsAdded()
{
$item = new TranslatableItem();
$this->assertCount(0, $item->getTranslatableItemI18ns());
$builder = $this->factory->createBuilder('form', null, array(
'data_class' => self::TRANSLATION_CLASS
));
$builder->add('translatableItemI18ns', 'propel_translation_collection', array(
'languages' => array('en', 'fr'),
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS,
'columns' => array('value', 'value2' => array('label' => 'Label', 'type' => 'textarea'))
)
));
$form = $builder->getForm();
$form->setData($item);
$this->assertCount(2, $item->getTranslatableItemI18ns());
}
/**
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testNoArrayGiven()
{
$item = new Item(null, 'val');
$builder = $this->factory->createBuilder('form', null, array(
'data_class' => self::NON_TRANSLATION_CLASS
));
$builder->add('value', 'propel_translation_collection', array(
'languages' => array('en', 'fr'),
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS,
'columns' => array('value', 'value2' => array('label' => 'Label', 'type' => 'textarea'))
)
));
$form = $builder->getForm();
$form->setData($item);
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException
*/
public function testNoDataClassAdded()
{
$this->factory->createNamed('itemI18ns', 'propel_translation_collection', null, array(
'languages' => array('en', 'fr'),
'options' => array(
'columns' => array('value', 'value2')
)
));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException
*/
public function testNoLanguagesAdded()
{
$this->factory->createNamed('itemI18ns', 'propel_translation_collection', null, array(
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS,
'columns' => array('value', 'value2')
)
));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException
*/
public function testNoColumnsAdded()
{
$this->factory->createNamed('itemI18ns', 'propel_translation_collection', null, array(
'languages' => array('en', 'fr'),
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS
)
));
}
}

View file

@ -0,0 +1,129 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Form;
use Propel\PropelBundle\Form\TypeGuesser;
use Propel\PropelBundle\Tests\TestCase;
use Symfony\Component\Form\Guess\Guess;
class TypeGuesserTest extends TestCase
{
const CLASS_NAME = 'Propel\PropelBundle\Tests\Fixtures\Item';
const UNKNOWN_CLASS_NAME = 'Propel\PropelBundle\Tests\Fixtures\UnknownItem';
private $guesser;
public function setUp()
{
$this->guesser = new TypeGuesser();
}
public function testGuessMaxLengthWithText()
{
$value = $this->guesser->guessMaxLength(self::CLASS_NAME, 'value');
$this->assertNotNull($value);
$this->assertEquals(255, $value->getValue());
}
public function testGuessMaxLengthWithFloat()
{
$value = $this->guesser->guessMaxLength(self::CLASS_NAME, 'price');
$this->assertNotNull($value);
$this->assertNull($value->getValue());
}
public function testGuessMinLengthWithText()
{
$value = $this->guesser->guessPattern(self::CLASS_NAME, 'value');
$this->assertNull($value);
}
public function testGuessMinLengthWithFloat()
{
$value = $this->guesser->guessPattern(self::CLASS_NAME, 'price');
$this->assertNotNull($value);
$this->assertNull($value->getValue());
}
public function testGuessRequired()
{
$value = $this->guesser->guessRequired(self::CLASS_NAME, 'id');
$this->assertNotNull($value);
$this->assertTrue($value->getValue());
}
public function testGuessRequiredWithNullableColumn()
{
$value = $this->guesser->guessRequired(self::CLASS_NAME, 'value');
$this->assertNotNull($value);
$this->assertFalse($value->getValue());
}
public function testGuessTypeWithoutTable()
{
$value = $this->guesser->guessType(self::UNKNOWN_CLASS_NAME, 'property');
$this->assertNotNull($value);
$this->assertEquals('text', $value->getType());
$this->assertEquals(Guess::LOW_CONFIDENCE, $value->getConfidence());
}
public function testGuessTypeWithoutColumn()
{
$value = $this->guesser->guessType(self::CLASS_NAME, 'property');
$this->assertNotNull($value);
$this->assertEquals('text', $value->getType());
$this->assertEquals(Guess::LOW_CONFIDENCE, $value->getConfidence());
}
/**
* @dataProvider dataProviderForGuessType
*/
public function testGuessType($property, $type, $confidence, $multiple = null)
{
$value = $this->guesser->guessType(self::CLASS_NAME, $property);
$this->assertNotNull($value);
$this->assertEquals($type, $value->getType());
$this->assertEquals($confidence, $value->getConfidence());
if ($type === 'model') {
$options = $value->getOptions();
$this->assertSame($multiple, $options['multiple']);
}
}
public static function dataProviderForGuessType()
{
return array(
array('is_active', 'checkbox', Guess::HIGH_CONFIDENCE),
array('enabled', 'checkbox', Guess::HIGH_CONFIDENCE),
array('id', 'integer', Guess::MEDIUM_CONFIDENCE),
array('value', 'text', Guess::MEDIUM_CONFIDENCE),
array('price', 'number', Guess::MEDIUM_CONFIDENCE),
array('updated_at', 'datetime', Guess::HIGH_CONFIDENCE),
array('Authors', 'model', Guess::HIGH_CONFIDENCE, true),
array('Resellers', 'model', Guess::HIGH_CONFIDENCE, true),
array('MainAuthor', 'model', Guess::HIGH_CONFIDENCE, false),
);
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests\Model\Acl;
use Propel\PropelBundle\Model\Acl\AclClass;
use Propel\PropelBundle\Model\Acl\AclClassQuery;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Propel\PropelBundle\Tests\AclTestCase;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AclClassTest extends AclTestCase
{
public function testFromAclObjectIdentity()
{
$type = 'Merchant';
$aclClass = AclClass::fromAclObjectIdentity(new ObjectIdentity(5, $type), $this->con);
$this->assertInstanceOf('Propel\PropelBundle\Model\Acl\AclClass', $aclClass);
$this->assertEquals($type, $aclClass->getType());
$dbEntry = AclClassQuery::create()->findOne($this->con);
$this->assertInstanceOf('Propel\PropelBundle\Model\Acl\AclClass', $dbEntry);
$this->assertEquals($type, $dbEntry->getType());
$this->assertEquals($dbEntry->getId(), $aclClass->getId());
}
}

View file

@ -0,0 +1,135 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests\Model\Acl;
use Propel\PropelBundle\Model\Acl\Entry;
use Propel\PropelBundle\Model\Acl\EntryQuery;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Propel\PropelBundle\Tests\AclTestCase;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class EntryQueryTest extends AclTestCase
{
public function setUp()
{
parent::setUp();
$obj = $this->createModelObjectIdentity(1);
$entry = $this->createEntry();
$entry
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($this->getRoleSecurityIdentity('ROLE_USER')))
->setAclClass($obj->getAclClass())
->setMask(64)
;
$obj->addEntry($entry)->save($this->con);
}
public function testFindByAclIdentityInvalidSecurityIdentity()
{
$this->setExpectedException('InvalidArgumentException');
EntryQuery::create()->findByAclIdentity($this->getAclObjectIdentity(), array('foo'), $this->con);
}
public function testFindByAclIdentityInvalidSecurityIdentityObject()
{
$this->setExpectedException('InvalidArgumentException');
EntryQuery::create()->findByAclIdentity($this->getAclObjectIdentity(), array(new \stdClass()), $this->con);
}
public function testFindByAclIdentityNotExists()
{
$this->assertCount(0, EntryQuery::create()->findByAclIdentity($this->getAclObjectIdentity(2), array(), $this->con));
}
public function testFindByAclIdentitySecurityIdentityNotFound()
{
$this->assertCount(0, EntryQuery::create()->findByAclIdentity($this->getAclObjectIdentity(1), array($this->getRoleSecurityIdentity('ROLE_ADMIN')), $this->con));
}
public function testFindByAclIdentity()
{
// Another Entry, should not be found (different ObjectIdentity).
$obj = $this->createModelObjectIdentity(2);
$entry = $this->createEntry();
$entry
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($this->getRoleSecurityIdentity()))
->setAclClass($obj->getAclClass())
->setMask(64)
;
$obj->addEntry($entry)->save($this->con);
$entries = EntryQuery::create()->findByAclIdentity($this->getAclObjectIdentity(1), array(), $this->con);
$this->assertCount(1, $entries);
$this->assertEquals(1, $entries[0]->getObjectIdentityId());
// A class based entry for the wrong ObjectIdentity.
$classEntry = $this->createEntry();
$classEntry
->setObjectIdentityId(2)
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($this->getRoleSecurityIdentity()))
->setAclClass($obj->getAclClass())
->setMask(64)
->save($this->con)
;
// A class based entry for the correct ObjectIdentity.
$classEntry = $this->createEntry();
$classEntry
->setObjectIdentityId(null)
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($this->getRoleSecurityIdentity()))
->setAclClass($this->getAclClass())
->setMask(64)
->save($this->con)
;
$this->assertEquals(4, EntryQuery::create()->count($this->con));
$entries = EntryQuery::create()->findByAclIdentity($this->getAclObjectIdentity(1), array(), $this->con);
$this->assertCount(2, $entries);
$this->assertEquals($obj->getClassId(), $entries[0]->getClassId());
$this->assertEquals($obj->getClassId(), $entries[1]->getClassId());
}
public function testFindByAclIdentityFilterSecurityIdentity()
{
// Another Entry, should not be found (different SecurityIdentity).
$entry = $this->createEntry();
$entry
->setObjectIdentityId(1)
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($this->getRoleSecurityIdentity('ROLE_ADMIN')))
->setAclClass($this->getAclClass())
->setMask(64)
->save($this->con)
;
$this->assertEquals(2, EntryQuery::create()->count($this->con));
$entries = EntryQuery::create()->findByAclIdentity($this->getAclObjectIdentity(1), array($this->getRoleSecurityIdentity('ROLE_USER')), $this->con);
$this->assertCount(1, $entries);
$this->assertEquals(SecurityIdentity::fromAclIdentity($this->getRoleSecurityIdentity('ROLE_USER'))->getId(), $entries[0]->getSecurityIdentityId());
}
public function testFindByAclIdentityOnlyClassEntries()
{
$this->assertEquals(1, EntryQuery::create()->count($this->con));
EntryQuery::create()->findOne($this->con)
->setObjectIdentity(null)
->save($this->con);
$entries = EntryQuery::create()->findByAclIdentity($this->getAclObjectIdentity(1), array(), $this->con);
$this->assertCount(1, $entries);
}
}

88
Tests/Model/EntryTest.php Normal file
View file

@ -0,0 +1,88 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests\Model\Acl;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Propel\PropelBundle\Security\Acl\Domain\Entry as AclEntry;
use Propel\PropelBundle\Tests\AclTestCase;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class EntryTest extends AclTestCase
{
public function testToAclEntry()
{
$acl = $this->getMock('Propel\PropelBundle\Security\Acl\Domain\AuditableAcl', array(), array(), '', false, false);
$entry = $this->createModelEntry();
$aclEntry = ModelEntry::toAclEntry($entry, $acl);
$this->assertInstanceOf('Propel\PropelBundle\Security\Acl\Domain\Entry', $aclEntry);
$this->assertSame($acl, $aclEntry->getAcl());
$this->assertEquals(42, $aclEntry->getId());
$this->assertTrue($aclEntry->isAuditFailure());
$this->assertFalse($aclEntry->isAuditSuccess());
$this->assertEquals('all', $aclEntry->getStrategy());
$this->assertTrue($aclEntry->isGranting());
$this->assertEquals(64, $aclEntry->getMask());
return $aclEntry;
}
/**
* @depends testToAclEntry
*/
public function testToAclEntryFieldEntry()
{
$acl = $this->getMock('Propel\PropelBundle\Security\Acl\Domain\AuditableAcl', array(), array(), '', false, false);
$entry = $this->createModelEntry();
$entry->setFieldName('name');
$aclEntry = ModelEntry::toAclEntry($entry, $acl);
$this->assertInstanceOf('Propel\PropelBundle\Security\Acl\Domain\FieldEntry', $aclEntry);
}
/**
* @depends testToAclEntry
*/
public function testFromAclEntry($aclEntry)
{
$modelEntry = ModelEntry::fromAclEntry($aclEntry);
$this->assertInstanceOf('Propel\PropelBundle\Model\Acl\Entry', $modelEntry);
$this->assertEquals(42, $modelEntry->getId());
$this->assertTrue($modelEntry->getAuditFailure());
$this->assertFalse($modelEntry->getAuditSuccess());
$this->assertEquals('all', $modelEntry->getGrantingStrategy());
$this->assertTrue($modelEntry->getGranting());
$this->assertEquals(64, $modelEntry->getMask());
}
protected function createModelEntry()
{
$entry = new ModelEntry();
$entry
->setId(42)
->setAclClass($this->getAclClass())
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($this->getRoleSecurityIdentity()))
->setAuditFailure(true)
->setAuditSuccess(false)
->setGrantingStrategy('all')
->setGranting(true)
->setMask(64)
;
return $entry;
}
}

Some files were not shown because too many files have changed in this diff Show more