1
0
mirror of synced 2025-01-30 12:01:44 +03:00

Merge master into DDC-117

This commit is contained in:
Benjamin Eberlei 2010-12-28 17:27:47 +01:00
commit e7b4dca611
149 changed files with 6589 additions and 4089 deletions

9
bin/doctrine.bat Normal file
View File

@ -0,0 +1,9 @@
@echo off
if "%PHPBIN%" == "" set PHPBIN=@php_bin@
if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH
GOTO RUN
:USE_PEAR_PATH
set PHPBIN=%PHP_PEAR_PHP_BIN%
:RUN
"%PHPBIN%" "@bin_dir@\doctrine" %*

View File

@ -1,4 +1,21 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
require_once 'Doctrine/Common/ClassLoader.php';
@ -30,29 +47,4 @@ if (file_exists($configFile)) {
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
$cli = new \Symfony\Component\Console\Application('Doctrine Command Line Interface', Doctrine\ORM\Version::VERSION);
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);
$cli->addCommands(array(
// DBAL Commands
new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(),
new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(),
// ORM Commands
new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand(),
new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand(),
new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand(),
new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand(),
new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(),
new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(),
new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(),
new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(),
));
$cli->run();
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);

View File

@ -1,10 +1,13 @@
version=2.0.0BETA2
dependencies.common=2.0.0BETA4
dependencies.dbal=2.0.0BETA4
stability=beta
build.dir=build
dist.dir=dist
report.dir=reports
log.archive.dir=logs
svn.path=/usr/bin/svn
project.pirum_dir=
project.download_dir=
test.phpunit_configuration_file=
test.phpunit_generate_coverage=0
test.pmd_reports=0

View File

@ -5,8 +5,6 @@
-->
<project name="Doctrine2" default="build" basedir=".">
<taskdef classname="NativePhpunitTask" classpath="./tests/" name="nativephpunit" />
<taskdef classname="phing.tasks.ext.d51PearPkg2Task" name="d51pearpkg2" />
<property file="build.properties" />
@ -24,6 +22,7 @@
<fileset id="bin-scripts" dir="./bin">
<include name="doctrine"/>
<include name="doctrine.php"/>
<include name="doctrine.bat"/>
</fileset>
<!--
@ -51,7 +50,7 @@
Fileset for source of the Symfony YAML and Console components.
-->
<fileset id="symfony-sources" dir="./lib/vendor">
<include name="Symfony/Components/**"/>
<include name="Symfony/Component/**"/>
</fileset>
<!--
@ -83,23 +82,24 @@
<!--
Builds ORM package, preparing it for distribution.
-->
<target name="build-orm" depends="test">
<copy todir="${build.dir}/orm">
<target name="build-orm" depends="prepare">
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
<copy todir="${build.dir}/doctrine-orm">
<fileset refid="shared-artifacts"/>
</copy>
<copy todir="${build.dir}/orm">
<copy todir="${build.dir}/doctrine-orm">
<fileset refid="common-sources"/>
<fileset refid="dbal-sources"/>
<fileset refid="orm-sources"/>
</copy>
<copy todir="${build.dir}/orm/Doctrine">
<copy todir="${build.dir}/doctrine-orm/Doctrine">
<fileset refid="symfony-sources"/>
</copy>
<copy todir="${build.dir}/orm/bin">
<copy todir="${build.dir}/doctrine-orm/bin">
<fileset refid="bin-scripts"/>
</copy>
<exec command="sed 's/${version}-DEV/${version}/' ${build.dir}/orm/Doctrine/ORM/Version.php > ${build.dir}/orm/Doctrine/ORM/Version2.php" passthru="true" />
<exec command="mv ${build.dir}/orm/Doctrine/ORM/Version2.php ${build.dir}/orm/Doctrine/ORM/Version.php" passthru="true" />
<exec command="sed 's/${version}-DEV/${version}/' ${build.dir}/doctrine-orm/Doctrine/ORM/Version.php > ${build.dir}/doctrine-orm/Doctrine/ORM/Version2.php" passthru="true" />
<exec command="mv ${build.dir}/doctrine-orm/Doctrine/ORM/Version2.php ${build.dir}/doctrine-orm/Doctrine/ORM/Version.php" passthru="true" />
</target>
<target name="build" depends="test, build-orm"/>
@ -141,8 +141,8 @@
<!--
Builds distributable PEAR packages.
-->
<target name="build-packages" depends="build">
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/orm">
<target name="build-packages" depends="build-orm">
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/doctrine-orm">
<name>DoctrineORM</name>
<summary>Doctrine Object Relational Mapper</summary>
<channel>pear.doctrine-project.org</channel>
@ -150,6 +150,7 @@
<lead user="jwage" name="Jonathan H. Wage" email="jonwage@gmail.com" />
<lead user="guilhermeblanco" name="Guilherme Blanco" email="guilhermeblanco@gmail.com" />
<lead user="romanb" name="Roman Borschel" email="roman@code-factory.org" />
<lead user="beberlei" name="Benjamin Eberlei" email="kontakt@beberlei.de" />
<license>LGPL</license>
<version release="${version}" api="${version}" />
<stability release="${stability}" api="${stability}" />
@ -157,17 +158,55 @@
<dependencies>
<php minimum_version="5.3.0" />
<pear minimum_version="1.6.0" recommended_version="1.6.1" />
<package name="DoctrineCommon" channel="pear.doctrine-project.org" minimum_version="${dependencies.common}" />
<package name="DoctrineDBAL" channel="pear.doctrine-project.org" minimum_version="${dependencies.dbal}" />
</dependencies>
<dirroles key="bin">script</dirroles>
<replacement path="bin/doctrine" type="pear-config" from="@php_bin@" to="php_bin" />
<replacement path="bin/doctrine.php" type="pear-config" from="@php_bin@" to="php_bin" />
<replacement path="bin/doctrine.php" type="pear-config" from="@bin_dir@" to="bin_dir" />
<ignore>Doctrine/Common/</ignore>
<ignore>Doctrine/DBAL/</ignore>
<release>
<install as="doctrine" name="bin/doctrine" />
<install as="doctrine.php" name="bin/doctrine.php" />
<install as="doctrine.bat" name="bin/doctrine.bat" />
</release>
<replacement path="bin/doctrine.bat" type="pear-config" from="@php_bin@" to="php_bin" />
<replacement path="bin/doctrine.bat" type="pear-config" from="@bin_dir@" to="bin_dir" />
</d51pearpkg2>
<exec command="pear package" dir="${build.dir}/orm" passthru="true" />
<exec command="mv DoctrineORM-${version}.tgz ../../dist" dir="${build.dir}/orm" passthru="true" />
<exec command="pear package" dir="${build.dir}/doctrine-orm" passthru="true" />
<exec command="mv DoctrineORM-${version}.tgz ../../dist" dir="${build.dir}/doctrine-orm" passthru="true" />
<tar destfile="dist/DoctrineORM-${version}-full.tar.gz" compression="gzip" basedir="${build.dir}">
<fileset dir="${build.dir}">
<include name="**/**" />
<exclude name="logs/" />
<exclude name="doctrine-orm/package.xml" />
</fileset>
</tar>
</target>
<target name="git-tag">
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
<exec command="git tag -a ${version}" passthru="true" />
<exec command="git push origin ${version}" passthru="true" />
</target>
<target name="pirum-release">
<exec command="sudo pirum add ${project.pirum_dir} ${project.basedir}/dist/DoctrineORM-${version}.tgz" dir="." passthru="true" />
<exec command="sudo pirum build ${project.pirum_dir}" passthru="true" />
</target>
<target name="distribute-download">
<copy file="dist/DoctrineORM-${version}-full.tar.gz" todir="${project.download_dir}" />
</target>
<target name="update-dev-version">
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
<propertyprompt propertyName="next_version" defaultValue="${version}" promptText="Enter next version string (without -DEV)" />
<exec command="sed 's/${version}-DEV/${next_version}-DEV/' ${project.basedir}/lib/Doctrine/ORM/Version.php > ${project.basedir}/lib/Doctrine/ORM/Version2.php" passthru="true" />
<exec command="mv ${project.basedir}/lib/Doctrine/ORM/Version2.php ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
<exec command="git add ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
<exec command="git commit -m 'Bump Dev Version to ${next_version}-DEV'" passthru="true" />
<exec command="git push origin master" passthru="true" />
</target>
<target name="release" depends="git-tag,build-packages,distribute-download,pirum-release,update-dev-version" />
</project>

View File

@ -63,7 +63,7 @@
<xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/>
<xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" />
<xs:element name="id" type="orm:id" />
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="1" />
<xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-many" type="orm:one-to-many" minOccurs="0" maxOccurs="unbounded" />
@ -73,7 +73,7 @@
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="table" type="xs:NMTOKEN" />
<xs:attribute name="schema" type="xs:NMTOKEN" />
<xs:attribute name="repository-class" type="xs:NMTOKEN"/>
<xs:attribute name="repository-class" type="xs:string"/>
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
<xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" />
</xs:complexType>
@ -252,7 +252,7 @@
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
</xs:sequence>
<xs:attribute name="target-entity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
@ -264,7 +264,7 @@
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
</xs:sequence>
<xs:attribute name="target-entity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
@ -279,7 +279,7 @@
<xs:element name="join-columns" type="orm:join-columns"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="target-entity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
@ -295,7 +295,7 @@
</xs:choice>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />

View File

@ -482,15 +482,6 @@ abstract class AbstractQuery
*/
public function execute($params = array(), $hydrationMode = null)
{
// If there are still pending insertions in the UnitOfWork we need to flush
// in order to guarantee a correct result.
//TODO: Think this over. Its tricky. Not doing this can lead to strange results
// potentially, but doing it could result in endless loops when querying during
// a flush, i.e. inside an event listener.
if ($this->_em->getUnitOfWork()->hasPendingInsertions()) {
$this->_em->flush();
}
if ($hydrationMode !== null) {
$this->setHydrationMode($hydrationMode);
}
@ -562,9 +553,22 @@ abstract class AbstractQuery
if ($this->_resultCacheId) {
return $this->_resultCacheId;
} else {
$params = $this->_params;
foreach ($params AS $key => $value) {
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
$idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
} else {
$class = $this->_em->getClassMetadata(get_class($value));
$idValues = $class->getIdentifierValues($value);
}
$params[$key] = $idValues;
}
}
$sql = $this->getSql();
ksort($this->_hints);
return md5(implode(";", (array)$sql) . var_export($this->_params, true) .
return md5(implode(";", (array)$sql) . var_export($params, true) .
var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode);
}
}
@ -583,6 +587,8 @@ abstract class AbstractQuery
*/
public function __clone()
{
$this->free();
$this->_params = array();
$this->_paramTypes = array();
$this->_hints = array();
}
}

View File

@ -462,4 +462,25 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
$this->_attributes['customHydrationModes'][$modeName] = $hydrator;
}
/**
* Set a class metadata factory.
*
* @param string $cmf
*/
public function setClassMetadataFactoryName($cmfName)
{
$this->_attributes['classMetadataFactoryName'] = $cmfName;
}
/**
* @return string
*/
public function getClassMetadataFactoryName()
{
if (!isset($this->_attributes['classMetadataFactoryName'])) {
$this->_attributes['classMetadataFactoryName'] = 'Doctrine\ORM\Mapping\ClassMetadataFactory';
}
return $this->_attributes['classMetadataFactoryName'];
}
}

View File

@ -118,8 +118,12 @@ class EntityManager
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$this->metadataFactory = new ClassMetadataFactory($this);
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
$this->metadataFactory = new $metadataFactoryClassName;
$this->metadataFactory->setEntityManager($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory($this,
$config->getProxyDir(),
@ -352,11 +356,15 @@ class EntityManager
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
return $entity;
}
if ($class->subClasses) {
$entity = $this->find($entityName, $identifier);
} else {
if ( ! is_array($identifier)) {
$identifier = array($class->identifier[0] => $identifier);
}
$entity = $this->proxyFactory->getProxy($class->name, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array());
}
return $entity;
}
@ -611,6 +619,16 @@ class EntityManager
}
}
/**
* Check if the Entity manager is open or closed.
*
* @return bool
*/
public function isOpen()
{
return (!$this->closed);
}
/**
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
*

View File

@ -133,7 +133,6 @@ class EntityRepository
/**
* Finds all entities in the repository.
*
* @param int $hydrationMode
* @return array The entities.
*/
public function findAll()
@ -144,8 +143,7 @@ class EntityRepository
/**
* Finds entities by a set of criteria.
*
* @param string $column
* @param string $value
* @param array $criteria
* @return array
*/
public function findBy(array $criteria)
@ -156,8 +154,7 @@ class EntityRepository
/**
* Finds a single entity by a set of criteria.
*
* @param string $column
* @param string $value
* @param array $criteria
* @return object
*/
public function findOneBy(array $criteria)
@ -189,12 +186,13 @@ class EntityRepository
}
if ( !isset($arguments[0])) {
// we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL.
throw ORMException::findByRequiresParameter($method.$by);
}
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
if ($this->_class->hasField($fieldName)) {
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
return $this->$method(array($fieldName => $arguments[0]));
} else {
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);

View File

@ -4,6 +4,9 @@ namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\EntityManager;
/**
* Class that holds event arguments for a loadMetadata event.
*
@ -12,16 +15,40 @@ use Doctrine\Common\EventArgs;
*/
class LoadClassMetadataEventArgs extends EventArgs
{
private $_classMetadata;
/**
* @var ClassMetadata
*/
private $classMetadata;
public function __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata)
/**
* @var EntityManager
*/
private $em;
/**
* @param ClassMetadataInfo $classMetadata
* @param EntityManager $em
*/
public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em)
{
$this->_classMetadata = $classMetadata;
$this->classMetadata = $classMetadata;
$this->em = $em;
}
/**
* @return ClassMetadataInfo
*/
public function getClassMetadata()
{
return $this->_classMetadata;
return $this->classMetadata;
}
/**
* @return EntityManager
*/
public function getEntityManager()
{
return $this->em;
}
}

View File

@ -161,14 +161,16 @@ class ObjectHydrator extends AbstractHydrator
$class->reflFields[$fieldName]->setValue($entity, $value);
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
$this->_initializedCollections[$oid . $fieldName] = $value;
} else if (isset($this->_hints[Query::HINT_REFRESH])) {
// Is already PersistentCollection, but REFRESH
} else if (isset($this->_hints[Query::HINT_REFRESH]) ||
isset($this->_hints['fetched'][$class->name][$fieldName]) &&
! $value->isInitialized()) {
// Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
$value->setDirty(false);
$value->setInitialized(true);
$value->unwrap()->clear();
$this->_initializedCollections[$oid . $fieldName] = $value;
} else {
// Is already PersistentCollection, and DONT REFRESH
// Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN!
$this->_existingCollections[$oid . $fieldName] = $value;
}
@ -269,6 +271,9 @@ class ObjectHydrator extends AbstractHydrator
// It's a joined result
$parentAlias = $this->_rsm->parentAliasMap[$dqlAlias];
// we need the $path to save into the identifier map which entities were already
// seen for this parent-child relationship
$path = $parentAlias . '.' . $dqlAlias;
// Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) {
@ -298,8 +303,8 @@ class ObjectHydrator extends AbstractHydrator
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField);
}
$indexExists = isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] : false;
$indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false;
if ( ! $indexExists || ! $indexIsValid) {
@ -317,11 +322,11 @@ class ObjectHydrator extends AbstractHydrator
$field = $this->_rsm->indexByMap[$dqlAlias];
$indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
$reflFieldValue->hydrateSet($indexValue, $element);
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $indexValue;
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
} else {
$reflFieldValue->hydrateAdd($element);
$reflFieldValue->last();
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $reflFieldValue->key();
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
}
// Update result pointer
$this->_resultPointers[$dqlAlias] = $element;
@ -332,6 +337,7 @@ class ObjectHydrator extends AbstractHydrator
}
} else if ( ! $reflField->getValue($parentObject)) {
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection);
$coll->setOwner($parentObject, $relation);
$reflField->setValue($parentObject, $coll);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
}

View File

@ -74,11 +74,9 @@ class ClassMetadataFactory
private $initialized = false;
/**
* Creates a new factory instance that uses the given metadata driver implementation.
*
* @param $driver The metadata driver to use.
* @param EntityManager $$em
*/
public function __construct(EntityManager $em)
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
@ -262,15 +260,19 @@ class ClassMetadataFactory
$class = $this->newClassMetadataInstance($className);
if ($parent) {
if (!$parent->isMappedSuperclass) {
$class->setInheritanceType($parent->inheritanceType);
$class->setDiscriminatorColumn($parent->discriminatorColumn);
}
$class->setIdGeneratorType($parent->generatorType);
$this->addInheritedFields($class, $parent);
$this->addInheritedRelations($class, $parent);
$class->setIdentifier($parent->identifier);
$class->setVersioned($parent->isVersioned);
$class->setVersionField($parent->versionField);
if (!$parent->isMappedSuperclass) {
$class->setDiscriminatorMap($parent->discriminatorMap);
}
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
}
@ -302,6 +304,17 @@ class ClassMetadataFactory
$this->completeIdGeneratorMapping($class);
}
if ($parent && $parent->isInheritanceTypeSingleTable()) {
$class->setPrimaryTable($parent->table);
}
$class->setParentClasses($visited);
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class, $this->em);
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
// verify inheritance
if (!$parent && !$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
if (count($class->discriminatorMap) == 0) {
@ -312,17 +325,6 @@ class ClassMetadataFactory
}
}
if ($parent && $parent->isInheritanceTypeSingleTable()) {
$class->setPrimaryTable($parent->table);
}
$class->setParentClasses($visited);
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class);
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
$this->loadedMetadata[$className] = $class;
$parent = $class;
@ -379,6 +381,13 @@ class ClassMetadataFactory
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
{
foreach ($parentClass->associationMappings as $field => $mapping) {
if ($parentClass->isMappedSuperclass) {
if ($mapping['type'] & ClassMetadata::TO_MANY) {
throw MappingException::illegalToManyAssocationOnMappedSuperclass($parentClass->name, $field);
}
$mapping['sourceEntity'] = $subClass->name;
}
//$subclassMapping = $mapping;
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;

View File

@ -598,9 +598,10 @@ class ClassMetadataInfo
/**
* Gets the mapping of an association.
*
* @see ClassMetadataInfo::$associationMappings
* @param string $fieldName The field name that represents the association in
* the object model.
* @return Doctrine\ORM\Mapping\AssociationMapping The mapping.
* @return array The mapping.
*/
public function getAssociationMapping($fieldName)
{
@ -669,6 +670,10 @@ class ClassMetadataInfo
// Complete id mapping
if (isset($mapping['id']) && $mapping['id'] === true) {
if ($this->versionField == $mapping['fieldName']) {
throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
}
if ( ! in_array($mapping['fieldName'], $this->identifier)) {
$this->identifier[] = $mapping['fieldName'];
}
@ -947,6 +952,7 @@ class ClassMetadataInfo
public function setIdentifier(array $identifier)
{
$this->identifier = $identifier;
$this->isIdentifierComposite = (count($this->identifier) > 1);
}
/**
@ -1485,6 +1491,13 @@ class ClassMetadataInfo
if ( ! isset($columnDef['fieldName'])) {
$columnDef['fieldName'] = $columnDef['name'];
}
if ( ! isset($columnDef['type'])) {
$columnDef['type'] = "string";
}
if (in_array($columnDef['type'], array("boolean", "array", "object", "datetime", "time", "date"))) {
throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
}
$this->discriminatorColumn = $columnDef;
}
}

View File

@ -155,7 +155,7 @@ abstract class AbstractFileDriver implements Driver
if ($this->_paths) {
foreach ((array) $this->_paths as $path) {
if ( ! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath();
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RecursiveIteratorIterator(

View File

@ -179,6 +179,8 @@ class AnnotationDriver implements Driver
'type' => $discrColumnAnnot->type,
'length' => $discrColumnAnnot->length
));
} else {
$metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255));
}
// Evaluate DiscriminatorMap annotation
@ -443,7 +445,7 @@ class AnnotationDriver implements Driver
foreach ($this->_paths as $path) {
if ( ! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath();
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RecursiveIteratorIterator(

View File

@ -48,7 +48,7 @@ class DatabaseDriver implements Driver
*/
private $tables = null;
private $classes = array();
private $classToTableNames = array();
/**
* @var array
@ -73,11 +73,11 @@ class DatabaseDriver implements Driver
}
foreach ($this->_sm->listTableNames() as $tableName) {
$tables[strtolower($tableName)] = $this->_sm->listTableDetails($tableName);
$tables[$tableName] = $this->_sm->listTableDetails($tableName);
}
$this->tables = array();
foreach ($tables AS $name => $table) {
foreach ($tables AS $tableName => $table) {
/* @var $table Table */
if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$foreignKeys = $table->getForeignKeys();
@ -96,14 +96,16 @@ class DatabaseDriver implements Driver
if ($pkColumns == $allForeignKeyColumns) {
if (count($table->getForeignKeys()) > 2) {
throw new \InvalidArgumentException("ManyToMany table '" . $name . "' with more or less than two foreign keys are not supported by the Database Reverese Engineering Driver.");
throw new \InvalidArgumentException("ManyToMany table '" . $tableName . "' with more or less than two foreign keys are not supported by the Database Reverese Engineering Driver.");
}
$this->manyToManyTables[$name] = $table;
$this->manyToManyTables[$tableName] = $table;
} else {
$className = Inflector::classify($name);
$this->tables[$name] = $table;
$this->classes[$className] = $name;
// lower-casing is necessary because of Oracle Uppercase Tablenames,
// assumption is lower-case + underscore separated.
$className = Inflector::classify(strtolower($tableName));
$this->tables[$tableName] = $table;
$this->classToTableNames[$className] = $tableName;
}
}
}
@ -115,11 +117,11 @@ class DatabaseDriver implements Driver
{
$this->reverseEngineerMappingFromDatabase();
if (!isset($this->classes[$className])) {
if (!isset($this->classToTableNames[$className])) {
throw new \InvalidArgumentException("Unknown class " . $className);
}
$tableName = Inflector::tableize($className);
$tableName = $this->classToTableNames[$className];
$metadata->name = $className;
$metadata->table['name'] = $tableName;
@ -183,7 +185,7 @@ class DatabaseDriver implements Driver
foreach ($this->manyToManyTables AS $manyTable) {
foreach ($manyTable->getForeignKeys() AS $foreignKey) {
if ($tableName == strtolower($foreignKey->getForeignTableName())) {
if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) {
$myFk = $foreignKey;
foreach ($manyTable->getForeignKeys() AS $foreignKey) {
if ($foreignKey != $myFk) {
@ -269,6 +271,6 @@ class DatabaseDriver implements Driver
{
$this->reverseEngineerMappingFromDatabase();
return array_keys($this->classes);
return array_keys($this->classToTableNames);
}
}

View File

@ -114,8 +114,8 @@ final class Index extends Annotation {
final class JoinTable extends Annotation {
public $name;
public $schema;
public $joinColumns;
public $inverseJoinColumns;
public $joinColumns = array();
public $inverseJoinColumns = array();
}
final class SequenceGenerator extends Annotation {
public $sequenceName;

View File

@ -88,10 +88,15 @@ class DriverChain implements Driver
public function getAllClassNames()
{
$classNames = array();
foreach ($this->_drivers AS $driver) {
$classNames = array_merge($classNames, $driver->getAllClassNames());
foreach ($this->_drivers AS $namespace => $driver) {
$driverClasses = $driver->getAllClassNames();
foreach ($driverClasses AS $className) {
if (strpos($className, $namespace) === 0) {
$classNames[] = $className;
}
return $classNames;
}
}
return array_unique($classNames);
}
/**

View File

@ -78,7 +78,7 @@ class StaticPHPDriver implements Driver
foreach ($this->_paths as $path) {
if ( ! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath();
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RecursiveIteratorIterator(

View File

@ -87,6 +87,8 @@ class XmlDriver extends AbstractFileDriver
'type' => (string)$discrColumn['type'],
'length' => (string)$discrColumn['length']
));
} else {
$metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255));
}
// Evaluate <discriminator-map...>

View File

@ -79,6 +79,8 @@ class YamlDriver extends AbstractFileDriver
'type' => $discrColumn['type'],
'length' => $discrColumn['length']
));
} else {
$metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255));
}
// Evaluate discriminatorMap

View File

@ -68,9 +68,9 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self("No mapping file found named '$fileName' for class '$entityName'.");
}
public static function mappingNotFound($fieldName)
public static function mappingNotFound($className, $fieldName)
{
return new self("No mapping found for field '$fieldName'.");
return new self("No mapping found for field '$fieldName' on class '$className'.");
}
public static function oneToManyRequiresMappedBy($fieldName)
@ -170,9 +170,16 @@ class MappingException extends \Doctrine\ORM\ORMException
);
}
public static function fileMappingDriversRequireConfiguredDirectoryPath()
public static function fileMappingDriversRequireConfiguredDirectoryPath($path = null)
{
return new self('File mapping drivers must have a valid directory path, however the given path seems to be incorrect!');
if ( ! empty($path)) {
$path = '[' . $path . ']';
}
return new self(
'File mapping drivers must have a valid directory path, ' .
'however the given path ' . $path . ' seems to be incorrect!'
);
}
/**
@ -200,6 +207,16 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self("Entity class '$className' is using inheritance but no discriminator column was defined.");
}
public static function invalidDiscriminatorColumnType($className, $type)
{
return new self("Discriminator column type on entity class '$className' is not allowed to be '$type'. 'string' or 'integer' type variables are suggested!");
}
public static function cannotVersionIdField($className, $fieldName)
{
return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported.");
}
/**
* @param string $className
* @param string $columnName
@ -209,4 +226,9 @@ class MappingException extends \Doctrine\ORM\ORMException
{
return new self("Duplicate definition of column '".$columnName."' on entity '".$className."' in a field or discriminator column mapping.");
}
public static function illegalToManyAssocationOnMappedSuperclass($className, $field)
{
return new self("It is illegal to put a one-to-many or many-to-many association on mapped superclass '".$className."#".$field."'.");
}
}

View File

@ -78,6 +78,14 @@ class ORMException extends Exception
);
}
public static function invalidFindByInverseAssociation($entityName, $associationFieldName)
{
return new self(
"You cannot search for the association field '".$entityName."#".$associationFieldName."', ".
"because it is the inverse side of an association. Find methods only work on owning side associations."
);
}
public static function invalidResultCacheDriver() {
return new self("Invalid result cache driver; it must implement \Doctrine\Common\Cache\Cache.");
}

View File

@ -196,7 +196,7 @@ final class PersistentCollection implements Collection
* Initializes the collection by loading its contents from the database
* if the collection is not yet initialized.
*/
private function initialize()
public function initialize()
{
if ( ! $this->initialized && $this->association) {
if ($this->isDirty) {

View File

@ -33,11 +33,18 @@ use Doctrine\ORM\Mapping\ClassMetadata,
abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
{
/**
* Map from column names to class names that declare the field the column is mapped to.
* Map from column names to class metadata instances that declare the field the column is mapped to.
*
* @var array
*/
private $_declaringClassMap = array();
private $declaringClassMap = array();
/**
* Map from column names to class names that declare the field the association with join column is mapped to.
*
* @var array
*/
private $declaringJoinColumnMap = array();
/**
* {@inheritdoc}
@ -70,8 +77,8 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
unset($sqlResult[$discrColumnName]);
foreach ($sqlResult as $column => $value) {
$realColumnName = $this->_resultColumnNames[$column];
if (isset($this->_declaringClassMap[$column])) {
$class = $this->_declaringClassMap[$column];
if (isset($this->declaringClassMap[$column])) {
$class = $this->declaringClassMap[$column];
if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
$field = $class->fieldNames[$realColumnName];
if (isset($data[$field])) {
@ -81,6 +88,10 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
->convertToPHPValue($value, $this->_platform);
}
}
} else if (isset($this->declaringJoinColumnMap[$column])) {
if ($this->declaringJoinColumnMap[$column] == $entityName || is_subclass_of($entityName, $this->declaringJoinColumnMap[$column])) {
$data[$realColumnName] = $value;
}
} else {
$data[$realColumnName] = $value;
}
@ -99,9 +110,21 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
if ( ! isset($this->_resultColumnNames[$columnAlias])) {
$this->_resultColumnNames[$columnAlias] = $columnName;
$this->_declaringClassMap[$columnAlias] = $class;
$this->declaringClassMap[$columnAlias] = $class;
}
return "$sql AS $columnAlias";
}
protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className)
{
$columnAlias = $joinColumnName . $this->_sqlAliasCounter++;
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
$this->_resultColumnNames[$resultColumnName] = $joinColumnName;
$this->declaringJoinColumnMap[$resultColumnName] = $className;
}
return $tableAlias . ".$joinColumnName AS $columnAlias";
}
}

View File

@ -251,6 +251,8 @@ class BasicEntityPersister
$sql = "SELECT " . $versionFieldColumnName . " FROM " . $class->getQuotedTableName($this->_platform)
. " WHERE " . implode(' = ? AND ', $identifier) . " = ?";
$value = $this->_conn->fetchColumn($sql, array_values((array)$id));
$value = Type::getType($class->fieldMappings[$versionField]['type'])->convertToPHPValue($value, $this->_platform);
$this->_class->setFieldValue($entity, $versionField, $value);
}
@ -273,7 +275,15 @@ class BasicEntityPersister
$updateData = $this->_prepareUpdateData($entity);
$tableName = $this->_class->table['name'];
if (isset($updateData[$tableName]) && $updateData[$tableName]) {
$this->_updateTable($entity, $tableName, $updateData[$tableName], $this->_class->isVersioned);
$this->_updateTable(
$entity, $this->_class->getQuotedTableName($this->_platform),
$updateData[$tableName], $this->_class->isVersioned
);
if ($this->_class->isVersioned) {
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
$this->_assignDefaultVersionValue($this->_class, $entity, $id);
}
}
}
@ -282,11 +292,11 @@ class BasicEntityPersister
* The UPDATE can optionally be versioned, which requires the entity to have a version field.
*
* @param object $entity The entity object being updated.
* @param string $tableName The name of the table to apply the UPDATE on.
* @param string $quotedTableName The quoted name of the table to apply the UPDATE on.
* @param array $updateData The map of columns to update (column => value).
* @param boolean $versioned Whether the UPDATE should be versioned.
*/
protected final function _updateTable($entity, $tableName, array $updateData, $versioned = false)
protected final function _updateTable($entity, $quotedTableName, array $updateData, $versioned = false)
{
$set = $params = $types = array();
@ -317,7 +327,7 @@ class BasicEntityPersister
if ($versioned) {
$versionField = $this->_class->versionField;
$versionFieldType = $this->_class->getTypeOfField($versionField);
$versionFieldType = $this->_class->fieldMappings[$versionField]['type'];
$versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform);
if ($versionFieldType == Type::INTEGER) {
$set[] = $versionColumn . ' = ' . $versionColumn . ' + 1';
@ -329,7 +339,7 @@ class BasicEntityPersister
$types[] = $this->_class->fieldMappings[$versionField]['type'];
}
$sql = "UPDATE $tableName SET " . implode(', ', $set)
$sql = "UPDATE $quotedTableName SET " . implode(', ', $set)
. ' WHERE ' . implode(' = ? AND ', $where) . ' = ?';
$result = $this->_conn->executeUpdate($sql, $params, $types);
@ -393,17 +403,7 @@ class BasicEntityPersister
$this->deleteJoinTableRecords($identifier);
$id = array_combine($this->_class->getIdentifierColumnNames(), $identifier);
$this->_conn->delete($this->_class->table['name'], $id);
}
/**
* Gets the ClassMetadata instance of the entity class this persister is used for.
*
* @return Doctrine\ORM\Mapping\ClassMetadata
*/
public function getClassMetadata()
{
return $this->_class;
$this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id);
}
/**
@ -660,7 +660,7 @@ class BasicEntityPersister
if ($found = $this->_em->getUnitOfWork()->tryGetById($joinColumnValues, $targetClass->rootEntityName)) {
$this->_class->reflFields[$field]->setValue($entity, $found);
// Complete inverse side, if necessary.
if ($assoc['inversedBy']) {
if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
$inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($found, $entity);
}
@ -681,6 +681,9 @@ class BasicEntityPersister
}
} else if ($value instanceof PersistentCollection && $value->isInitialized()) {
$value->setInitialized(false);
// no matter if dirty or non-dirty entities are already loaded, smoke them out!
// the beauty of it being, they are still in the identity map
$value->unwrap()->clear();
$newData[$field] = $value;
}
}
@ -848,8 +851,8 @@ class BasicEntityPersister
}
return 'SELECT ' . $this->_getSelectColumnListSQL()
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name)
. $this->_platform->appendLockHint(' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name), $lockMode)
. $joinSql
. ($conditionSql ? ' WHERE ' . $conditionSql : '')
. $orderBySql
@ -1086,7 +1089,7 @@ class BasicEntityPersister
}
$sql = 'SELECT 1 '
. $this->getLockTablesSql()
. $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode)
. ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql;
$params = array_values($criteria);
$this->_conn->executeQuery($sql, $params);
@ -1128,9 +1131,16 @@ class BasicEntityPersister
}
$conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform);
} else if (isset($this->_class->associationMappings[$field])) {
// TODO: Inherited?
// TODO: Composite Keys as Foreign Key PK? That would be super ugly! And should probably be disallowed ;)
if (!$this->_class->associationMappings[$field]['isOwningSide']) {
throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field);
}
if (isset($this->_class->associationMappings[$field]['inherited'])) {
$conditionSql .= $this->_getSQLTableAlias($this->_class->associationMappings[$field]['inherited']) . '.';
} else {
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.';
}
$conditionSql .= $this->_class->associationMappings[$field]['joinColumns'][0]['name'];
} else if ($assoc !== null) {

View File

@ -35,9 +35,18 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
/**
* Map that maps column names to the table names that own them.
* This is mainly a temporary cache, used during a single request.
*
* @var array
*/
private $_owningTableMap = array();
/**
* Map of table to quoted table names.
*
* @var array
*/
private $_quotedTableMap = array();
/**
* {@inheritdoc}
*/
@ -76,16 +85,14 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
{
if (!isset($this->_owningTableMap[$fieldName])) {
if (isset($this->_class->associationMappings[$fieldName]['inherited'])) {
$this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata(
$this->_class->associationMappings[$fieldName]['inherited']
)->table['name'];
$cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']);
} else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) {
$this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata(
$this->_class->fieldMappings[$fieldName]['inherited']
)->table['name'];
$cm = $this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited']);
} else {
$this->_owningTableMap[$fieldName] = $this->_class->table['name'];
$cm = $this->_class;
}
$this->_owningTableMap[$fieldName] = $cm->table['name'];
$this->_quotedTableMap[$cm->table['name']] = $cm->getQuotedTableName($this->_platform);
}
return $this->_owningTableMap[$fieldName];
@ -186,17 +193,21 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$updateData = $this->_prepareUpdateData($entity);
if ($isVersioned = $this->_class->isVersioned) {
$versionedTable = $this->_getVersionedClassMetadata()->table['name'];
$versionedClass = $this->_getVersionedClassMetadata();
$versionedTable = $versionedClass->table['name'];
}
if ($updateData) {
foreach ($updateData as $tableName => $data) {
$this->_updateTable($entity, $tableName, $data, $isVersioned && $versionedTable == $tableName);
$this->_updateTable($entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName);
}
// Make sure the table with the version column is updated even if no columns on that
// table were affected.
if ($isVersioned && ! isset($updateData[$versionedTable])) {
$this->_updateTable($entity, $versionedTable, array(), true);
$this->_updateTable($entity, $versionedClass->getQuotedTableName($this->_platform), array(), true);
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
$this->_assignDefaultVersionValue($this->_class, $entity, $id);
}
}
}
@ -252,12 +263,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$this->_getSQLTableAlias($assoc2['inherited'])
: $baseTableAlias;
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= ", $tableAlias.$srcColumn AS $columnAlias";
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
}
if ($columnList != '') $columnList .= ', ';
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name
);
}
}
}
@ -307,12 +316,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE
&& ! isset($assoc2['inherited'])) {
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= ', ' . $tableAlias . ".$srcColumn AS $columnAlias";
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
}
if ($columnList != '') $columnList .= ', ';
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name
);
}
}
}
@ -354,6 +361,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
*/
public function getLockTablesSql()
{
$idColumns = $this->_class->getIdentifierColumnNames();
$baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
// INNER JOIN parent tables

View File

@ -63,12 +63,10 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
foreach ($subClass->associationMappings as $assoc) {
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
$columnList .= ', ' . $tableAlias . ".$srcColumn AS $columnAlias";
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
}
if ($columnList != '') $columnList .= ', ';
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
isset($assoc['inherited']) ? $assoc['inherited'] : $this->_class->name
);
}
}
}

View File

@ -77,9 +77,11 @@ class ProxyFactory
$proxyClassName = str_replace('\\', '', $className) . 'Proxy';
$fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
if ($this->_autoGenerate && ! class_exists($fqn, false)) {
if (! class_exists($fqn, false)) {
$fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php';
if ($this->_autoGenerate) {
$this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate);
}
require $fileName;
}

View File

@ -242,10 +242,14 @@ final class Query extends AbstractQuery
}
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
//TODO: Check that $value is MANAGED?
$values = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
$idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
} else {
$class = $this->_em->getClassMetadata(get_class($value));
$idValues = $class->getIdentifierValues($value);
}
$sqlPositions = $paramMappings[$key];
$sqlParams += array_combine((array)$sqlPositions, $values);
$sqlParams += array_combine((array)$sqlPositions, $idValues);
} else {
foreach ($paramMappings[$key] as $position) {
$sqlParams[$position] = $value;
@ -544,4 +548,15 @@ final class Query extends AbstractQuery
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
);
}
/**
* Cleanup Query resource when clone is called.
*
* @return void
*/
public function __clone()
{
parent::__clone();
$this->_state = self::STATE_DIRTY;
}
}

View File

@ -41,8 +41,9 @@ class LengthFunction extends FunctionNode
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
//TODO: Use platform to get SQL
return 'LENGTH(' . $sqlWalker->walkStringPrimary($this->stringPrimary) . ')';
return $sqlWalker->getConnection()->getDatabasePlatform()->getLengthExpression(
$sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary)
);
}
/**

View File

@ -41,8 +41,9 @@ class LowerFunction extends FunctionNode
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
//TODO: Use platform to get SQL
return 'LOWER(' . $sqlWalker->walkStringPrimary($this->stringPrimary) . ')';
return $sqlWalker->getConnection()->getDatabasePlatform()->getLowerExpression(
$sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary)
);
}
/**

View File

@ -42,12 +42,10 @@ class ModFunction extends FunctionNode
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
//TODO: Use platform to get SQL
return 'MOD('
. $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression)
. ', '
. $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression)
. ')';
return $sqlWalker->getConnection()->getDatabasePlatform()->getModExpression(
$sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression),
$sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression)
);
}
/**

View File

@ -41,8 +41,9 @@ class UpperFunction extends FunctionNode
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
//TODO: Use platform to get SQL
return 'UPPER(' . $sqlWalker->walkStringPrimary($this->stringPrimary) . ')';
return $sqlWalker->getConnection()->getDatabasePlatform()->getUpperExpression(
$sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary)
);
}
/**

View File

@ -125,6 +125,11 @@ class Parser
*/
private $_customOutputWalker;
/**
* @var array
*/
private $_identVariableExpressions = array();
/**
* Creates a new query parser object.
*
@ -272,6 +277,9 @@ class Parser
{
$AST = $this->getAST();
$this->fixIdentificationVariableOrder($AST);
$this->assertSelectEntityRootAliasRequirement();
if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
$this->_customTreeWalkers = $customWalkers;
}
@ -313,6 +321,46 @@ class Parser
return $this->_parserResult;
}
private function assertSelectEntityRootAliasRequirement()
{
if ( count($this->_identVariableExpressions) > 0) {
$foundRootEntity = false;
foreach ($this->_identVariableExpressions AS $dqlAlias => $expr) {
if (isset($this->_queryComponents[$dqlAlias]) && $this->_queryComponents[$dqlAlias]['parent'] === null) {
$foundRootEntity = true;
}
}
if (!$foundRootEntity) {
$this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
}
}
}
/**
* Fix order of identification variables.
*
* They have to appear in the select clause in the same order as the
* declarations (from ... x join ... y join ... z ...) appear in the query
* as the hydration process relies on that order for proper operation.
*
* @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
* @return void
*/
private function fixIdentificationVariableOrder($AST)
{
if ( count($this->_identVariableExpressions) > 1) {
foreach ($this->_queryComponents as $dqlAlias => $qComp) {
if (isset($this->_identVariableExpressions[$dqlAlias])) {
$expr = $this->_identVariableExpressions[$dqlAlias];
$key = array_search($expr, $AST->selectClause->selectExpressions);
unset($AST->selectClause->selectExpressions[$key]);
$AST->selectClause->selectExpressions[] = $expr;
}
}
}
}
/**
* Generates a new syntax error.
*
@ -1628,6 +1676,7 @@ class Parser
public function SelectExpression()
{
$expression = null;
$identVariable = null;
$fieldAliasIdentificationVariable = null;
$peek = $this->_lexer->glimpse();
@ -1639,7 +1688,7 @@ class Parser
$expression = $this->ScalarExpression();
} else {
$supportsAlias = false;
$expression = $this->IdentificationVariable();
$expression = $identVariable = $this->IdentificationVariable();
}
} else if ($this->_lexer->lookahead['value'] == '(') {
if ($peek['type'] == Lexer::T_SELECT) {
@ -1666,6 +1715,7 @@ class Parser
} else if ($this->_lexer->lookahead['type'] == Lexer::T_PARTIAL) {
$supportsAlias = false;
$expression = $this->PartialObjectExpression();
$identVariable = $expression->identificationVariable;
} else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER ||
$this->_lexer->lookahead['type'] == Lexer::T_FLOAT) {
// Shortcut: ScalarExpression => SimpleArithmeticExpression
@ -1694,7 +1744,11 @@ class Parser
}
}
return new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
$expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
if (!$supportsAlias) {
$this->_identVariableExpressions[$identVariable] = $expr;
}
return $expr;
}
/**

View File

@ -689,7 +689,7 @@ class SqlWalker implements TreeWalker
$sql = $this->_scalarResultAliasMap[$columnName];
}
return $sql . ' ' . strtoupper($orderByItem->type);;
return $sql . ' ' . strtoupper($orderByItem->type);
}
/**

View File

@ -888,6 +888,40 @@ class QueryBuilder
. (isset($options['post']) ? $options['post'] : '');
}
/**
* Reset DQL parts
*
* @param array $parts
* @return QueryBuilder
*/
public function resetDQLParts($parts = null)
{
if (is_null($parts)) {
$parts = array_keys($this->_dqlParts);
}
foreach ($parts as $part) {
$this->resetDQLPart($part);
}
return $this;
}
/**
* Reset single DQL part
*
* @param string $part
* @return QueryBuilder;
*/
public function resetDQLPart($part)
{
if (is_array($this->_dqlParts[$part])) {
$this->_dqlParts[$part] = array();
} else {
$this->_dqlParts[$part] = null;
}
$this->_state = self::STATE_DIRTY;
return $this;
}
/**
* Gets a string representation of this QueryBuilder which corresponds to
* the final DQL query being constructed.
@ -898,4 +932,24 @@ class QueryBuilder
{
return $this->getDQL();
}
/**
* Deep clone of all expression objects in the DQL parts.
*
* @return void
*/
public function __clone()
{
foreach ($this->_dqlParts AS $part => $elements) {
if (is_array($this->_dqlParts[$part])) {
foreach ($this->_dqlParts[$part] AS $idx => $element) {
if (is_object($element)) {
$this->_dqlParts[$part][$idx] = clone $element;
}
}
} else if (\is_object($elements)) {
$this->_dqlParts[$part] = clone $elements;
}
}
}
}

View File

@ -49,19 +49,19 @@ class ResultCommand extends Console\Command\Command
->setDescription('Clear result cache of the various cache drivers.')
->setDefinition(array(
new InputOption(
'id', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
'id', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'ID(s) of the cache entry to delete (accepts * wildcards).', array()
),
new InputOption(
'regex', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
'regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Delete cache entries that match the given regular expression(s).', array()
),
new InputOption(
'prefix', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
'prefix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Delete cache entries that have the given prefix(es).', array()
),
new InputOption(
'suffix', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
'suffix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Delete cache entries that have the given suffix(es).', array()
),
))

View File

@ -112,16 +112,16 @@ class ConvertDoctrine1SchemaCommand extends Console\Command\Command
'The path to generate your Doctrine 2.X mapping information.'
),
new InputOption(
'from', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
'from', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Optional paths of Doctrine 1.X schema information.',
array()
),
new InputOption(
'extend', null, InputOption::PARAMETER_OPTIONAL,
'extend', null, InputOption::VALUE_OPTIONAL,
'Defines a base class to be extended by generated entity classes.'
),
new InputOption(
'num-spaces', null, InputOption::PARAMETER_OPTIONAL,
'num-spaces', null, InputOption::VALUE_OPTIONAL,
'Defines the number of indentation spaces', 4
)
))

View File

@ -53,7 +53,7 @@ class ConvertMappingCommand extends Console\Command\Command
->setDescription('Convert mapping information between supported formats.')
->setDefinition(array(
new InputOption(
'filter', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'A string pattern used to match entities that should be processed.'
),
new InputArgument(
@ -67,11 +67,11 @@ class ConvertMappingCommand extends Console\Command\Command
'from-database', null, null, 'Whether or not to convert mapping information from existing database.'
),
new InputOption(
'extend', null, InputOption::PARAMETER_OPTIONAL,
'extend', null, InputOption::VALUE_OPTIONAL,
'Defines a base class to be extended by generated entity classes.'
),
new InputOption(
'num-spaces', null, InputOption::PARAMETER_OPTIONAL,
'num-spaces', null, InputOption::VALUE_OPTIONAL,
'Defines the number of indentation spaces', 4
)
))
@ -96,7 +96,8 @@ EOT
);
}
$cmf = new DisconnectedClassMetadataFactory($em);
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadata = $cmf->getAllMetadata();
$metadata = MetadataFilter::filter($metadata, $input->getOption('filter'));

View File

@ -49,7 +49,7 @@ class EnsureProductionSettingsCommand extends Console\Command\Command
->setDescription('Verify that Doctrine is properly configured for a production environment.')
->setDefinition(array(
new InputOption(
'complete', null, InputOption::PARAMETER_NONE,
'complete', null, InputOption::VALUE_NONE,
'Flag to also inspect database connection existance.'
)
))

View File

@ -52,34 +52,34 @@ class GenerateEntitiesCommand extends Console\Command\Command
->setDescription('Generate entity classes and method stubs from your mapping information.')
->setDefinition(array(
new InputOption(
'filter', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'A string pattern used to match entities that should be processed.'
),
new InputArgument(
'dest-path', InputArgument::REQUIRED, 'The path to generate your entity classes.'
),
new InputOption(
'generate-annotations', null, InputOption::PARAMETER_OPTIONAL,
'generate-annotations', null, InputOption::VALUE_OPTIONAL,
'Flag to define if generator should generate annotation metadata on entities.', false
),
new InputOption(
'generate-methods', null, InputOption::PARAMETER_OPTIONAL,
'generate-methods', null, InputOption::VALUE_OPTIONAL,
'Flag to define if generator should generate stub methods on entities.', true
),
new InputOption(
'regenerate-entities', null, InputOption::PARAMETER_OPTIONAL,
'regenerate-entities', null, InputOption::VALUE_OPTIONAL,
'Flag to define if generator should regenerate entity if it exists.', false
),
new InputOption(
'update-entities', null, InputOption::PARAMETER_OPTIONAL,
'update-entities', null, InputOption::VALUE_OPTIONAL,
'Flag to define if generator should only update entity if it exists.', true
),
new InputOption(
'extend', null, InputOption::PARAMETER_OPTIONAL,
'extend', null, InputOption::VALUE_OPTIONAL,
'Defines a base class to be extended by generated entity classes.'
),
new InputOption(
'num-spaces', null, InputOption::PARAMETER_OPTIONAL,
'num-spaces', null, InputOption::VALUE_OPTIONAL,
'Defines the number of indentation spaces', 4
)
))
@ -96,7 +96,8 @@ EOT
{
$em = $this->getHelper('em')->getEntityManager();
$cmf = new DisconnectedClassMetadataFactory($em);
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadatas = $cmf->getAllMetadata();
$metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter'));

View File

@ -50,7 +50,7 @@ class GenerateProxiesCommand extends Console\Command\Command
->setDescription('Generates proxy classes for entity classes.')
->setDefinition(array(
new InputOption(
'filter', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'A string pattern used to match entities that should be processed.'
),
new InputArgument(

View File

@ -51,7 +51,7 @@ class GenerateRepositoriesCommand extends Console\Command\Command
->setDescription('Generate repository classes from your mapping information.')
->setDefinition(array(
new InputOption(
'filter', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'A string pattern used to match entities that should be processed.'
),
new InputArgument(

View File

@ -50,20 +50,20 @@ class RunDqlCommand extends Console\Command\Command
->setDefinition(array(
new InputArgument('dql', InputArgument::REQUIRED, 'The DQL to execute.'),
new InputOption(
'hydrate', null, InputOption::PARAMETER_REQUIRED,
'hydrate', null, InputOption::VALUE_REQUIRED,
'Hydration mode of result set. Should be either: object, array, scalar or single-scalar.',
'object'
),
new InputOption(
'first-result', null, InputOption::PARAMETER_REQUIRED,
'first-result', null, InputOption::VALUE_REQUIRED,
'The first result in the result set.'
),
new InputOption(
'max-result', null, InputOption::PARAMETER_REQUIRED,
'max-result', null, InputOption::VALUE_REQUIRED,
'The maximum number of results in the result set.'
),
new InputOption(
'depth', null, InputOption::PARAMETER_REQUIRED,
'depth', null, InputOption::VALUE_REQUIRED,
'Dumping depth of Entity graph.', 7
)
))
@ -114,7 +114,7 @@ EOT
throw new \LogicException("Option 'max-result' must contains an integer value");
}
$query->setMaxResult((int) $maxResult);
$query->setMaxResults((int) $maxResult);
}
$resultSet = $query->execute(array(), constant($hydrationMode));

View File

@ -53,7 +53,7 @@ class CreateCommand extends AbstractCommand
)
->setDefinition(array(
new InputOption(
'dump-sql', null, InputOption::PARAMETER_NONE,
'dump-sql', null, InputOption::VALUE_NONE,
'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.'
)
))
@ -65,6 +65,8 @@ EOT
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas)
{
$output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL . PHP_EOL);
if ($input->getOption('dump-sql') === true) {
$sqls = $schemaTool->getCreateSchemaSql($metadatas);
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);

View File

@ -49,13 +49,21 @@ class DropCommand extends AbstractCommand
$this
->setName('orm:schema-tool:drop')
->setDescription(
'Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output.'
'Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output.'
)
->setDefinition(array(
new InputOption(
'dump-sql', null, InputOption::PARAMETER_NONE,
'dump-sql', null, InputOption::VALUE_NONE,
'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.'
)
),
new InputOption(
'force', null, InputOption::VALUE_NONE,
"Don't ask for the deletion of the database, but force the operation to run."
),
new InputOption(
'full-database', null, InputOption::VALUE_NONE,
'Instead of using the Class Metadata to detect the database table schema, drop ALL assets that the database contains.'
),
))
->setHelp(<<<EOT
Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output.
@ -66,13 +74,38 @@ EOT
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas)
{
$isFullDatabaseDrop = ($input->getOption('full-database'));
if ($input->getOption('dump-sql') === true) {
$sqls = $schemaTool->getDropSchemaSql($metadatas);
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
if ($isFullDatabaseDrop) {
$sqls = $schemaTool->getDropDatabaseSQL();
} else {
$sqls = $schemaTool->getDropSchemaSQL($metadatas);
}
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
} else if ($input->getOption('force') === true) {
$output->write('Dropping database schema...' . PHP_EOL);
if ($isFullDatabaseDrop) {
$schemaTool->dropDatabase();
} else {
$schemaTool->dropSchema($metadatas);
}
$output->write('Database schema dropped successfully!' . PHP_EOL);
} else {
$output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL . PHP_EOL);
if ($isFullDatabaseDrop) {
$sqls = $schemaTool->getDropDatabaseSQL();
} else {
$sqls = $schemaTool->getDropSchemaSQL($metadatas);
}
if (count($sqls)) {
$output->write('Schema-Tool would execute ' . count($sqls) . ' queries to drop the database.' . PHP_EOL);
$output->write('Please run the operation with --force to execute these queries or use --dump-sql to see them.' . PHP_EOL);
} else {
$output->write('Nothing to drop. The database is empty!' . PHP_EOL);
}
}
}
}

View File

@ -53,13 +53,17 @@ class UpdateCommand extends AbstractCommand
)
->setDefinition(array(
new InputOption(
'complete', null, InputOption::PARAMETER_NONE,
'complete', null, InputOption::VALUE_NONE,
'If defined, all assets of the database which are not relevant to the current metadata will be dropped.'
),
new InputOption(
'dump-sql', null, InputOption::PARAMETER_NONE,
'dump-sql', null, InputOption::VALUE_NONE,
'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.'
)
),
new InputOption(
'force', null, InputOption::VALUE_NONE,
"Don't ask for the incremental update of the database, but force the operation to run."
),
))
->setHelp(<<<EOT
Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.
@ -72,15 +76,28 @@ EOT
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas)
{
// Defining if update is complete or not (--complete not defined means $saveMode = true)
$saveMode = ($input->getOption('complete') === true);
$saveMode = ($input->getOption('complete') !== true);
if ($input->getOption('dump-sql') === true) {
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
} else {
} else if ($input->getOption('force') === true) {
$output->write('Updating database schema...' . PHP_EOL);
$schemaTool->updateSchema($metadatas, $saveMode);
$output->write('Database schema updated successfully!' . PHP_EOL);
} else {
$output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL);
$output->write('Use the incremental update to detect changes during development and use' . PHP_EOL);
$output->write('this SQL DDL to manually update your database in production.' . PHP_EOL . PHP_EOL);
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
if (count($sqls)) {
$output->write('Schema-Tool would execute ' . count($sqls) . ' queries to update the database.' . PHP_EOL);
$output->write('Please run the operation with --force to execute these queries or use --dump-sql to see them.' . PHP_EOL);
} else {
$output->write('Nothing to update. The database is in sync with the current entity metadata.' . PHP_EOL);
}
}
}
}

View File

@ -0,0 +1,69 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Tools\Console;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
class ConsoleRunner
{
/**
* Run console with the given helperset.
*
* @param \Symfony\Component\Console\Helper\HelperSet $helperSet
* @return void
*/
static public function run(HelperSet $helperSet)
{
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);
self::addCommands($cli);
$cli->run();
}
/**
* @param Application $cli
*/
static public function addCommands(Application $cli)
{
$cli->addCommands(array(
// DBAL Commands
new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(),
new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(),
// ORM Commands
new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand(),
new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand(),
new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand(),
new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand(),
new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(),
new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(),
new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(),
new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(),
));
}
}

View File

@ -188,7 +188,6 @@ class ConvertDoctrine1Schema
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
} else if (isset($column['sequence'])) {
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE);
$metadata->setSequenceGeneratorDefinition($definition);
$definition = array(
'sequenceName' => is_array($column['sequence']) ? $column['sequence']['name']:$column['sequence']
);
@ -198,6 +197,7 @@ class ConvertDoctrine1Schema
if (isset($column['sequence']['value'])) {
$definition['initialValue'] = $column['sequence']['value'];
}
$metadata->setSequenceGeneratorDefinition($definition);
}
return $fieldMapping;
}

View File

@ -1,7 +1,5 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -33,7 +31,6 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo;
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
@ -46,7 +43,13 @@ class DisconnectedClassMetadataFactory extends ClassMetadataFactory
*/
protected function newClassMetadataInstance($className)
{
return new ClassMetadataInfo($className);
$metadata = new ClassMetadataInfo($className);
if (strpos($className, "\\") !== false) {
$metadata->namespace = strrev(substr( strrev($className), strpos(strrev($className), "\\")+1 ));
} else {
$metadata->namespace = "";
}
return $metadata;
}
/**

View File

@ -1,7 +1,5 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -82,7 +80,7 @@ class EntityGenerator
private static $_classTemplate =
'<?php
<namespace><use>
<namespace>
<entityAnnotation>
<entityClassName>
@ -189,7 +187,6 @@ public function <methodName>()
{
$placeHolders = array(
'<namespace>',
'<use>',
'<entityAnnotation>',
'<entityClassName>',
'<entityBody>'
@ -197,7 +194,6 @@ public function <methodName>()
$replacements = array(
$this->_generateEntityNamespace($metadata),
$this->_generateEntityUse($metadata),
$this->_generateEntityDocBlock($metadata),
$this->_generateEntityClassName($metadata),
$this->_generateEntityBody($metadata)
@ -222,7 +218,7 @@ public function <methodName>()
$body = str_replace('<spaces>', $this->_spaces, $body);
$last = strrpos($currentCode, '}');
return substr($currentCode, 0, $last) . $body . "\n}";
return substr($currentCode, 0, $last) . $body . (strlen($body) > 0 ? "\n" : ''). "}";
}
/**
@ -309,13 +305,6 @@ public function <methodName>()
}
}
private function _generateEntityUse(ClassMetadataInfo $metadata)
{
if ($this->_extendsClass()) {
return "\n\nuse " . $this->_getClassToExtendNamespace() . ";\n";
}
}
private function _generateEntityClassName(ClassMetadataInfo $metadata)
{
return 'class ' . $this->_getClassName($metadata) .
@ -379,14 +368,7 @@ public function <methodName>()
{
$refl = new \ReflectionClass($this->_getClassToExtend());
return $refl->getName();
}
private function _getClassToExtendNamespace()
{
$refl = new \ReflectionClass($this->_getClassToExtend());
return $refl->getNamespaceName() ? $refl->getNamespaceName():$refl->getShortName();
return '\\' . $refl->getName();
}
private function _getClassName(ClassMetadataInfo $metadata)

View File

@ -46,11 +46,14 @@ class XmlExporter extends AbstractExporter
*/
public function exportClassMetadata(ClassMetadataInfo $metadata)
{
$xml = new \SimpleXmlElement("<?xml version=\"1.0\" encoding=\"utf-8\"?><doctrine-mapping/>");
$xml = new \SimpleXmlElement("<?xml version=\"1.0\" encoding=\"utf-8\"?><doctrine-mapping ".
"xmlns=\"http://doctrine-project.org/schemas/orm/doctrine-mapping\" " .
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ".
"xsi:schemaLocation=\"http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd\" />");
$xml->addAttribute('xmlns', 'http://doctrine-project.org/schemas/orm/doctrine-mapping');
/*$xml->addAttribute('xmlns', 'http://doctrine-project.org/schemas/orm/doctrine-mapping');
$xml->addAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$xml->addAttribute('xsi:schemaLocation', 'http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd');
$xml->addAttribute('xsi:schemaLocation', 'http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd');*/
if ($metadata->isMappedSuperclass) {
$root = $xml->addChild('mapped-superclass');
@ -109,7 +112,7 @@ class XmlExporter extends AbstractExporter
foreach ($metadata->table['uniqueConstraints'] as $unique) {
$uniqueConstraintXml = $uniqueConstraintsXml->addChild('unique-constraint');
$uniqueConstraintXml->addAttribute('name', $name);
$uniqueConstraintXml->addAttribute('name', $unique['name']);
$uniqueConstraintXml->addAttribute('columns', implode(',', $unique['columns']));
}
}
@ -128,6 +131,21 @@ class XmlExporter extends AbstractExporter
$id[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType;
}
if ($id) {
foreach ($id as $field) {
$idXml = $root->addChild('id');
$idXml->addAttribute('name', $field['fieldName']);
$idXml->addAttribute('type', $field['type']);
if (isset($field['columnName'])) {
$idXml->addAttribute('column', $field['columnName']);
}
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$generatorXml = $idXml->addChild('generator');
$generatorXml->addAttribute('strategy', $idGeneratorType);
}
}
}
if ($fields) {
foreach ($fields as $field) {
$fieldXml = $root->addChild('field');
@ -163,21 +181,6 @@ class XmlExporter extends AbstractExporter
}
}
if ($id) {
foreach ($id as $field) {
$idXml = $root->addChild('id');
$idXml->addAttribute('name', $field['fieldName']);
$idXml->addAttribute('type', $field['type']);
if (isset($field['columnName'])) {
$idXml->addAttribute('column', $field['columnName']);
}
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$generatorXml = $idXml->addChild('generator');
$generatorXml->addAttribute('strategy', $idGeneratorType);
}
}
}
foreach ($metadata->associationMappings as $name => $associationMapping) {
if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_ONE) {
$associationMappingXml = $root->addChild('one-to-one');
@ -305,47 +308,16 @@ class XmlExporter extends AbstractExporter
}
/**
* Code originally taken from
* http://recurser.com/articles/2007/04/05/format-xml-with-php/
*
* @param string $simpleXml
* @param \SimpleXMLElement $simpleXml
* @return string $xml
*/
private function _asXml($simpleXml)
{
$xml = $simpleXml->asXml();
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->loadXML($simpleXml->asXML());
$dom->formatOutput = true;
// add marker linefeeds to aid the pretty-tokeniser (adds a linefeed between all tag-end boundaries)
$xml = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", $xml);
// now indent the tags
$token = strtok($xml, "\n");
$result = ''; // holds formatted version as it is built
$pad = 0; // initial indent
$matches = array(); // returns from preg_matches()
// test for the various tag states
while ($token !== false) {
// 1. open and closing tags on same line - no change
if (preg_match('/.+<\/\w[^>]*>$/', $token, $matches)) {
$indent = 0;
// 2. closing tag - outdent now
} else if (preg_match('/^<\/\w/', $token, $matches)) {
$pad = $pad - 4;
// 3. opening tag - don't pad this one, only subsequent tags
} elseif (preg_match('/^<\w[^>]*[^\/]>.*$/', $token, $matches)) {
$indent = 4;
// 4. no indentation needed
} else {
$indent = 0;
}
// pad the line with the required number of leading spaces
$line = str_pad($token, strlen($token)+$pad, ' ', STR_PAD_LEFT);
$result .= $line . "\n"; // add to the cumulative result, with linefeed
$token = strtok("\n"); // get the next token
$pad += $indent; // update the pad size for subsequent lines
}
$result = $dom->saveXML();
return $result;
}
}

View File

@ -89,11 +89,6 @@ class YamlExporter extends AbstractExporter
$ids = array();
foreach ($fieldMappings as $name => $fieldMapping) {
if (isset($fieldMapping['length'])) {
$fieldMapping['type'] = $fieldMapping['type'] . '(' . $fieldMapping['length'] . ')';
unset($fieldMapping['length']);
}
$fieldMapping['column'] = $fieldMapping['columnName'];
unset(
$fieldMapping['columnName'],

View File

@ -1,7 +1,5 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -94,6 +92,22 @@ class SchemaTool
return $schema->toSql($this->_platform);
}
/**
* Some instances of ClassMetadata don't need to be processed in the SchemaTool context. This method detects them.
*
* @param ClassMetadata $class
* @param array $processedClasses
* @return bool
*/
private function processingNotRequired($class, array $processedClasses)
{
return (
isset($processedClasses[$class->name]) ||
$class->isMappedSuperclass ||
($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName)
);
}
/**
* From a given set of metadata classes this method creates a Schema instance.
*
@ -112,19 +126,12 @@ class SchemaTool
$evm = $this->_em->getEventManager();
foreach ($classes as $class) {
if (isset($processedClasses[$class->name]) || $class->isMappedSuperclass) {
if ($this->processingNotRequired($class, $processedClasses)) {
continue;
}
$table = $schema->createTable($class->getQuotedTableName($this->_platform));
// TODO: Remove
/**if ($class->isIdGeneratorIdentity()) {
$table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_IDENTITY);
} else if ($class->isIdGeneratorSequence()) {
$table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_SEQUENCE);
}*/
$columns = array(); // table columns
if ($class->isInheritanceTypeSingleTable()) {
@ -171,16 +178,14 @@ class SchemaTool
$idMapping = $class->fieldMappings[$class->identifier[0]];
$this->_gatherColumn($class, $idMapping, $table);
$columnName = $class->getQuotedColumnName($class->identifier[0], $this->_platform);
// TODO: This seems rather hackish, can we optimize it?
$table->getColumn($class->identifier[0])->setAutoincrement(false);
$pkColumns[] = $columnName;
// TODO: REMOVE
/*if ($table->isIdGeneratorIdentity()) {
$table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_NONE);
}*/
// Add a FK constraint on the ID column
$table->addUnnamedForeignKeyConstraint(
$this->_em->getClassMetadata($class->rootEntityName)->getQuotedTableName($this->_platform),
$this->_em->getClassMetadata($class->rootEntityName)->getTableName(),
array($columnName), array($columnName), array('onDelete' => 'CASCADE')
);
}
@ -318,6 +323,9 @@ class SchemaTool
$options = array();
$options['length'] = isset($mapping['length']) ? $mapping['length'] : null;
$options['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : true;
if ($class->isInheritanceTypeSingleTable() && count($class->parentClasses) > 0) {
$options['notnull'] = false;
}
$options['platformOptions'] = array();
$options['platformOptions']['version'] = $class->isVersioned && $class->versionField == $mapping['fieldName'] ? true : false;
@ -345,6 +353,9 @@ class SchemaTool
if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == array($mapping['fieldName'])) {
$options['autoincrement'] = true;
}
if ($class->isInheritanceTypeJoined() && $class->name != $class->rootEntityName) {
$options['autoincrement'] = false;
}
if ($table->hasColumn($columnName)) {
// required in some inheritance scenarios
@ -459,6 +470,12 @@ class SchemaTool
if (isset($joinColumn['nullable'])) {
$columnOptions['notnull'] = !$joinColumn['nullable'];
}
if ($fieldMapping['type'] == "string") {
$columnOptions['length'] = $fieldMapping['length'];
} else if ($fieldMapping['type'] == "decimal") {
$columnOptions['scale'] = $fieldMapping['scale'];
$columnOptions['precision'] = $fieldMapping['precision'];
}
$theJoinTable->addColumn(
$columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions
@ -479,7 +496,7 @@ class SchemaTool
}
$theJoinTable->addUnnamedForeignKeyConstraint(
$class->getQuotedTableName($this->_platform), $localColumns, $foreignColumns, $fkOptions
$class->getTableName(), $localColumns, $foreignColumns, $fkOptions
);
}
@ -494,7 +511,26 @@ class SchemaTool
*/
public function dropSchema(array $classes)
{
$dropSchemaSql = $this->getDropSchemaSql($classes);
$dropSchemaSql = $this->getDropSchemaSQL($classes);
$conn = $this->_em->getConnection();
foreach ($dropSchemaSql as $sql) {
try {
$conn->executeQuery($sql);
} catch(\Exception $e) {
}
}
}
/**
* Drops all elements in the database of the current connection.
*
* @return void
*/
public function dropDatabase()
{
$dropSchemaSql = $this->getDropDatabaseSQL();
$conn = $this->_em->getConnection();
foreach ($dropSchemaSql as $sql) {
@ -503,12 +539,11 @@ class SchemaTool
}
/**
* Gets the SQL needed to drop the database schema for the given classes.
* Gets the SQL needed to drop the database schema for the connections database.
*
* @param array $classes
* @return array
*/
public function getDropSchemaSql(array $classes)
public function getDropDatabaseSQL()
{
$sm = $this->_em->getConnection()->getSchemaManager();
$schema = $sm->createSchema();
@ -520,40 +555,32 @@ class SchemaTool
}
/**
* Drop all tables of the database connection.
*
* @param array $classes
* @return array
*/
private function _getDropSchemaTablesDatabaseMode($classes)
public function getDropSchemaSQL(array $classes)
{
$conn = $this->_em->getConnection();
$sm = $this->_em->getConnection()->getSchemaManager();
$sm = $conn->getSchemaManager();
/* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */
$allTables = $sm->listTables();
$orderedTables = $this->_getDropSchemaTablesMetadataMode($classes);
foreach($allTables AS $tableName) {
if(!in_array($tableName, $orderedTables)) {
$orderedTables[] = $tableName;
}
}
return $orderedTables;
}
private function _getDropSchemaTablesMetadataMode(array $classes)
{
$sql = array();
$orderedTables = array();
foreach ($classes AS $class) {
if ($class->isIdGeneratorSequence() && !$class->isMappedSuperclass && $class->name == $class->rootEntityName && $this->_platform->supportsSequences()) {
$sql[] = $this->_platform->getDropSequenceSQL($class->sequenceGeneratorDefinition['sequenceName']);
}
}
$commitOrder = $this->_getCommitOrder($classes);
$associationTables = $this->_getAssociationTables($commitOrder);
// Drop association tables first
foreach ($associationTables as $associationTable) {
if (!in_array($associationTable, $orderedTables)) {
$orderedTables[] = $associationTable;
}
}
// Drop tables in reverse commit order
for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
@ -564,17 +591,27 @@ class SchemaTool
continue;
}
if (!in_array($class->getTableName(), $orderedTables)) {
$orderedTables[] = $class->getTableName();
}
}
//TODO: Drop other schema elements, like sequences etc.
$dropTablesSql = array();
foreach ($orderedTables AS $tableName) {
/* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */
$foreignKeys = $sm->listTableForeignKeys($tableName);
foreach ($foreignKeys AS $foreignKey) {
$sql[] = $this->_platform->getDropForeignKeySQL($foreignKey, $tableName);
}
$dropTablesSql[] = $this->_platform->getDropTableSQL($tableName);
}
return $orderedTables;
return array_merge($sql, $dropTablesSql);
}
/**
* Updates the database schema of the given classes by comparing the ClassMetadata
* instances to the current database schema that is inspected.
* ins$tableNametances to the current database schema that is inspected.
*
* @param array $classes
* @return void
@ -645,7 +682,7 @@ class SchemaTool
foreach ($classes as $class) {
foreach ($class->associationMappings as $assoc) {
if ($assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY) {
$associationTables[] = $assoc->joinTable['name'];
$associationTables[] = $assoc['joinTable']['name'];
}
}
}

View File

@ -573,7 +573,7 @@ class UnitOfWork implements PropertyChangedListener
$this->computeChangeSet($targetClass, $entry);
} else if ($state == self::STATE_REMOVED) {
return new InvalidArgumentException("Removed entity detected during flush: "
. self::objToStr($removedEntity).". Remove deleted entities from associations.");
. self::objToStr($entry).". Remove deleted entities from associations.");
} else if ($state == self::STATE_DETACHED) {
// Can actually not happen right now as we assume STATE_NEW,
// so the exception will be raised from the DBAL layer (constraint violation).
@ -1437,7 +1437,16 @@ class UnitOfWork implements PropertyChangedListener
$prop->setValue($managedCopy, $managedCol);
$this->originalEntityData[$oid][$name] = $managedCol;
}
$managedCol->setInitialized($assoc2['isCascadeMerge']);
if ($assoc2['isCascadeMerge']) {
$managedCol->initialize();
if (!$managedCol->isEmpty()) {
$managedCol->unwrap()->clear();
$managedCol->setDirty(true);
if ($assoc2['isOwningSide'] && $assoc2['type'] == ClassMetadata::MANY_TO_MANY && $class->isChangeTrackingNotify()) {
$this->scheduleForDirtyCheck($managedCopy);
}
}
}
}
}
if ($class->isChangeTrackingNotify()) {
@ -1456,7 +1465,7 @@ class UnitOfWork implements PropertyChangedListener
if ($assoc['type'] & ClassMetadata::TO_ONE) {
$prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy);
} else {
$prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->unwrap()->add($managedCopy);
$prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy);
if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
$class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy);
}
@ -1500,7 +1509,9 @@ class UnitOfWork implements PropertyChangedListener
switch ($this->getEntityState($entity, self::STATE_DETACHED)) {
case self::STATE_MANAGED:
if ($this->isInIdentityMap($entity)) {
$this->removeFromIdentityMap($entity);
}
unset($this->entityInsertions[$oid], $this->entityUpdates[$oid],
$this->entityDeletions[$oid], $this->entityIdentifiers[$oid],
$this->entityStates[$oid], $this->originalEntityData[$oid]);
@ -1889,7 +1900,7 @@ class UnitOfWork implements PropertyChangedListener
if ($assoc['isOwningSide']) {
$associatedId = array();
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
$joinColumnValue = $data[$srcColumn];
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
if ($joinColumnValue !== null) {
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
}
@ -1937,14 +1948,12 @@ class UnitOfWork implements PropertyChangedListener
}
} else {
// Inject collection
$reflField = $class->reflFields[$field];
$pColl = new PersistentCollection(
$this->em, $targetClass,
//TODO: getValue might be superfluous once DDC-79 is implemented.
$reflField->getValue($entity) ?: new ArrayCollection
);
$pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
$pColl->setOwner($entity, $assoc);
$reflField = $class->reflFields[$field];
$reflField->setValue($entity, $pColl);
if ($assoc['fetch'] == ClassMetadata::FETCH_LAZY) {
$pColl->setInitialized(false);
} else {

View File

@ -36,7 +36,7 @@ class Version
/**
* Current Doctrine Version
*/
const VERSION = '2.0.0BETA4-DEV';
const VERSION = '2.1.0-DEV';
/**
* Compares a Doctrine version with the current one.

View File

@ -19,7 +19,7 @@ use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\DialogHelper;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -37,18 +37,15 @@ use Symfony\Component\Console\Helper\DialogHelper;
* Usage:
*
* $app = new Application('myapp', '1.0 (stable)');
* $app->addCommand(new SimpleCommand());
* $app->add(new SimpleCommand());
* $app->run();
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Application
{
protected $commands;
protected $aliases;
protected $application;
protected $wantHelps = false;
protected $runningCommand;
protected $name;
@ -77,18 +74,18 @@ class Application
new DialogHelper(),
));
$this->addCommand(new HelpCommand());
$this->addCommand(new ListCommand());
$this->add(new HelpCommand());
$this->add(new ListCommand());
$this->definition = new InputDefinition(array(
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
new InputOption('--help', '-h', InputOption::PARAMETER_NONE, 'Display this help message.'),
new InputOption('--quiet', '-q', InputOption::PARAMETER_NONE, 'Do not output any message.'),
new InputOption('--verbose', '-v', InputOption::PARAMETER_NONE, 'Increase verbosity of messages.'),
new InputOption('--version', '-V', InputOption::PARAMETER_NONE, 'Display this program version.'),
new InputOption('--color', '-c', InputOption::PARAMETER_NONE, 'Force ANSI color output.'),
new InputOption('--no-interaction', '-n', InputOption::PARAMETER_NONE, 'Do not ask any interactive question.'),
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message.'),
new InputOption('--verbose', '-v', InputOption::VALUE_NONE, 'Increase verbosity of messages.'),
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this program version.'),
new InputOption('--ansi', '-a', InputOption::VALUE_NONE, 'Force ANSI output.'),
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question.'),
));
}
@ -99,27 +96,23 @@ class Application
* @param OutputInterface $output An Output instance
*
* @return integer 0 if everything went fine, or an error code
*
* @throws \Exception When doRun returns Exception
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
if (null === $input)
{
if (null === $input) {
$input = new ArgvInput();
}
if (null === $output)
{
if (null === $output) {
$output = new ConsoleOutput();
}
try
{
try {
$statusCode = $this->doRun($input, $output);
}
catch (\Exception $e)
{
if (!$this->catchExceptions)
{
} catch (\Exception $e) {
if (!$this->catchExceptions) {
throw $e;
}
@ -129,14 +122,14 @@ class Application
$statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1;
}
if ($this->autoExit)
{
if ($this->autoExit) {
if ($statusCode > 255) {
$statusCode = 255;
}
// @codeCoverageIgnoreStart
exit($statusCode);
// @codeCoverageIgnoreEnd
}
else
{
} else {
return $statusCode;
}
}
@ -151,55 +144,44 @@ class Application
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
$name = $input->getFirstArgument('command');
$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(array('--color', '-c')))
{
if (true === $input->hasParameterOption(array('--ansi', '-a'))) {
$output->setDecorated(true);
}
if (true === $input->hasParameterOption(array('--help', '-H')))
{
if (!$name)
{
if (true === $input->hasParameterOption(array('--help', '-h'))) {
if (!$name) {
$name = 'help';
$input = new ArrayInput(array('command' => 'help'));
}
else
{
} else {
$this->wantHelps = true;
}
}
if (true === $input->hasParameterOption(array('--no-interaction', '-n')))
{
if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
$input->setInteractive(false);
}
if (true === $input->hasParameterOption(array('--quiet', '-q')))
{
if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
$output->setVerbosity(Output::VERBOSITY_QUIET);
}
elseif (true === $input->hasParameterOption(array('--verbose', '-v')))
{
} elseif (true === $input->hasParameterOption(array('--verbose', '-v'))) {
$output->setVerbosity(Output::VERBOSITY_VERBOSE);
}
if (true === $input->hasParameterOption(array('--version', '-V')))
{
if (true === $input->hasParameterOption(array('--version', '-V'))) {
$output->writeln($this->getLongVersion());
return 0;
}
if (!$name)
{
if (!$name) {
$name = 'list';
$input = new ArrayInput(array('command' => 'list'));
}
// the command name MUST be the first element of the input
$command = $this->findCommand($name);
$command = $this->find($name);
$this->runningCommand = $command;
$statusCode = $command->run($input, $output);
@ -221,7 +203,7 @@ class Application
/**
* Get the helper set associated with the command
*
* @return HelperSet The HelperSet isntance associated with this command
* @return HelperSet The HelperSet instance associated with this command
*/
public function getHelperSet()
{
@ -253,8 +235,7 @@ class Application
'<comment>Options:</comment>',
);
foreach ($this->definition->getOptions() as $option)
{
foreach ($this->definition->getOptions() as $option) {
$messages[] = sprintf(' %-29s %s %s',
'<info>--'.$option->getName().'</info>',
$option->getShortcut() ? '<info>-'.$option->getShortcut().'</info>' : ' ',
@ -332,12 +313,9 @@ class Application
*/
public function getLongVersion()
{
if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion())
{
if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
}
else
{
} else {
return '<info>Console Tool</info>';
}
}
@ -351,19 +329,18 @@ class Application
*/
public function register($name)
{
return $this->addCommand(new Command($name));
return $this->add(new Command($name));
}
/**
* Adds an array of command objects.
*
* @param array $commands An array of commands
* @param Command[] $commands An array of commands
*/
public function addCommands(array $commands)
{
foreach ($commands as $command)
{
$this->addCommand($command);
foreach ($commands as $command) {
$this->add($command);
}
}
@ -376,14 +353,13 @@ class Application
*
* @return Command The registered command
*/
public function addCommand(Command $command)
public function add(Command $command)
{
$command->setApplication($this);
$this->commands[$command->getFullName()] = $command;
foreach ($command->getAliases() as $alias)
{
foreach ($command->getAliases() as $alias) {
$this->aliases[$alias] = $command;
}
@ -396,21 +372,21 @@ class Application
* @param string $name The command name or alias
*
* @return Command A Command object
*
* @throws \InvalidArgumentException When command name given does not exist
*/
public function getCommand($name)
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name]))
public function get($name)
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
}
$command = isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
if ($this->wantHelps)
{
if ($this->wantHelps) {
$this->wantHelps = false;
$helpCommand = $this->getCommand('help');
$helpCommand = $this->get('help');
$helpCommand->setCommand($command);
return $helpCommand;
@ -426,7 +402,7 @@ class Application
*
* @return Boolean true if the command exists, false otherwise
*/
public function hasCommand($name)
public function has($name)
{
return isset($this->commands[$name]) || isset($this->aliases[$name]);
}
@ -441,10 +417,8 @@ class Application
public function getNamespaces()
{
$namespaces = array();
foreach ($this->commands as $command)
{
if ($command->getNamespace())
{
foreach ($this->commands as $command) {
if ($command->getNamespace()) {
$namespaces[$command->getNamespace()] = true;
}
}
@ -456,18 +430,18 @@ class Application
* Finds a registered namespace by a name or an abbreviation.
*
* @return string A registered namespace
*
* @throws \InvalidArgumentException When namespace is incorrect or ambiguous
*/
public function findNamespace($namespace)
{
$abbrevs = static::getAbbreviations($this->getNamespaces());
if (!isset($abbrevs[$namespace]))
{
if (!isset($abbrevs[$namespace])) {
throw new \InvalidArgumentException(sprintf('There are no commands defined in the "%s" namespace.', $namespace));
}
if (count($abbrevs[$namespace]) > 1)
{
if (count($abbrevs[$namespace]) > 1) {
throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$namespace])));
}
@ -477,19 +451,20 @@ class Application
/**
* Finds a command by name or alias.
*
* Contrary to getCommand, this command tries to find the best
* Contrary to get, this command tries to find the best
* match if you give it an abbreviation of a name or alias.
*
* @param string $name A command name or a command alias
*
* @return Command A Command instance
*
* @throws \InvalidArgumentException When command name is incorrect or ambiguous
*/
public function findCommand($name)
public function find($name)
{
// namespace
$namespace = '';
if (false !== $pos = strrpos($name, ':'))
{
if (false !== $pos = strrpos($name, ':')) {
$namespace = $this->findNamespace(substr($name, 0, $pos));
$name = substr($name, $pos + 1);
}
@ -498,22 +473,18 @@ class Application
// name
$commands = array();
foreach ($this->commands as $command)
{
if ($command->getNamespace() == $namespace)
{
foreach ($this->commands as $command) {
if ($command->getNamespace() == $namespace) {
$commands[] = $command->getName();
}
}
$abbrevs = static::getAbbreviations($commands);
if (isset($abbrevs[$name]) && 1 == count($abbrevs[$name]))
{
return $this->getCommand($namespace ? $namespace.':'.$abbrevs[$name][0] : $abbrevs[$name][0]);
if (isset($abbrevs[$name]) && 1 == count($abbrevs[$name])) {
return $this->get($namespace ? $namespace.':'.$abbrevs[$name][0] : $abbrevs[$name][0]);
}
if (isset($abbrevs[$name]) && count($abbrevs[$name]) > 1)
{
if (isset($abbrevs[$name]) && count($abbrevs[$name]) > 1) {
$suggestions = $this->getAbbreviationSuggestions(array_map(function ($command) use ($namespace) { return $namespace.':'.$command; }, $abbrevs[$name]));
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $suggestions));
@ -521,17 +492,15 @@ class Application
// aliases
$abbrevs = static::getAbbreviations(array_keys($this->aliases));
if (!isset($abbrevs[$fullName]))
{
if (!isset($abbrevs[$fullName])) {
throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $fullName));
}
if (count($abbrevs[$fullName]) > 1)
{
if (count($abbrevs[$fullName]) > 1) {
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $this->getAbbreviationSuggestions($abbrevs[$fullName])));
}
return $this->getCommand($abbrevs[$fullName][0]);
return $this->get($abbrevs[$fullName][0]);
}
/**
@ -543,18 +512,15 @@ class Application
*
* @return array An array of Command instances
*/
public function getCommands($namespace = null)
{
if (null === $namespace)
public function all($namespace = null)
{
if (null === $namespace) {
return $this->commands;
}
$commands = array();
foreach ($this->commands as $name => $command)
{
if ($namespace === $command->getNamespace())
{
foreach ($this->commands as $name => $command) {
if ($namespace === $command->getNamespace()) {
$commands[$name] = $command;
}
}
@ -565,32 +531,26 @@ class Application
/**
* Returns an array of possible abbreviations given a set of names.
*
* @param array An array of names
* @param array $names An array of names
*
* @return array An array of abbreviations
*/
static public function getAbbreviations($names)
{
$abbrevs = array();
foreach ($names as $name)
{
for ($len = strlen($name) - 1; $len > 0; --$len)
{
foreach ($names as $name) {
for ($len = strlen($name) - 1; $len > 0; --$len) {
$abbrev = substr($name, 0, $len);
if (!isset($abbrevs[$abbrev]))
{
if (!isset($abbrevs[$abbrev])) {
$abbrevs[$abbrev] = array($name);
}
else
{
} else {
$abbrevs[$abbrev][] = $name;
}
}
}
// Non-abbreviations always get entered, even if they aren't unique
foreach ($names as $name)
{
foreach ($names as $name) {
$abbrevs[$name] = array($name);
}
@ -606,35 +566,28 @@ class Application
*/
public function asText($namespace = null)
{
$commands = $namespace ? $this->getCommands($this->findNamespace($namespace)) : $this->commands;
$commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
$messages = array($this->getHelp(), '');
if ($namespace)
{
if ($namespace) {
$messages[] = sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $namespace);
}
else
{
} else {
$messages[] = '<comment>Available commands:</comment>';
}
$width = 0;
foreach ($commands as $command)
{
foreach ($commands as $command) {
$width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
}
$width += 2;
// add commands by namespace
foreach ($this->sortCommands($commands) as $space => $commands)
{
if (!$namespace && '_global' !== $space)
{
foreach ($this->sortCommands($commands) as $space => $commands) {
if (!$namespace && '_global' !== $space) {
$messages[] = '<comment>'.$space.'</comment>';
}
foreach ($commands as $command)
{
foreach ($commands as $command) {
$aliases = $command->getAliases() ? '<comment> ('.implode(', ', $command->getAliases()).')</comment>' : '';
$messages[] = sprintf(" <info>%-${width}s</info> %s%s", ($command->getNamespace() ? ':' : '').$command->getName(), $command->getDescription(), $aliases);
@ -654,7 +607,7 @@ class Application
*/
public function asXml($namespace = null, $asDom = false)
{
$commands = $namespace ? $this->getCommands($this->findNamespace($namespace)) : $this->commands;
$commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
@ -662,36 +615,26 @@ class Application
$xml->appendChild($commandsXML = $dom->createElement('commands'));
if ($namespace)
{
if ($namespace) {
$commandsXML->setAttribute('namespace', $namespace);
}
else
{
} else {
$xml->appendChild($namespacesXML = $dom->createElement('namespaces'));
}
// add commands by namespace
foreach ($this->sortCommands($commands) as $space => $commands)
{
if (!$namespace)
{
foreach ($this->sortCommands($commands) as $space => $commands) {
if (!$namespace) {
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
$namespaceArrayXML->setAttribute('id', $space);
}
foreach ($commands as $command)
{
if (!$namespace)
{
foreach ($commands as $command) {
if (!$namespace) {
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
$commandXML->appendChild($dom->createTextNode($command->getName()));
}
$commandXML = new \DOMDocument('1.0', 'UTF-8');
$commandXML->formatOutput = true;
$commandXML->loadXML($command->asXml());
$node = $commandXML->getElementsByTagName('command')->item(0);
$node = $command->asXml(true)->getElementsByTagName('command')->item(0);
$node = $dom->importNode($node, true);
$commandsXML->appendChild($node);
@ -717,36 +660,31 @@ class Application
$title = sprintf(' [%s] ', get_class($e));
$len = $strlen($title);
$lines = array();
foreach (explode("\n", $e->getMessage()) as $line)
{
foreach (explode("\n", $e->getMessage()) as $line) {
$lines[] = sprintf(' %s ', $line);
$len = max($strlen($line) + 4, $len);
}
$messages = array(str_repeat(' ', $len), $title.str_repeat(' ', $len - $strlen($title)));
foreach ($lines as $line)
{
foreach ($lines as $line) {
$messages[] = $line.str_repeat(' ', $len - $strlen($line));
}
$messages[] = str_repeat(' ', $len);
$output->writeln("\n");
foreach ($messages as $message)
{
$output->writeln("<error>$message</error>");
foreach ($messages as $message) {
$output->writeln('<error>'.$message.'</error>');
}
$output->writeln("\n");
if (null !== $this->runningCommand)
{
if (null !== $this->runningCommand) {
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
$output->writeln("\n");
}
if (Output::VERBOSITY_VERBOSE === $output->getVerbosity())
{
if (Output::VERBOSITY_VERBOSE === $output->getVerbosity()) {
$output->writeln('</comment>Exception trace:</comment>');
// exception related properties
@ -758,8 +696,7 @@ class Application
'args' => array(),
));
for ($i = 0, $count = count($trace); $i < $count; $i++)
{
for ($i = 0, $count = count($trace); $i < $count; $i++) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
$function = $trace[$i]['function'];
@ -773,15 +710,18 @@ class Application
}
}
private function sortCommands($commands)
protected function getCommandName(InputInterface $input)
{
return $input->getFirstArgument('command');
}
protected function sortCommands($commands)
{
$namespacedCommands = array();
foreach ($commands as $name => $command)
{
foreach ($commands as $name => $command) {
$key = $command->getNamespace() ? $command->getNamespace() : '_global';
if (!isset($namespacedCommands[$key]))
{
if (!isset($namespacedCommands[$key])) {
$namespacedCommands[$key] = array();
}
@ -789,15 +729,14 @@ class Application
}
ksort($namespacedCommands);
foreach ($namespacedCommands as $name => &$commands)
{
foreach ($namespacedCommands as $name => &$commands) {
ksort($commands);
}
return $namespacedCommands;
}
private function getAbbreviationSuggestions($abbrevs)
protected function getAbbreviationSuggestions($abbrevs)
{
return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
}

View File

@ -10,7 +10,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -21,8 +21,6 @@ use Symfony\Component\Console\Application;
/**
* Base class for all commands.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Command
@ -35,7 +33,6 @@ class Command
protected $application;
protected $description;
protected $ignoreValidationErrors;
protected $formatter;
protected $applicationDefinitionMerged;
protected $code;
@ -43,6 +40,8 @@ class Command
* Constructor.
*
* @param string $name The name of the command
*
* @throws \LogicException When the command name is empty
*/
public function __construct($name = null)
{
@ -51,15 +50,13 @@ class Command
$this->applicationDefinitionMerged = false;
$this->aliases = array();
if (null !== $name)
{
if (null !== $name) {
$this->setName($name);
}
$this->configure();
if (!$this->name)
{
if (!$this->name) {
throw new \LogicException('The command name cannot be empty.');
}
}
@ -88,6 +85,8 @@ class Command
* @param OutputInterface $output An OutputInterface instance
*
* @return integer 0 if everything went fine, or an error code
*
* @throws \LogicException When this abstract class is not implemented
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@ -129,33 +128,25 @@ class Command
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
try
{
try {
$input->bind($this->definition);
}
catch (\Exception $e)
{
if (!$this->ignoreValidationErrors)
{
} catch (\Exception $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
}
$this->initialize($input, $output);
if ($input->isInteractive())
{
if ($input->isInteractive()) {
$this->interact($input, $output);
}
$input->validate();
if ($this->code)
{
if ($this->code) {
return call_user_func($this->code, $input, $output);
}
else
{
} else {
return $this->execute($input, $output);
}
}
@ -179,8 +170,7 @@ class Command
*/
protected function mergeApplicationDefinition()
{
if (null === $this->application || true === $this->applicationDefinitionMerged)
{
if (null === $this->application || true === $this->applicationDefinitionMerged) {
return;
}
@ -203,12 +193,9 @@ class Command
*/
public function setDefinition($definition)
{
if ($definition instanceof InputDefinition)
{
if ($definition instanceof InputDefinition) {
$this->definition = $definition;
}
else
{
} else {
$this->definition->setDefinition($definition);
}
@ -220,7 +207,7 @@ class Command
/**
* Gets the InputDefinition attached to this Command.
*
* @return InputDefinition $definition An InputDefinition instance
* @return InputDefinition An InputDefinition instance
*/
public function getDefinition()
{
@ -249,9 +236,9 @@ class Command
*
* @param string $name The option name
* @param string $shortcut The shortcut (can be null)
* @param integer $mode The option mode: self::PARAMETER_REQUIRED, self::PARAMETER_NONE or self::PARAMETER_OPTIONAL
* @param integer $mode The option mode: One of the InputOption::VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for self::PARAMETER_REQUIRED or self::PARAMETER_NONE)
* @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or self::VALUE_NONE)
*
* @return Command The current instance
*/
@ -273,22 +260,20 @@ class Command
* @param string $name The command name
*
* @return Command The current instance
*
* @throws \InvalidArgumentException When command name given is empty
*/
public function setName($name)
{
if (false !== $pos = strrpos($name, ':'))
{
if (false !== $pos = strrpos($name, ':')) {
$namespace = substr($name, 0, $pos);
$name = substr($name, $pos + 1);
}
else
{
} else {
$namespace = $this->namespace;
}
if (!$name)
{
throw new \InvalidArgumentException('A command name cannot be empty');
if (!$name) {
throw new \InvalidArgumentException('A command name cannot be empty.');
}
$this->namespace = $namespace;
@ -472,15 +457,13 @@ class Command
'',
);
if ($this->getAliases())
{
if ($this->getAliases()) {
$messages[] = '<comment>Aliases:</comment> <info>'.implode(', ', $this->getAliases()).'</info>';
}
$messages[] = $this->definition->asText();
if ($help = $this->getProcessedHelp())
{
if ($help = $this->getProcessedHelp()) {
$messages[] = '<comment>Help:</comment>';
$messages[] = ' '.implode("\n ", explode("\n", $help))."\n";
}
@ -515,8 +498,7 @@ class Command
$helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help))));
$commandXML->appendChild($aliasesXML = $dom->createElement('aliases'));
foreach ($this->getAliases() as $alias)
{
foreach ($this->getAliases() as $alias) {
$aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
$aliasXML->appendChild($dom->createTextNode($alias));
}

View File

@ -10,7 +10,7 @@ use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Command\Command;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -21,8 +21,6 @@ use Symfony\Component\Console\Command\Command;
/**
* HelpCommand displays the help for a given command.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class HelpCommand extends Command
@ -39,7 +37,7 @@ class HelpCommand extends Command
$this
->setDefinition(array(
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('xml', null, InputOption::PARAMETER_NONE, 'To output help as XML'),
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
))
->setName('help')
->setAliases(array('?'))
@ -47,11 +45,11 @@ class HelpCommand extends Command
->setHelp(<<<EOF
The <info>help</info> command displays help for a given command:
<info>./symfony help test:all</info>
<info>./symfony help list</info>
You can also output the help as XML by using the <comment>--xml</comment> option:
<info>./symfony help --xml test:all</info>
<info>./symfony help --xml list</info>
EOF
);
}
@ -66,17 +64,13 @@ EOF
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (null === $this->command)
{
$this->command = $this->application->getCommand($input->getArgument('command_name'));
if (null === $this->command) {
$this->command = $this->application->get($input->getArgument('command_name'));
}
if ($input->getOption('xml'))
{
if ($input->getOption('xml')) {
$output->writeln($this->command->asXml(), Output::OUTPUT_RAW);
}
else
{
} else {
$output->writeln($this->command->asText());
}
}

View File

@ -10,7 +10,7 @@ use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Command\Command;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -21,8 +21,6 @@ use Symfony\Component\Console\Command\Command;
/**
* ListCommand displays the list of all available commands for the application.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class ListCommand extends Command
@ -35,7 +33,7 @@ class ListCommand extends Command
$this
->setDefinition(array(
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputOption('xml', null, InputOption::PARAMETER_NONE, 'To output help as XML'),
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
))
->setName('list')
->setDescription('Lists commands')
@ -60,12 +58,9 @@ EOF
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('xml'))
{
if ($input->getOption('xml')) {
$output->writeln($this->application->asXml($input->getArgument('namespace')), Output::OUTPUT_RAW);
}
else
{
} else {
$output->writeln($this->application->asText($input->getArgument('namespace')));
}
}

View File

@ -5,7 +5,7 @@ namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\OutputInterface;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -16,8 +16,6 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* The Dialog class provides helpers to interact with the user.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class DialogHelper extends Helper
@ -29,7 +27,7 @@ class DialogHelper extends Helper
* @param string|array $question The question to ask
* @param string $default The default answer if none is given by the user
*
* @param string The user answer
* @return string The user answer
*/
public function ask(OutputInterface $output, $question, $default = null)
{
@ -51,23 +49,19 @@ class DialogHelper extends Helper
* @param string|array $question The question to ask
* @param Boolean $default The default answer if the user enters nothing
*
* @param Boolean true if the user has confirmed, false otherwise
* @return Boolean true if the user has confirmed, false otherwise
*/
public function askConfirmation(OutputInterface $output, $question, $default = true)
{
// @codeCoverageIgnoreStart
$answer = 'z';
while ($answer && !in_array(strtolower($answer[0]), array('y', 'n')))
{
while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) {
$answer = $this->ask($output, $question);
}
if (false === $default)
{
if (false === $default) {
return $answer && 'y' == strtolower($answer[0]);
}
else
{
} else {
return !$answer || 'y' == strtolower($answer[0]);
}
// @codeCoverageIgnoreEnd
@ -82,26 +76,23 @@ class DialogHelper extends Helper
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
*
* @return mixed
*
* @throws \Exception When any of the validator returns an error
*/
public function askAndValidate(OutputInterface $output, $question, \Closure $validator, $attempts = false)
{
// @codeCoverageIgnoreStart
$error = null;
while (false === $attempts || $attempts--)
{
if (null !== $error)
{
while (false === $attempts || $attempts--) {
if (null !== $error) {
$output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
}
$value = $this->ask($output, $question, null);
try
{
try {
return $validator($value);
}
catch (\Exception $error)
{
} catch (\Exception $error) {
}
}

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Helper;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -14,8 +14,6 @@ namespace Symfony\Component\Console\Helper;
/**
* The Formatter class provides helpers to format messages.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class FormatterHelper extends Helper
@ -29,7 +27,7 @@ class FormatterHelper extends Helper
*/
public function formatSection($section, $message, $style = 'info')
{
return sprintf("<%s>[%s]</%s> %s", $style, $section, $style, $message);
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
}
/**
@ -43,31 +41,26 @@ class FormatterHelper extends Helper
*/
public function formatBlock($messages, $style, $large = false)
{
if (!is_array($messages))
{
if (!is_array($messages)) {
$messages = array($messages);
}
$len = 0;
$lines = array();
foreach ($messages as $message)
{
foreach ($messages as $message) {
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
$len = max($this->strlen($message) + ($large ? 4 : 2), $len);
}
$messages = $large ? array(str_repeat(' ', $len)) : array();
foreach ($lines as $line)
{
foreach ($lines as $line) {
$messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
}
if ($large)
{
if ($large) {
$messages[] = str_repeat(' ', $len);
}
foreach ($messages as &$message)
{
foreach ($messages as &$message) {
$message = sprintf('<%s>%s</%s>', $style, $message, $style);
}

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Helper;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -14,14 +14,11 @@ namespace Symfony\Component\Console\Helper;
/**
* Helper is the base class for all helper classes.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
abstract class Helper implements HelperInterface
{
protected
$helperSet = null;
protected $helperSet = null;
/**
* Sets the helper set associated with this helper.

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Helper;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -14,8 +14,6 @@ namespace Symfony\Component\Console\Helper;
/**
* HelperInterface is the interface all helpers must implement.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
interface HelperInterface

View File

@ -5,7 +5,7 @@ namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Command\Command;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -16,20 +16,20 @@ use Symfony\Component\Console\Command\Command;
/**
* HelperSet represents a set of helpers to be used with a command.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class HelperSet
{
protected
$helpers = array(),
$command = null;
protected $helpers;
protected $command;
/**
* @param Helper[] $helpers An array of helper.
*/
public function __construct(array $helpers = array())
{
foreach ($helpers as $alias => $helper)
{
$this->helpers = array();
foreach ($helpers as $alias => $helper) {
$this->set($helper, is_int($alias) ? null : $alias);
}
}
@ -43,8 +43,7 @@ class HelperSet
public function set(HelperInterface $helper, $alias = null)
{
$this->helpers[$helper->getName()] = $helper;
if (null !== $alias)
{
if (null !== $alias) {
$this->helpers[$alias] = $helper;
}
@ -74,8 +73,7 @@ class HelperSet
*/
public function get($name)
{
if (!$this->has($name))
{
if (!$this->has($name)) {
throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
}

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -20,7 +20,7 @@ namespace Symfony\Component\Console\Input;
*
* By default, the `$_SERVER['argv']` array is used for the input values.
*
* This can be overriden by explicitly passing the input values in the constructor:
* This can be overridden by explicitly passing the input values in the constructor:
*
* $input = new ArgvInput($_SERVER['argv']);
*
@ -31,8 +31,6 @@ namespace Symfony\Component\Console\Input;
* the same rules as the argv one. It's almost always better to use the
* `StringInput` when you want to provide your own input.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*
* @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
@ -51,8 +49,7 @@ class ArgvInput extends Input
*/
public function __construct(array $argv = null, InputDefinition $definition = null)
{
if (null === $argv)
{
if (null === $argv) {
$argv = $_SERVER['argv'];
}
@ -70,18 +67,12 @@ class ArgvInput extends Input
protected function parse()
{
$this->parsed = $this->tokens;
while (null !== ($token = array_shift($this->parsed)))
{
if ('--' === substr($token, 0, 2))
{
while (null !== $token = array_shift($this->parsed)) {
if ('--' === substr($token, 0, 2)) {
$this->parseLongOption($token);
}
elseif ('-' === $token[0])
{
} elseif ('-' === $token[0]) {
$this->parseShortOption($token);
}
else
{
} else {
$this->parseArgument($token);
}
}
@ -96,20 +87,14 @@ class ArgvInput extends Input
{
$name = substr($token, 1);
if (strlen($name) > 1)
{
if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptParameter())
{
if (strlen($name) > 1) {
if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
// an option with a value (with no space)
$this->addShortOption($name[0], substr($name, 1));
}
else
{
} else {
$this->parseShortOptionSet($name);
}
}
else
{
} else {
$this->addShortOption($name, null);
}
}
@ -118,26 +103,23 @@ class ArgvInput extends Input
* Parses a short option set.
*
* @param string $token The current token
*
* @throws \RuntimeException When option given doesn't exist
*/
protected function parseShortOptionSet($name)
{
$len = strlen($name);
for ($i = 0; $i < $len; $i++)
{
if (!$this->definition->hasShortcut($name[$i]))
{
for ($i = 0; $i < $len; $i++) {
if (!$this->definition->hasShortcut($name[$i])) {
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
}
$option = $this->definition->getOptionForShortcut($name[$i]);
if ($option->acceptParameter())
{
if ($option->acceptValue()) {
$this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
break;
}
else
{
} else {
$this->addLongOption($option->getName(), true);
}
}
@ -152,12 +134,9 @@ class ArgvInput extends Input
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '='))
{
if (false !== $pos = strpos($name, '=')) {
$this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
}
else
{
} else {
$this->addLongOption($name, null);
}
}
@ -166,11 +145,12 @@ class ArgvInput extends Input
* Parses an argument.
*
* @param string $token The current token
*
* @throws \RuntimeException When too many arguments are given
*/
protected function parseArgument($token)
{
if (!$this->definition->hasArgument(count($this->arguments)))
{
if (!$this->definition->hasArgument(count($this->arguments))) {
throw new \RuntimeException('Too many arguments.');
}
@ -182,11 +162,12 @@ class ArgvInput extends Input
*
* @param string $shortcut The short option key
* @param mixed $value The value for the option
*
* @throws \RuntimeException When option given doesn't exist
*/
protected function addShortOption($shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut))
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
}
@ -198,39 +179,34 @@ class ArgvInput extends Input
*
* @param string $name The long option key
* @param mixed $value The value for the option
*
* @throws \RuntimeException When option given doesn't exist
*/
protected function addLongOption($name, $value)
{
if (!$this->definition->hasOption($name))
{
if (!$this->definition->hasOption($name)) {
throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (null === $value && $option->acceptParameter())
{
if (null === $value && $option->acceptValue()) {
// if option accepts an optional or mandatory argument
// let's see if there is one provided
$next = array_shift($this->parsed);
if ('-' !== $next[0])
{
if ('-' !== $next[0]) {
$value = $next;
}
else
{
} else {
array_unshift($this->parsed, $next);
}
}
if (null === $value)
{
if ($option->isParameterRequired())
{
if (null === $value) {
if ($option->isValueRequired()) {
throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
}
$value = $option->isParameterOptional() ? $option->getDefault() : true;
$value = $option->isValueOptional() ? $option->getDefault() : true;
}
$this->options[$name] = $value;
@ -243,10 +219,8 @@ class ArgvInput extends Input
*/
public function getFirstArgument()
{
foreach ($this->tokens as $token)
{
if ($token && '-' === $token[0])
{
foreach ($this->tokens as $token) {
if ($token && '-' === $token[0]) {
continue;
}
@ -266,15 +240,12 @@ class ArgvInput extends Input
*/
public function hasParameterOption($values)
{
if (!is_array($values))
{
if (!is_array($values)) {
$values = array($values);
}
foreach ($this->tokens as $v)
{
if (in_array($v, $values))
{
foreach ($this->tokens as $v) {
if (in_array($v, $values)) {
return true;
}
}

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -18,8 +18,6 @@ namespace Symfony\Component\Console\Input;
*
* $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar'));
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class ArrayInput extends Input
@ -46,10 +44,8 @@ class ArrayInput extends Input
*/
public function getFirstArgument()
{
foreach ($this->parameters as $key => $value)
{
if ($key && '-' === $key[0])
{
foreach ($this->parameters as $key => $value) {
if ($key && '-' === $key[0]) {
continue;
}
@ -69,20 +65,16 @@ class ArrayInput extends Input
*/
public function hasParameterOption($values)
{
if (!is_array($values))
{
if (!is_array($values)) {
$values = array($values);
}
foreach ($this->parameters as $k => $v)
{
if (!is_int($k))
{
foreach ($this->parameters as $k => $v) {
if (!is_int($k)) {
$v = $k;
}
if (in_array($v, $values))
{
if (in_array($v, $values)) {
return true;
}
}
@ -95,18 +87,12 @@ class ArrayInput extends Input
*/
protected function parse()
{
foreach ($this->parameters as $key => $value)
{
if ('--' === substr($key, 0, 2))
{
foreach ($this->parameters as $key => $value) {
if ('--' === substr($key, 0, 2)) {
$this->addLongOption(substr($key, 2), $value);
}
elseif ('-' === $key[0])
{
} elseif ('-' === $key[0]) {
$this->addShortOption(substr($key, 1), $value);
}
else
{
} else {
$this->addArgument($key, $value);
}
}
@ -117,12 +103,13 @@ class ArrayInput extends Input
*
* @param string $shortcut The short option key
* @param mixed $value The value for the option
*
* @throws \RuntimeException When option given doesn't exist
*/
protected function addShortOption($shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut))
{
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
if (!$this->definition->hasShortcut($shortcut)) {
throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
}
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
@ -133,24 +120,24 @@ class ArrayInput extends Input
*
* @param string $name The long option key
* @param mixed $value The value for the option
*
* @throws \InvalidArgumentException When option given doesn't exist
* @throws \InvalidArgumentException When a required value is missing
*/
protected function addLongOption($name, $value)
{
if (!$this->definition->hasOption($name))
{
throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
if (!$this->definition->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (null === $value)
{
if ($option->isParameterRequired())
{
throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
if (null === $value) {
if ($option->isValueRequired()) {
throw new \InvalidArgumentException(sprintf('The "--%s" option requires a value.', $name));
}
$value = $option->isParameterOptional() ? $option->getDefault() : true;
$value = $option->isValueOptional() ? $option->getDefault() : true;
}
$this->options[$name] = $value;
@ -161,12 +148,13 @@ class ArrayInput extends Input
*
* @param string $name The argument name
* @param mixed $value The value for the argument
*
* @throws \InvalidArgumentException When argument given doesn't exist
*/
protected function addArgument($name, $value)
{
if (!$this->definition->hasArgument($name))
{
throw new \RuntimeException(sprintf('The "%s" argument does not exist.', $name));
if (!$this->definition->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -20,8 +20,6 @@ namespace Symfony\Component\Console\Input;
* * `StringInput`: The input is provided as a string
* * `ArrayInput`: The input is provided as an array
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
abstract class Input implements InputInterface
@ -38,12 +36,9 @@ abstract class Input implements InputInterface
*/
public function __construct(InputDefinition $definition = null)
{
if (null === $definition)
{
if (null === $definition) {
$this->definition = new InputDefinition();
}
else
{
} else {
$this->bind($definition);
$this->validate();
}
@ -68,10 +63,12 @@ abstract class Input implements InputInterface
*/
abstract protected function parse();
/**
* @throws \RuntimeException When not enough arguments are given
*/
public function validate()
{
if (count($this->arguments) < $this->definition->getArgumentRequiredCount())
{
if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
throw new \RuntimeException('Not enough arguments.');
}
}
@ -102,11 +99,12 @@ abstract class Input implements InputInterface
* @param string $name The argument name
*
* @return mixed The argument value
*
* @throws \InvalidArgumentException When argument given doesn't exist
*/
public function getArgument($name)
{
if (!$this->definition->hasArgument($name))
{
if (!$this->definition->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
@ -118,11 +116,12 @@ abstract class Input implements InputInterface
*
* @param string $name The argument name
* @param string $value The argument value
*
* @throws \InvalidArgumentException When argument given doesn't exist
*/
public function setArgument($name, $value)
{
if (!$this->definition->hasArgument($name))
{
if (!$this->definition->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
@ -157,11 +156,12 @@ abstract class Input implements InputInterface
* @param string $name The option name
*
* @return mixed The option value
*
* @throws \InvalidArgumentException When option given doesn't exist
*/
public function getOption($name)
{
if (!$this->definition->hasOption($name))
{
if (!$this->definition->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
@ -173,11 +173,12 @@ abstract class Input implements InputInterface
*
* @param string $name The option name
* @param string $value The option value
*
* @throws \InvalidArgumentException When option given doesn't exist
*/
public function setOption($name, $value)
{
if (!$this->definition->hasOption($name))
{
if (!$this->definition->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -14,8 +14,6 @@ namespace Symfony\Component\Console\Input;
/**
* Represents a command line argument.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class InputArgument
@ -36,15 +34,14 @@ class InputArgument
* @param integer $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for self::OPTIONAL mode only)
*
* @throws \InvalidArgumentException When argument mode is not valid
*/
public function __construct($name, $mode = null, $description = '', $default = null)
{
if (null === $mode)
{
if (null === $mode) {
$mode = self::OPTIONAL;
}
else if (is_string($mode) || $mode > 7)
{
} else if (is_string($mode) || $mode > 7) {
throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
}
@ -89,22 +86,19 @@ class InputArgument
* Sets the default value.
*
* @param mixed $default The default value
*
* @throws \LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::REQUIRED === $this->mode && null !== $default)
{
if (self::REQUIRED === $this->mode && null !== $default) {
throw new \LogicException('Cannot set a default value except for Parameter::OPTIONAL mode.');
}
if ($this->isArray())
{
if (null === $default)
{
if ($this->isArray()) {
if (null === $default) {
$default = array();
}
else if (!is_array($default))
{
} else if (!is_array($default)) {
throw new \LogicException('A default value for an array argument must be an array.');
}
}

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -18,11 +18,9 @@ namespace Symfony\Component\Console\Input;
*
* $definition = new InputDefinition(array(
* new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::PARAMETER_REQUIRED),
* new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* ));
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class InputDefinition
@ -48,14 +46,10 @@ class InputDefinition
{
$arguments = array();
$options = array();
foreach ($definition as $item)
{
if ($item instanceof InputOption)
{
foreach ($definition as $item) {
if ($item instanceof InputOption) {
$options[] = $item;
}
else
{
} else {
$arguments[] = $item;
}
}
@ -74,20 +68,19 @@ class InputDefinition
$this->arguments = array();
$this->requiredCount = 0;
$this->hasOptional = false;
$this->hasAnArrayArgument = false;
$this->addArguments($arguments);
}
/**
* Add an array of InputArgument objects.
*
* @param array $arguments An array of InputArgument objects
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function addArguments($arguments = array())
{
if (null !== $arguments)
{
foreach ($arguments as $argument)
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
$this->addArgument($argument);
}
}
@ -97,35 +90,30 @@ class InputDefinition
* Add an InputArgument object.
*
* @param InputArgument $argument An InputArgument object
*
* @throws \LogicException When incorrect argument is given
*/
public function addArgument(InputArgument $argument)
{
if (isset($this->arguments[$argument->getName()]))
{
if (isset($this->arguments[$argument->getName()])) {
throw new \LogicException(sprintf('An argument with name "%s" already exist.', $argument->getName()));
}
if ($this->hasAnArrayArgument)
{
if ($this->hasAnArrayArgument) {
throw new \LogicException('Cannot add an argument after an array argument.');
}
if ($argument->isRequired() && $this->hasOptional)
{
if ($argument->isRequired() && $this->hasOptional) {
throw new \LogicException('Cannot add a required argument after an optional one.');
}
if ($argument->isArray())
{
if ($argument->isArray()) {
$this->hasAnArrayArgument = true;
}
if ($argument->isRequired())
{
if ($argument->isRequired()) {
++$this->requiredCount;
}
else
{
} else {
$this->hasOptional = true;
}
@ -138,13 +126,14 @@ class InputDefinition
* @param string|integer $name The InputArgument name or position
*
* @return InputArgument An InputArgument object
*
* @throws \InvalidArgumentException When argument given doesn't exist
*/
public function getArgument($name)
{
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
if (!$this->hasArgument($name))
{
if (!$this->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
@ -203,8 +192,7 @@ class InputDefinition
public function getArgumentDefaults()
{
$values = array();
foreach ($this->arguments as $argument)
{
foreach ($this->arguments as $argument) {
$values[$argument->getName()] = $argument->getDefault();
}
@ -226,12 +214,11 @@ class InputDefinition
/**
* Add an array of InputOption objects.
*
* @param array $options An array of InputOption objects
* @param InputOption[] $options An array of InputOption objects
*/
public function addOptions($options = array())
{
foreach ($options as $option)
{
foreach ($options as $option) {
$this->addOption($option);
}
}
@ -240,21 +227,19 @@ class InputDefinition
* Add an InputOption object.
*
* @param InputOption $option An InputOption object
*
* @throws \LogicException When option given already exist
*/
public function addOption(InputOption $option)
{
if (isset($this->options[$option->getName()]))
{
if (isset($this->options[$option->getName()])) {
throw new \LogicException(sprintf('An option named "%s" already exist.', $option->getName()));
}
else if (isset($this->shortcuts[$option->getShortcut()]))
{
} else if (isset($this->shortcuts[$option->getShortcut()])) {
throw new \LogicException(sprintf('An option with shortcut "%s" already exist.', $option->getShortcut()));
}
$this->options[$option->getName()] = $option;
if ($option->getShortcut())
{
if ($option->getShortcut()) {
$this->shortcuts[$option->getShortcut()] = $option->getName();
}
}
@ -268,8 +253,7 @@ class InputDefinition
*/
public function getOption($name)
{
if (!$this->hasOption($name))
{
if (!$this->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
}
@ -328,8 +312,7 @@ class InputDefinition
public function getOptionDefaults()
{
$values = array();
foreach ($this->options as $option)
{
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
}
@ -342,11 +325,12 @@ class InputDefinition
* @param string $shortcut The shortcut
*
* @return string The InputOption name
*
* @throws \InvalidArgumentException When option given does not exist
*/
protected function shortcutToName($shortcut)
{
if (!isset($this->shortcuts[$shortcut]))
{
if (!isset($this->shortcuts[$shortcut])) {
throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
}
@ -361,18 +345,15 @@ class InputDefinition
public function getSynopsis()
{
$elements = array();
foreach ($this->getOptions() as $option)
{
foreach ($this->getOptions() as $option) {
$shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
$elements[] = sprintf('['.($option->isParameterRequired() ? '%s--%s="..."' : ($option->isParameterOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName());
$elements[] = sprintf('['.($option->isValueRequired() ? '%s--%s="..."' : ($option->isValueOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName());
}
foreach ($this->getArguments() as $argument)
{
foreach ($this->getArguments() as $argument) {
$elements[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : ''));
if ($argument->isArray())
{
if ($argument->isArray()) {
$elements[] = sprintf('... [%sN]', $argument->getName());
}
}
@ -389,29 +370,22 @@ class InputDefinition
{
// find the largest option or argument name
$max = 0;
foreach ($this->getOptions() as $option)
{
foreach ($this->getOptions() as $option) {
$max = strlen($option->getName()) + 2 > $max ? strlen($option->getName()) + 2 : $max;
}
foreach ($this->getArguments() as $argument)
{
foreach ($this->getArguments() as $argument) {
$max = strlen($argument->getName()) > $max ? strlen($argument->getName()) : $max;
}
++$max;
$text = array();
if ($this->getArguments())
{
if ($this->getArguments()) {
$text[] = '<comment>Arguments:</comment>';
foreach ($this->getArguments() as $argument)
{
if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault())))
{
foreach ($this->getArguments() as $argument) {
if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) {
$default = sprintf('<comment> (default: %s)</comment>', is_array($argument->getDefault()) ? str_replace("\n", '', var_export($argument->getDefault(), true)): $argument->getDefault());
}
else
{
} else {
$default = '';
}
@ -421,18 +395,13 @@ class InputDefinition
$text[] = '';
}
if ($this->getOptions())
{
if ($this->getOptions()) {
$text[] = '<comment>Options:</comment>';
foreach ($this->getOptions() as $option)
{
if ($option->acceptParameter() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault())))
{
foreach ($this->getOptions() as $option) {
if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) {
$default = sprintf('<comment> (default: %s)</comment>', is_array($option->getDefault()) ? str_replace("\n", '', print_r($option->getDefault(), true)): $option->getDefault());
}
else
{
} else {
$default = '';
}
@ -460,8 +429,7 @@ class InputDefinition
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($this->getArguments() as $argument)
{
foreach ($this->getArguments() as $argument) {
$argumentsXML->appendChild($argumentXML = $dom->createElement('argument'));
$argumentXML->setAttribute('name', $argument->getName());
$argumentXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
@ -471,31 +439,27 @@ class InputDefinition
$argumentXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = is_array($argument->getDefault()) ? $argument->getDefault() : ($argument->getDefault() ? array($argument->getDefault()) : array());
foreach ($defaults as $default)
{
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
}
$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($this->getOptions() as $option)
{
foreach ($this->getOptions() as $option) {
$optionsXML->appendChild($optionXML = $dom->createElement('option'));
$optionXML->setAttribute('name', '--'.$option->getName());
$optionXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
$optionXML->setAttribute('accept_parameter', $option->acceptParameter() ? 1 : 0);
$optionXML->setAttribute('is_parameter_required', $option->isParameterRequired() ? 1 : 0);
$optionXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
$optionXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
$optionXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
$optionXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
if ($option->acceptParameter())
{
if ($option->acceptValue()) {
$optionXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = is_array($option->getDefault()) ? $option->getDefault() : ($option->getDefault() ? array($option->getDefault()) : array());
foreach ($defaults as $default)
{
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -14,8 +14,6 @@ namespace Symfony\Component\Console\Input;
/**
* InputInterface is the interface implemented by all input classes.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
interface InputInterface

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -14,16 +14,14 @@ namespace Symfony\Component\Console\Input;
/**
* Represents a command line option.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class InputOption
{
const PARAMETER_NONE = 1;
const PARAMETER_REQUIRED = 2;
const PARAMETER_OPTIONAL = 4;
const PARAMETER_IS_ARRAY = 8;
const VALUE_NONE = 1;
const VALUE_REQUIRED = 2;
const VALUE_OPTIONAL = 4;
const VALUE_IS_ARRAY = 8;
protected $name;
protected $shortcut;
@ -36,36 +34,31 @@ class InputOption
*
* @param string $name The option name
* @param string $shortcut The shortcut (can be null)
* @param integer $mode The option mode: self::PARAMETER_REQUIRED, self::PARAMETER_NONE or self::PARAMETER_OPTIONAL
* @param integer $mode The option mode: One of the VALUE_* constants
* @param string $description A description text
* @param mixed $default The default value (must be null for self::PARAMETER_REQUIRED or self::PARAMETER_NONE)
* @param mixed $default The default value (must be null for self::VALUE_REQUIRED or self::VALUE_NONE)
*
* @throws \InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
if ('--' === substr($name, 0, 2))
{
if ('--' === substr($name, 0, 2)) {
$name = substr($name, 2);
}
if (empty($shortcut))
{
if (empty($shortcut)) {
$shortcut = null;
}
if (null !== $shortcut)
{
if ('-' === $shortcut[0])
{
if (null !== $shortcut) {
if ('-' === $shortcut[0]) {
$shortcut = substr($shortcut, 1);
}
}
if (null === $mode)
{
$mode = self::PARAMETER_NONE;
}
else if (!is_int($mode) || $mode > 15)
{
if (null === $mode) {
$mode = self::VALUE_NONE;
} else if (!is_int($mode) || $mode > 15) {
throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
}
@ -74,9 +67,8 @@ class InputOption
$this->mode = $mode;
$this->description = $description;
if ($this->isArray() && !$this->acceptParameter())
{
throw new \InvalidArgumentException('Impossible to have an option mode PARAMETER_IS_ARRAY if the option does not accept a parameter.');
if ($this->isArray() && !$this->acceptValue()) {
throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
}
$this->setDefault($default);
@ -103,43 +95,43 @@ class InputOption
}
/**
* Returns true if the option accept a parameter.
* Returns true if the option accepts a value.
*
* @return Boolean true if parameter mode is not self::PARAMETER_NONE, false otherwise
* @return Boolean true if value mode is not self::VALUE_NONE, false otherwise
*/
public function acceptParameter()
public function acceptValue()
{
return $this->isParameterRequired() || $this->isParameterOptional();
return $this->isValueRequired() || $this->isValueOptional();
}
/**
* Returns true if the option requires a parameter.
* Returns true if the option requires a value.
*
* @return Boolean true if parameter mode is self::PARAMETER_REQUIRED, false otherwise
* @return Boolean true if value mode is self::VALUE_REQUIRED, false otherwise
*/
public function isParameterRequired()
public function isValueRequired()
{
return self::PARAMETER_REQUIRED === (self::PARAMETER_REQUIRED & $this->mode);
return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
}
/**
* Returns true if the option takes an optional parameter.
* Returns true if the option takes an optional value.
*
* @return Boolean true if parameter mode is self::PARAMETER_OPTIONAL, false otherwise
* @return Boolean true if value mode is self::VALUE_OPTIONAL, false otherwise
*/
public function isParameterOptional()
public function isValueOptional()
{
return self::PARAMETER_OPTIONAL === (self::PARAMETER_OPTIONAL & $this->mode);
return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
}
/**
* Returns true if the option can take multiple values.
*
* @return Boolean true if mode is self::PARAMETER_IS_ARRAY, false otherwise
* @return Boolean true if mode is self::VALUE_IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::PARAMETER_IS_ARRAY === (self::PARAMETER_IS_ARRAY & $this->mode);
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
}
/**
@ -149,24 +141,19 @@ class InputOption
*/
public function setDefault($default = null)
{
if (self::PARAMETER_NONE === (self::PARAMETER_NONE & $this->mode) && null !== $default)
{
throw new \LogicException('Cannot set a default value when using Option::PARAMETER_NONE mode.');
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
throw new \LogicException('Cannot set a default value when using Option::VALUE_NONE mode.');
}
if ($this->isArray())
{
if (null === $default)
{
if ($this->isArray()) {
if (null === $default) {
$default = array();
}
elseif (!is_array($default))
{
} elseif (!is_array($default)) {
throw new \LogicException('A default value for an array option must be an array.');
}
}
$this->default = $this->acceptParameter() ? $default : false;
$this->default = $this->acceptValue() ? $default : false;
}
/**

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -18,8 +18,6 @@ namespace Symfony\Component\Console\Input;
*
* $input = new StringInput('foo --bar="foobar"');
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class StringInput extends ArgvInput
@ -40,6 +38,9 @@ class StringInput extends ArgvInput
$this->tokens = $this->tokenize($input);
}
/**
* @throws \InvalidArgumentException When unable to parse input (should never happen)
*/
protected function tokenize($input)
{
$input = preg_replace('/(\r\n|\r|\n|\t)/', ' ', $input);
@ -47,25 +48,15 @@ class StringInput extends ArgvInput
$tokens = array();
$length = strlen($input);
$cursor = 0;
while ($cursor < $length)
{
if (preg_match('/\s+/A', $input, $match, null, $cursor))
{
}
elseif (preg_match('/([^="\' ]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor))
{
while ($cursor < $length) {
if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
} elseif (preg_match('/([^="\' ]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) {
$tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2)));
}
elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor))
{
} elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
}
elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor))
{
} elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes($match[1]);
}
else
{
} else {
// should never happen
// @codeCoverageIgnoreStart
throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Output;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -22,8 +22,6 @@ namespace Symfony\Component\Console\Output;
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class ConsoleOutput extends StreamOutput

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Output;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -16,8 +16,6 @@ namespace Symfony\Component\Console\Output;
*
* $output = new NullOutput();
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class NullOutput extends Output

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Output;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -20,8 +20,6 @@ namespace Symfony\Component\Console\Output;
* * verbose: -v (more output - debug)
* * quiet: -q (no output)
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
abstract class Output implements OutputInterface
@ -127,23 +125,21 @@ abstract class Output implements OutputInterface
* @param string|array $messages The message as an array of lines of a single string
* @param Boolean $newline Whether to add a newline or not
* @param integer $type The type of output
*
* @throws \InvalidArgumentException When unknown output type is given
*/
public function write($messages, $newline = false, $type = 0)
{
if (self::VERBOSITY_QUIET === $this->verbosity)
{
if (self::VERBOSITY_QUIET === $this->verbosity) {
return;
}
if (!is_array($messages))
{
if (!is_array($messages)) {
$messages = array($messages);
}
foreach ($messages as $message)
{
switch ($type)
{
foreach ($messages as $message) {
switch ($type) {
case Output::OUTPUT_NORMAL:
$message = $this->format($message);
break;
@ -177,51 +173,56 @@ abstract class Output implements OutputInterface
*/
protected function format($message)
{
$message = preg_replace_callback('#<([a-z][a-z0-9\-_]+)>#i', array($this, 'replaceStartStyle'), $message);
$message = preg_replace_callback('#<([a-z][a-z0-9\-_=;]+)>#i', array($this, 'replaceStartStyle'), $message);
return preg_replace_callback('#</([a-z][a-z0-9\-_]+)>#i', array($this, 'replaceEndStyle'), $message);
return preg_replace_callback('#</([a-z][a-z0-9\-_]*)?>#i', array($this, 'replaceEndStyle'), $message);
}
/**
* @throws \InvalidArgumentException When style is unknown
*/
protected function replaceStartStyle($match)
{
if (!$this->decorated)
{
if (!$this->decorated) {
return '';
}
if (!isset(static::$styles[strtolower($match[1])]))
{
if (isset(static::$styles[strtolower($match[1])])) {
$parameters = static::$styles[strtolower($match[1])];
} else {
// bg=blue;fg=red
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($match[1]), $matches, PREG_SET_ORDER)) {
throw new \InvalidArgumentException(sprintf('Unknown style "%s".', $match[1]));
}
$parameters = static::$styles[strtolower($match[1])];
$parameters = array();
foreach ($matches as $match) {
$parameters[$match[1]] = $match[2];
}
}
$codes = array();
if (isset($parameters['fg']))
{
if (isset($parameters['fg'])) {
$codes[] = static::$foreground[$parameters['fg']];
}
if (isset($parameters['bg']))
{
if (isset($parameters['bg'])) {
$codes[] = static::$background[$parameters['bg']];
}
foreach (static::$options as $option => $value)
{
if (isset($parameters[$option]) && $parameters[$option])
{
foreach (static::$options as $option => $value) {
if (isset($parameters[$option]) && $parameters[$option]) {
$codes[] = $value;
}
}
return "\033[".implode(';', $codes)."m";
return "\033[".implode(';', $codes).'m';
}
protected function replaceEndStyle($match)
{
if (!$this->decorated)
{
if (!$this->decorated) {
return '';
}

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Output;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -14,8 +14,6 @@ namespace Symfony\Component\Console\Output;
/**
* OutputInterface is the interface implemented by all Output classes.
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
interface OutputInterface
@ -24,21 +22,24 @@ interface OutputInterface
* Writes a message to the output.
*
* @param string|array $messages The message as an array of lines of a single string
* @param Boolean $newline Whether to add a newline or not
* @param integer $type The type of output
*
* @throws \InvalidArgumentException When unknown output type is given
*/
public function write($messages, $type = 0);
function write($messages, $newline = false, $type = 0);
/**
* Sets the verbosity of the output.
*
* @param integer $level The level of verbosity
*/
public function setVerbosity($level);
function setVerbosity($level);
/**
* Sets the decorated flag.
*
* @param Boolean $decorated Whether to decorated the messages or not
*/
public function setDecorated($decorated);
function setDecorated($decorated);
}

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Output;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -22,8 +22,6 @@ namespace Symfony\Component\Console\Output;
*
* $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
*
* @package symfony
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class StreamOutput extends Output
@ -36,18 +34,18 @@ class StreamOutput extends Output
* @param mixed $stream A stream resource
* @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE)
* @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing)
*
* @throws \InvalidArgumentException When first argument is not a real stream
*/
public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null)
{
if (!is_resource($stream) || 'stream' !== get_resource_type($stream))
{
if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) {
throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
}
$this->stream = $stream;
if (null === $decorated)
{
if (null === $decorated) {
$decorated = $this->hasColorSupport($decorated);
}
@ -69,11 +67,12 @@ class StreamOutput extends Output
*
* @param string $message A message to write to the output
* @param Boolean $newline Whether to add a newline or not
*
* @throws \RuntimeException When unable to write output (should never happen)
*/
public function doWrite($message, $newline)
{
if (false === @fwrite($this->stream, $message.($newline ? PHP_EOL : '')))
{
if (false === @fwrite($this->stream, $message.($newline ? PHP_EOL : ''))) {
// @codeCoverageIgnoreStart
// should never happen
throw new \RuntimeException('Unable to write output.');
@ -96,12 +95,9 @@ class StreamOutput extends Output
protected function hasColorSupport()
{
// @codeCoverageIgnoreStart
if (DIRECTORY_SEPARATOR == '\\')
{
if (DIRECTORY_SEPARATOR == '\\') {
return false !== getenv('ANSICON');
}
else
{
} else {
return function_exists('posix_isatty') && @posix_isatty($this->stream);
}
// @codeCoverageIgnoreEnd

View File

@ -7,7 +7,7 @@ use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\ConsoleOutput;
/*
* This file is part of the symfony framework.
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
@ -21,8 +21,6 @@ use Symfony\Component\Console\Output\ConsoleOutput;
* This class only works with a PHP compiled with readline support
* (either --with-readline or --with-libedit)
*
* @package symfony
* @subpackage cli
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Shell
@ -38,11 +36,12 @@ class Shell
* a \RuntimeException exception is thrown.
*
* @param Application $application An application instance
*
* @throws \RuntimeException When Readline extension is not enabled
*/
public function __construct(Application $application)
{
if (!function_exists('readline'))
{
if (!function_exists('readline')) {
throw new \RuntimeException('Unable to start the shell as the Readline extension is not enabled.');
}
@ -63,12 +62,10 @@ class Shell
readline_completion_function(array($this, 'autocompleter'));
$this->output->writeln($this->getHeader());
while (true)
{
while (true) {
$command = readline($this->application->getName().' > ');
if (false === $command)
{
if (false === $command) {
$this->output->writeln("\n");
break;
@ -77,8 +74,7 @@ class Shell
readline_add_history($command);
readline_write_history($this->history);
if (0 !== $ret = $this->application->run(new StringInput($command), $this->output))
{
if (0 !== $ret = $this->application->run(new StringInput($command), $this->output)) {
$this->output->writeln(sprintf('<error>The command terminated with an error status (%s)</error>', $ret));
}
}
@ -95,30 +91,24 @@ class Shell
$info = readline_info();
$text = substr($info['line_buffer'], 0, $info['end']);
if ($info['point'] !== $info['end'])
{
if ($info['point'] !== $info['end']) {
return true;
}
// task name?
if (false === strpos($text, ' ') || !$text)
{
return array_keys($this->application->getCommands());
if (false === strpos($text, ' ') || !$text) {
return array_keys($this->application->all());
}
// options and arguments?
try
{
try {
$command = $this->application->findCommand(substr($text, 0, strpos($text, ' ')));
}
catch (\Exception $e)
{
} catch (\Exception $e) {
return true;
}
$list = array('--help');
foreach ($command->getDefinition()->getOptions() as $option)
{
foreach ($command->getDefinition()->getOptions() as $option) {
$list[] = '--'.$option->getName();
}

View File

@ -6,6 +6,18 @@ use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class ApplicationTester
{
protected $application;
@ -38,18 +50,15 @@ class ApplicationTester
public function run(array $input, $options = array())
{
$this->input = new ArrayInput($input);
if (isset($options['interactive']))
{
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
}
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
if (isset($options['decorated']))
{
if (isset($options['decorated'])) {
$this->output->setDecorated($options['decorated']);
}
if (isset($options['verbosity']))
{
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}

View File

@ -6,6 +6,18 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class CommandTester
{
protected $command;
@ -37,19 +49,16 @@ class CommandTester
*/
public function execute(array $input, array $options = array())
{
$this->input = new ArrayInput(array_merge($input, array('command' => $this->command->getFullName())));
if (isset($options['interactive']))
{
$this->input = new ArrayInput($input);
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
}
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
if (isset($options['decorated']))
{
if (isset($options['decorated'])) {
$this->output->setDecorated($options['decorated']);
}
if (isset($options['verbosity']))
{
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}

@ -1 +1 @@
Subproject commit 3b5123434e979c7adfd42061484b5a8f10a3431b
Subproject commit 9eb66b7cf90919ff64281f7e476530fd879cefe4

@ -1 +1 @@
Subproject commit ce1e5f0f9c4f2e8894e1d2036ab11cc0597dd0a4
Subproject commit e97fbbf755ea7bc5d575d71778501a73f72a4ecb

View File

@ -8,7 +8,7 @@ namespace Doctrine\Tests\Models\Company;
class CompanyFixContract extends CompanyContract
{
/**
* @column(type="integer", nullable=true)
* @column(type="integer")
* @var int
*/
private $fixPrice = 0;

View File

@ -7,13 +7,13 @@ namespace Doctrine\Tests\Models\Company;
class CompanyFlexContract extends CompanyContract
{
/**
* @column(type="integer", nullable=true)
* @column(type="integer")
* @var int
*/
private $hoursWorked = 0;
/**
* @column(type="integer", nullable=true)
* @column(type="integer")
* @var int
*/
private $pricePerHour = 0;

View File

@ -8,7 +8,7 @@ namespace Doctrine\Tests\Models\Company;
class CompanyFlexUltraContract extends CompanyFlexContract
{
/**
* @column(type="integer", nullable=true)
* @column(type="integer")
* @var int
*/
private $maxPrice = 0;

View File

@ -0,0 +1,64 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Tests\Models\DirectoryTree;
/**
* @MappedSuperclass
*/
abstract class AbstractContentItem
{
/**
* @Id @Column(type="integer") @GeneratedValue
*/
private $id;
/**
* @ManyToOne(targetEntity="Directory")
*/
protected $parentDirectory;
/** @column(type="string") */
protected $name;
public function __construct(Directory $parentDir = null)
{
$this->parentDirectory = $parentDir;
}
public function getId()
{
return $this->id;
}
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function getParent()
{
return $this->parentDirectory;
}
}

View File

@ -0,0 +1,41 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Tests\Models\DirectoryTree;
/**
* @Entity
*/
class Directory extends AbstractContentItem
{
/**
* @Column(type="string")
*/
protected $path;
public function setPath($path)
{
$this->path = $path;
}
public function getPath()
{
return $this->path;
}
}

View File

@ -0,0 +1,46 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Tests\Models\DirectoryTree;
/**
* @Entity
* @Table(name="`file`")
*/
class File extends AbstractContentItem
{
/** @Column(type="string") */
protected $extension = "html";
public function __construct(Directory $parent = null)
{
parent::__construct($parent);
}
public function getExtension()
{
return $this->extension;
}
public function setExtension($ext)
{
$this->extension = $ext;
}
}

View File

@ -73,8 +73,8 @@ class ECommerceCategory
public function removeProduct(ECommerceProduct $product)
{
$removed = $this->products->removeElement($product);
if ($removed !== null) {
$removed->removeCategory($this);
if ($removed) {
$product->removeCategory($this);
}
}
@ -114,8 +114,8 @@ class ECommerceCategory
public function removeChild(ECommerceCategory $child)
{
$removed = $this->children->removeElement($child);
if ($removed !== null) {
$removed->removeParent();
if ($removed) {
$child->removeParent();
}
}

View File

@ -112,11 +112,10 @@ class ECommerceProduct
public function removeFeature(ECommerceFeature $feature)
{
$removed = $this->features->removeElement($feature);
if ($removed !== null) {
$removed->removeProduct();
return true;
if ($removed) {
$feature->removeProduct();
}
return false;
return $removed;
}
public function addCategory(ECommerceCategory $category)
@ -130,8 +129,8 @@ class ECommerceProduct
public function removeCategory(ECommerceCategory $category)
{
$removed = $this->categories->removeElement($category);
if ($removed !== null) {
$removed->removeProduct($this);
if ($removed) {
$category->removeProduct($this);
}
}

View File

@ -14,6 +14,16 @@ class EntityManagerTest extends \Doctrine\Tests\OrmTestCase
$this->_em = $this->_getTestEntityManager();
}
/**
* @group DDC-899
*/
public function testIsOpen()
{
$this->assertTrue($this->_em->isOpen());
$this->_em->close();
$this->assertFalse($this->_em->isOpen());
}
public function testGetConnection()
{
$this->assertType('\Doctrine\DBAL\Connection', $this->_em->getConnection());

View File

@ -3,6 +3,7 @@
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Query;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsAddress;
@ -264,6 +265,110 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('developer', $user->status);
}
/**
* @group DDC-833
*/
public function testRefreshResetsCollection()
{
$user = new CmsUser;
$user->name = 'Guilherme';
$user->username = 'gblanco';
$user->status = 'developer';
// Add a phonenumber
$ph1 = new CmsPhonenumber;
$ph1->phonenumber = "12345";
$user->addPhonenumber($ph1);
// Add a phonenumber
$ph2 = new CmsPhonenumber;
$ph2->phonenumber = "54321";
$this->_em->persist($user);
$this->_em->persist($ph1);
$this->_em->persist($ph2);
$this->_em->flush();
$user->addPhonenumber($ph2);
$this->assertEquals(2, count($user->phonenumbers));
$this->_em->refresh($user);
$this->assertEquals(1, count($user->phonenumbers));
}
/**
* @group DDC-833
*/
public function testDqlRefreshResetsCollection()
{
$user = new CmsUser;
$user->name = 'Guilherme';
$user->username = 'gblanco';
$user->status = 'developer';
// Add a phonenumber
$ph1 = new CmsPhonenumber;
$ph1->phonenumber = "12345";
$user->addPhonenumber($ph1);
// Add a phonenumber
$ph2 = new CmsPhonenumber;
$ph2->phonenumber = "54321";
$this->_em->persist($user);
$this->_em->persist($ph1);
$this->_em->persist($ph2);
$this->_em->flush();
$user->addPhonenumber($ph2);
$this->assertEquals(2, count($user->phonenumbers));
$dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1";
$user = $this->_em->createQuery($dql)
->setParameter(1, $user->id)
->setHint(Query::HINT_REFRESH, true)
->getSingleResult();
$this->assertEquals(1, count($user->phonenumbers));
}
/**
* @group DDC-833
*/
public function testCreateEntityOfProxy()
{
$user = new CmsUser;
$user->name = 'Guilherme';
$user->username = 'gblanco';
$user->status = 'developer';
// Add a phonenumber
$ph1 = new CmsPhonenumber;
$ph1->phonenumber = "12345";
$user->addPhonenumber($ph1);
// Add a phonenumber
$ph2 = new CmsPhonenumber;
$ph2->phonenumber = "54321";
$this->_em->persist($user);
$this->_em->persist($ph1);
$this->_em->persist($ph2);
$this->_em->flush();
$this->_em->clear();
$userId = $user->id;
$user = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsUser', $user->id);
$dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1";
$user = $this->_em->createQuery($dql)
->setParameter(1, $userId)
->getSingleResult();
$this->assertEquals(1, count($user->phonenumbers));
}
public function testAddToCollectionDoesNotInitialize()
{
$user = new CmsUser;

View File

@ -353,4 +353,61 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($manager->getId(), $dqlManager->getId());
$this->assertEquals($person->getId(), $dqlManager->getSpouse()->getId());
}
/**
* @group DDC-817
*/
public function testFindByAssociation()
{
$manager = new CompanyManager();
$manager->setName('gblanco');
$manager->setSalary(1234);
$manager->setTitle('Awesome!');
$manager->setDepartment('IT');
$person = new CompanyPerson();
$person->setName('spouse');
$manager->setSpouse($person);
$this->_em->persist($manager);
$this->_em->persist($person);
$this->_em->flush();
$this->_em->clear();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyManager');
$pmanager = $repos->findOneBy(array('spouse' => $person->getId()));
$this->assertEquals($manager->getId(), $pmanager->getId());
$repos = $this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyPerson');
$pmanager = $repos->findOneBy(array('spouse' => $person->getId()));
$this->assertEquals($manager->getId(), $pmanager->getId());
}
/**
* @group DDC-834
*/
public function testGetReferenceEntityWithSubclasses()
{
$manager = new CompanyManager();
$manager->setName('gblanco');
$manager->setSalary(1234);
$manager->setTitle('Awesome!');
$manager->setDepartment('IT');
$this->_em->persist($manager);
$this->_em->flush();
$this->_em->clear();
$ref = $this->_em->getReference('Doctrine\Tests\Models\Company\CompanyPerson', $manager->getId());
$this->assertNotType('Doctrine\ORM\Proxy\Proxy', $ref, "Cannot Request a proxy from a class that has subclasses.");
$this->assertType('Doctrine\Tests\Models\Company\CompanyPerson', $ref);
$this->assertType('Doctrine\Tests\Models\Company\CompanyEmployee', $ref, "Direct fetch of the reference has to load the child class Emplyoee directly.");
$this->_em->clear();
$ref = $this->_em->getReference('Doctrine\Tests\Models\Company\CompanyManager', $manager->getId());
$this->assertType('Doctrine\ORM\Proxy\Proxy', $ref, "A proxy can be generated only if no subclasses exists for the requested reference.");
}
}

View File

@ -54,7 +54,7 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user->status = 'developer';
$ph1 = new CmsPhonenumber;
$ph1->phonenumber = 1234;
$ph1->phonenumber = "1234";
$user->addPhonenumber($ph1);
$this->_em->persist($user);
@ -69,23 +69,35 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user = unserialize($serialized);
$this->assertEquals(1, count($user->getPhonenumbers()), "Pre-Condition: 1 Phonenumber");
$ph2 = new CmsPhonenumber;
$ph2->phonenumber = 56789;
$ph2->phonenumber = "56789";
$user->addPhonenumber($ph2);
$this->assertEquals(2, count($user->getPhonenumbers()));
$oldPhonenumbers = $user->getPhonenumbers();
$this->assertEquals(2, count($oldPhonenumbers), "Pre-Condition: 2 Phonenumbers");
$this->assertFalse($this->_em->contains($user));
$this->_em->persist($ph2);
// Merge back in
$user = $this->_em->merge($user); // merge cascaded to phonenumbers
$this->assertType('Doctrine\Tests\Models\CMS\CmsUser', $user->phonenumbers[0]->user);
$this->assertType('Doctrine\Tests\Models\CMS\CmsUser', $user->phonenumbers[1]->user);
$im = $this->_em->getUnitOfWork()->getIdentityMap();
$this->_em->flush();
$this->assertTrue($this->_em->contains($user));
$this->assertEquals(2, count($user->getPhonenumbers()));
$this->assertTrue($this->_em->contains($user), "Failed to assert that merged user is contained inside EntityManager persistence context.");
$phonenumbers = $user->getPhonenumbers();
$this->assertTrue($this->_em->contains($phonenumbers[0]));
$this->assertTrue($this->_em->contains($phonenumbers[1]));
$this->assertNotSame($oldPhonenumbers, $phonenumbers, "Merge should replace the Detached Collection with a new PersistentCollection.");
$this->assertEquals(2, count($phonenumbers), "Failed to assert that two phonenumbers are contained in the merged users phonenumber collection.");
$this->assertType('Doctrine\Tests\Models\CMS\CmsPhonenumber', $phonenumbers[1]);
$this->assertTrue($this->_em->contains($phonenumbers[1]), "Failed to assert that second phonenumber in collection is contained inside EntityManager persistence context.");
$this->assertType('Doctrine\Tests\Models\CMS\CmsPhonenumber', $phonenumbers[0]);
$this->assertTrue($this->_em->getUnitOfWork()->isInIdentityMap($phonenumbers[0]));
$this->assertTrue($this->_em->contains($phonenumbers[0]), "Failed to assert that first phonenumber in collection is contained inside EntityManager persistence context.");
}
/**
@ -105,8 +117,8 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
} catch (\Exception $expected) {}
}
public function testUninitializedLazyAssociationsAreIgnoredOnMerge() {
//$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger);
public function testUninitializedLazyAssociationsAreIgnoredOnMerge()
{
$user = new CmsUser;
$user->name = 'Guilherme';
$user->username = 'gblanco';
@ -136,5 +148,49 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertFalse($managedAddress2->user === $detachedAddress2->user);
$this->assertFalse($managedAddress2->user->__isInitialized__);
}
/**
* @group DDC-822
*/
public function testUseDetachedEntityAsQueryParameter()
{
$user = new CmsUser;
$user->name = 'Guilherme';
$user->username = 'gblanco';
$user->status = 'developer';
$this->_em->persist($user);
$this->_em->flush();
$this->_em->detach($user);
$dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1";
$query = $this->_em->createQuery($dql);
$query->setParameter(1, $user);
$newUser = $query->getSingleResult();
$this->assertType('Doctrine\Tests\Models\CMS\CmsUser', $newUser);
$this->assertEquals('gblanco', $newUser->username);
}
/**
* @group DDC-920
*/
public function testDetachManagedUnpersistedEntity()
{
$user = new CmsUser;
$user->name = 'Guilherme';
$user->username = 'gblanco';
$user->status = 'developer';
$this->_em->persist($user);
$this->_em->detach($user);
$this->_em->flush();
$this->assertFalse($this->_em->contains($user));
$this->assertFalse($this->_em->getUnitOfWork()->isInIdentityMap($user));
}
}

View File

@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsAddress;
require_once __DIR__ . '/../../TestInit.php';
@ -19,7 +20,8 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
parent::setUp();
}
public function testBasicFinders() {
public function loadFixture()
{
$user = new CmsUser;
$user->name = 'Roman';
$user->username = 'romanb';
@ -38,35 +40,58 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
unset($user2);
$this->_em->clear();
return $user1Id;
}
public function testBasicFind()
{
$user1Id = $this->loadFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$user = $repos->find($user1Id);
$this->assertTrue($user instanceof CmsUser);
$this->assertEquals('Roman', $user->name);
$this->assertEquals('freak', $user->status);
}
$this->_em->clear();
public function testFindByField()
{
$user1Id = $this->loadFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repos->findBy(array('status' => 'dev'));
$this->assertEquals(1, count($users));
$this->assertTrue($users[0] instanceof CmsUser);
$this->assertEquals('Guilherme', $users[0]->name);
$this->assertEquals('dev', $users[0]->status);
}
$this->_em->clear();
public function testFindFieldByMagicCall()
{
$user1Id = $this->loadFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repos->findByStatus('dev');
$this->assertEquals(1, count($users));
$this->assertTrue($users[0] instanceof CmsUser);
$this->assertEquals('Guilherme', $users[0]->name);
$this->assertEquals('dev', $users[0]->status);
}
$this->_em->clear();
public function testFindAll()
{
$user1Id = $this->loadFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repos->findAll();
$this->assertEquals(2, count($users));
}
$this->_em->clear();
public function testFindByAlias()
{
$user1Id = $this->loadFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$this->_em->getConfiguration()->addEntityNamespace('CMS', 'Doctrine\Tests\Models\CMS');
@ -74,8 +99,12 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$users = $repos->findAll();
$this->assertEquals(2, count($users));
}
public function tearDown()
{
$this->_em->getConfiguration()->setEntityNamespaces(array());
parent::tearDown();
}
/**
@ -150,5 +179,116 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->setExpectedException('Doctrine\ORM\OptimisticLockException');
$this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId, \Doctrine\DBAL\LockMode::OPTIMISTIC);
}
/**
* @group DDC-819
*/
public function testFindMagicCallByNullValue()
{
$this->loadFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$this->setExpectedException('Doctrine\ORM\ORMException');
$users = $repos->findByStatus(null);
}
/**
* @group DDC-819
*/
public function testInvalidMagicCall()
{
$this->setExpectedException('BadMethodCallException');
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$repos->foo();
}
public function loadAssociatedFixture()
{
$address = new CmsAddress();
$address->city = "Berlin";
$address->country = "Germany";
$address->street = "Foostreet";
$address->zip = "12345";
$user = new CmsUser();
$user->name = 'Roman';
$user->username = 'romanb';
$user->status = 'freak';
$user->setAddress($address);
$this->_em->persist($user);
$this->_em->persist($address);
$this->_em->flush();
$this->_em->clear();
return array($user->id, $address->id);
}
/**
* @group DDC-817
*/
public function testFindByAssociationKey_ExceptionOnInverseSide()
{
list($userId, $addressId) = $this->loadAssociatedFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$this->setExpectedException('Doctrine\ORM\ORMException', "You cannot search for the association field 'Doctrine\Tests\Models\CMS\CmsUser#address', because it is the inverse side of an association. Find methods only work on owning side associations.");
$user = $repos->findBy(array('address' => $addressId));
}
/**
* @group DDC-817
*/
public function testFindOneByAssociationKey()
{
list($userId, $addressId) = $this->loadAssociatedFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress');
$address = $repos->findOneBy(array('user' => $userId));
$this->assertType('Doctrine\Tests\Models\CMS\CmsAddress', $address);
$this->assertEquals($addressId, $address->id);
}
/**
* @group DDC-817
*/
public function testFindByAssociationKey()
{
list($userId, $addressId) = $this->loadAssociatedFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress');
$addresses = $repos->findBy(array('user' => $userId));
$this->assertContainsOnly('Doctrine\Tests\Models\CMS\CmsAddress', $addresses);
$this->assertEquals(1, count($addresses));
$this->assertEquals($addressId, $addresses[0]->id);
}
/**
* @group DDC-817
*/
public function testFindAssociationByMagicCall()
{
list($userId, $addressId) = $this->loadAssociatedFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress');
$addresses = $repos->findByUser($userId);
$this->assertContainsOnly('Doctrine\Tests\Models\CMS\CmsAddress', $addresses);
$this->assertEquals(1, count($addresses));
$this->assertEquals($addressId, $addresses[0]->id);
}
/**
* @group DDC-817
*/
public function testFindOneAssociationByMagicCall()
{
list($userId, $addressId) = $this->loadAssociatedFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress');
$address = $repos->findOneByUser($userId);
$this->assertType('Doctrine\Tests\Models\CMS\CmsAddress', $address);
$this->assertEquals($addressId, $address->id);
}
}

View File

@ -100,6 +100,20 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase
}
}
public function testMultipleFlushesDoIncrementalUpdates()
{
$test = new OptimisticStandard();
for ($i = 0; $i < 5; $i++) {
$test->name = 'test' . $i;
$this->_em->persist($test);
$this->_em->flush();
$this->assertType('int', $test->getVersion());
$this->assertEquals($i + 1, $test->getVersion());
}
}
public function testStandardInsertSetsInitialVersionValue()
{
$test = new OptimisticStandard();
@ -107,6 +121,7 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->persist($test);
$this->_em->flush();
$this->assertType('int', $test->getVersion());
$this->assertEquals(1, $test->getVersion());
return $test;
@ -139,10 +154,13 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
$test = new OptimisticTimestamp();
$test->name = 'Testing';
$this->assertNull($test->version, "Pre-Condition");
$this->_em->persist($test);
$this->_em->flush();
$this->assertTrue(strtotime($test->version) > 0);
$this->assertType('DateTime', $test->version);
return $test;
}

View File

@ -247,6 +247,34 @@ class ManyToManyBasicAssociationTest extends \Doctrine\Tests\OrmFunctionalTestCa
$this->assertEquals(0, count($newUser->getGroups()));
}
/**
* @group DDC-839
*/
public function testWorkWithDqlHydratedEmptyCollection()
{
$user = $this->addCmsUserGblancoWithGroups(0);
$group = new CmsGroup();
$group->name = "Developers0";
$this->_em->persist($group);
$this->_em->flush();
$this->_em->clear();
$newUser = $this->_em->createQuery('SELECT u, g FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.groups g WHERE u.id = ?1')
->setParameter(1, $user->getId())
->getSingleResult();
$this->assertEquals(0, count($newUser->groups));
$this->assertType('array', $newUser->groups->getMapping());
$newUser->addGroup($group);
$this->_em->flush();
$this->_em->clear();
$newUser = $this->_em->find(get_class($user), $user->getId());
$this->assertEquals(1, count($newUser->groups));
}
/**
* @param int $groupCount
* @return CmsUser

View File

@ -12,100 +12,35 @@ require_once __DIR__ . '/../../TestInit.php';
class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp() {
$this->useModelSet('directorytree');
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\EntitySubClass'),
));
} catch (\Exception $e) {
// Swallow all exceptions. We do not test the schema tool here.
}
}
public function testCRUD()
{
$e = new EntitySubClass;
$e->setId(1);
$e->setName('Roman');
$e->setMapped1(42);
$e->setMapped2('bar');
$root = new \Doctrine\Tests\Models\DirectoryTree\Directory();
$root->setName('Root');
$root->setPath('/root');
$directory = new \Doctrine\Tests\Models\DirectoryTree\Directory($root);
$directory->setName('TestA');
$directory->setPath('/root/dir');
$file = new \Doctrine\Tests\Models\DirectoryTree\File($directory);
$file->setName('test-b.html');
$this->_em->persist($root);
$this->_em->persist($directory);
$this->_em->persist($file);
$this->_em->persist($e);
$this->_em->flush();
$this->_em->clear();
$e2 = $this->_em->find('Doctrine\Tests\ORM\Functional\EntitySubClass', 1);
$this->assertEquals(1, $e2->getId());
$this->assertEquals('Roman', $e2->getName());
$this->assertNull($e2->getMappedRelated1());
$this->assertEquals(42, $e2->getMapped1());
$this->assertEquals('bar', $e2->getMapped2());
}
}
$cleanFile = $this->_em->find(get_class($file), $file->getId());
/** @MappedSuperclass */
class MappedSuperclassBase {
/** @Column(type="integer") */
private $mapped1;
/** @Column(type="string") */
private $mapped2;
/**
* @OneToOne(targetEntity="MappedSuperclassRelated1")
* @JoinColumn(name="related1_id", referencedColumnName="id")
*/
private $mappedRelated1;
private $transient;
public function setMapped1($val) {
$this->mapped1 = $val;
}
public function getMapped1() {
return $this->mapped1;
}
public function setMapped2($val) {
$this->mapped2 = $val;
}
public function getMapped2() {
return $this->mapped2;
}
public function getMappedRelated1() {
return $this->mappedRelated1;
$this->assertType('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent());
$this->assertEquals($directory->getId(), $cleanFile->getParent()->getId());
$this->assertType('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()->getParent());
$this->assertEquals($root->getId(), $cleanFile->getParent()->getParent()->getId());
}
}
/** @Entity */
class MappedSuperclassRelated1 {
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
}
/** @Entity */
class EntitySubClass extends MappedSuperclassBase {
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function setId($id) {
$this->id = $id;
}
public function getId() {
return $this->id;
}
}

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