1
0
mirror of synced 2025-01-30 20:11:49 +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 <?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'; require_once 'Doctrine/Common/ClassLoader.php';
@ -30,29 +47,4 @@ if (file_exists($configFile)) {
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet(); $helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
$cli = new \Symfony\Component\Console\Application('Doctrine Command Line Interface', Doctrine\ORM\Version::VERSION); \Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);
$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();

View File

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

View File

@ -5,8 +5,6 @@
--> -->
<project name="Doctrine2" default="build" basedir="."> <project name="Doctrine2" default="build" basedir=".">
<taskdef classname="NativePhpunitTask" classpath="./tests/" name="nativephpunit" />
<taskdef classname="phing.tasks.ext.d51PearPkg2Task" name="d51pearpkg2" /> <taskdef classname="phing.tasks.ext.d51PearPkg2Task" name="d51pearpkg2" />
<property file="build.properties" /> <property file="build.properties" />
@ -24,6 +22,7 @@
<fileset id="bin-scripts" dir="./bin"> <fileset id="bin-scripts" dir="./bin">
<include name="doctrine"/> <include name="doctrine"/>
<include name="doctrine.php"/> <include name="doctrine.php"/>
<include name="doctrine.bat"/>
</fileset> </fileset>
<!-- <!--
@ -51,7 +50,7 @@
Fileset for source of the Symfony YAML and Console components. Fileset for source of the Symfony YAML and Console components.
--> -->
<fileset id="symfony-sources" dir="./lib/vendor"> <fileset id="symfony-sources" dir="./lib/vendor">
<include name="Symfony/Components/**"/> <include name="Symfony/Component/**"/>
</fileset> </fileset>
<!-- <!--
@ -83,23 +82,24 @@
<!-- <!--
Builds ORM package, preparing it for distribution. Builds ORM package, preparing it for distribution.
--> -->
<target name="build-orm" depends="test"> <target name="build-orm" depends="prepare">
<copy todir="${build.dir}/orm"> <exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
<copy todir="${build.dir}/doctrine-orm">
<fileset refid="shared-artifacts"/> <fileset refid="shared-artifacts"/>
</copy> </copy>
<copy todir="${build.dir}/orm"> <copy todir="${build.dir}/doctrine-orm">
<fileset refid="common-sources"/> <fileset refid="common-sources"/>
<fileset refid="dbal-sources"/> <fileset refid="dbal-sources"/>
<fileset refid="orm-sources"/> <fileset refid="orm-sources"/>
</copy> </copy>
<copy todir="${build.dir}/orm/Doctrine"> <copy todir="${build.dir}/doctrine-orm/Doctrine">
<fileset refid="symfony-sources"/> <fileset refid="symfony-sources"/>
</copy> </copy>
<copy todir="${build.dir}/orm/bin"> <copy todir="${build.dir}/doctrine-orm/bin">
<fileset refid="bin-scripts"/> <fileset refid="bin-scripts"/>
</copy> </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="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}/orm/Doctrine/ORM/Version2.php ${build.dir}/orm/Doctrine/ORM/Version.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>
<target name="build" depends="test, build-orm"/> <target name="build" depends="test, build-orm"/>
@ -141,8 +141,8 @@
<!-- <!--
Builds distributable PEAR packages. Builds distributable PEAR packages.
--> -->
<target name="build-packages" depends="build"> <target name="build-packages" depends="build-orm">
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/orm"> <d51pearpkg2 baseinstalldir="/" dir="${build.dir}/doctrine-orm">
<name>DoctrineORM</name> <name>DoctrineORM</name>
<summary>Doctrine Object Relational Mapper</summary> <summary>Doctrine Object Relational Mapper</summary>
<channel>pear.doctrine-project.org</channel> <channel>pear.doctrine-project.org</channel>
@ -150,6 +150,7 @@
<lead user="jwage" name="Jonathan H. Wage" email="jonwage@gmail.com" /> <lead user="jwage" name="Jonathan H. Wage" email="jonwage@gmail.com" />
<lead user="guilhermeblanco" name="Guilherme Blanco" email="guilhermeblanco@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="romanb" name="Roman Borschel" email="roman@code-factory.org" />
<lead user="beberlei" name="Benjamin Eberlei" email="kontakt@beberlei.de" />
<license>LGPL</license> <license>LGPL</license>
<version release="${version}" api="${version}" /> <version release="${version}" api="${version}" />
<stability release="${stability}" api="${stability}" /> <stability release="${stability}" api="${stability}" />
@ -157,17 +158,55 @@
<dependencies> <dependencies>
<php minimum_version="5.3.0" /> <php minimum_version="5.3.0" />
<pear minimum_version="1.6.0" recommended_version="1.6.1" /> <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> </dependencies>
<dirroles key="bin">script</dirroles> <dirroles key="bin">script</dirroles>
<replacement path="bin/doctrine" type="pear-config" from="@php_bin@" to="php_bin" /> <ignore>Doctrine/Common/</ignore>
<replacement path="bin/doctrine.php" type="pear-config" from="@php_bin@" to="php_bin" /> <ignore>Doctrine/DBAL/</ignore>
<replacement path="bin/doctrine.php" type="pear-config" from="@bin_dir@" to="bin_dir" />
<release> <release>
<install as="doctrine" name="bin/doctrine" /> <install as="doctrine" name="bin/doctrine" />
<install as="doctrine.php" name="bin/doctrine.php" /> <install as="doctrine.php" name="bin/doctrine.php" />
<install as="doctrine.bat" name="bin/doctrine.bat" />
</release> </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> </d51pearpkg2>
<exec command="pear package" 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}/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>
<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> </project>

View File

@ -63,7 +63,7 @@
<xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/> <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="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" /> <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="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-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" /> <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="name" type="xs:string" use="required" />
<xs:attribute name="table" type="xs:NMTOKEN" /> <xs:attribute name="table" type="xs:NMTOKEN" />
<xs:attribute name="schema" 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="inheritance-type" type="orm:inheritance-type"/>
<xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" /> <xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" />
</xs:complexType> </xs:complexType>
@ -252,7 +252,7 @@
<xs:element name="join-table" type="orm:join-table" minOccurs="0" /> <xs:element name="join-table" type="orm:join-table" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" /> <xs:element name="order-by" type="orm:order-by" minOccurs="0" />
</xs:sequence> </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="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" /> <xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-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="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" /> <xs:element name="order-by" type="orm:order-by" minOccurs="0" />
</xs:sequence> </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="mapped-by" type="xs:NMTOKEN" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" /> <xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" /> <xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
@ -279,7 +279,7 @@
<xs:element name="join-columns" type="orm:join-columns"/> <xs:element name="join-columns" type="orm:join-columns"/>
</xs:choice> </xs:choice>
</xs:sequence> </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="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" /> <xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" /> <xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
@ -295,7 +295,7 @@
</xs:choice> </xs:choice>
</xs:sequence> </xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" /> <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="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" /> <xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" /> <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) 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) { if ($hydrationMode !== null) {
$this->setHydrationMode($hydrationMode); $this->setHydrationMode($hydrationMode);
} }
@ -562,9 +553,22 @@ abstract class AbstractQuery
if ($this->_resultCacheId) { if ($this->_resultCacheId) {
return $this->_resultCacheId; return $this->_resultCacheId;
} else { } 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(); $sql = $this->getSql();
ksort($this->_hints); 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); var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode);
} }
} }
@ -583,6 +587,8 @@ abstract class AbstractQuery
*/ */
public function __clone() 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; $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->conn = $conn;
$this->config = $config; $this->config = $config;
$this->eventManager = $eventManager; $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->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->unitOfWork = new UnitOfWork($this); $this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory($this, $this->proxyFactory = new ProxyFactory($this,
$config->getProxyDir(), $config->getProxyDir(),
@ -352,11 +356,15 @@ class EntityManager
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) { if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
return $entity; return $entity;
} }
if ( ! is_array($identifier)) { if ($class->subClasses) {
$identifier = array($class->identifier[0] => $identifier); $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());
} }
$entity = $this->proxyFactory->getProxy($class->name, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, array());
return $entity; 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. * Gets the UnitOfWork used by the EntityManager to coordinate operations.
* *

View File

@ -133,7 +133,6 @@ class EntityRepository
/** /**
* Finds all entities in the repository. * Finds all entities in the repository.
* *
* @param int $hydrationMode
* @return array The entities. * @return array The entities.
*/ */
public function findAll() public function findAll()
@ -144,8 +143,7 @@ class EntityRepository
/** /**
* Finds entities by a set of criteria. * Finds entities by a set of criteria.
* *
* @param string $column * @param array $criteria
* @param string $value
* @return array * @return array
*/ */
public function findBy(array $criteria) public function findBy(array $criteria)
@ -156,8 +154,7 @@ class EntityRepository
/** /**
* Finds a single entity by a set of criteria. * Finds a single entity by a set of criteria.
* *
* @param string $column * @param array $criteria
* @param string $value
* @return object * @return object
*/ */
public function findOneBy(array $criteria) public function findOneBy(array $criteria)
@ -188,13 +185,14 @@ class EntityRepository
); );
} }
if ( ! isset($arguments[0])) { 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); throw ORMException::findByRequiresParameter($method.$by);
} }
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($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])); return $this->$method(array($fieldName => $arguments[0]));
} else { } else {
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by); throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);

View File

@ -4,6 +4,9 @@ namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs; use Doctrine\Common\EventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\EntityManager;
/** /**
* Class that holds event arguments for a loadMetadata event. * Class that holds event arguments for a loadMetadata event.
* *
@ -12,16 +15,40 @@ use Doctrine\Common\EventArgs;
*/ */
class LoadClassMetadataEventArgs extends 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() 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); $class->reflFields[$fieldName]->setValue($entity, $value);
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
$this->_initializedCollections[$oid . $fieldName] = $value; $this->_initializedCollections[$oid . $fieldName] = $value;
} else if (isset($this->_hints[Query::HINT_REFRESH])) { } else if (isset($this->_hints[Query::HINT_REFRESH]) ||
// Is already PersistentCollection, but 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->setDirty(false);
$value->setInitialized(true); $value->setInitialized(true);
$value->unwrap()->clear(); $value->unwrap()->clear();
$this->_initializedCollections[$oid . $fieldName] = $value; $this->_initializedCollections[$oid . $fieldName] = $value;
} else { } else {
// Is already PersistentCollection, and DONT REFRESH // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN!
$this->_existingCollections[$oid . $fieldName] = $value; $this->_existingCollections[$oid . $fieldName] = $value;
} }
@ -269,6 +271,9 @@ class ObjectHydrator extends AbstractHydrator
// It's a joined result // It's a joined result
$parentAlias = $this->_rsm->parentAliasMap[$dqlAlias]; $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. // Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) {
@ -298,8 +303,8 @@ class ObjectHydrator extends AbstractHydrator
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField);
} }
$indexExists = isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]]); $indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] : false; $index = $indexExists ? $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false;
if ( ! $indexExists || ! $indexIsValid) { if ( ! $indexExists || ! $indexIsValid) {
@ -317,11 +322,11 @@ class ObjectHydrator extends AbstractHydrator
$field = $this->_rsm->indexByMap[$dqlAlias]; $field = $this->_rsm->indexByMap[$dqlAlias];
$indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element); $indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
$reflFieldValue->hydrateSet($indexValue, $element); $reflFieldValue->hydrateSet($indexValue, $element);
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $indexValue; $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
} else { } else {
$reflFieldValue->hydrateAdd($element); $reflFieldValue->hydrateAdd($element);
$reflFieldValue->last(); $reflFieldValue->last();
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $reflFieldValue->key(); $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
} }
// Update result pointer // Update result pointer
$this->_resultPointers[$dqlAlias] = $element; $this->_resultPointers[$dqlAlias] = $element;
@ -332,6 +337,7 @@ class ObjectHydrator extends AbstractHydrator
} }
} else if ( ! $reflField->getValue($parentObject)) { } else if ( ! $reflField->getValue($parentObject)) {
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection); $coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection);
$coll->setOwner($parentObject, $relation);
$reflField->setValue($parentObject, $coll); $reflField->setValue($parentObject, $coll);
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll); $this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
} }

View File

@ -74,11 +74,9 @@ class ClassMetadataFactory
private $initialized = false; private $initialized = false;
/** /**
* Creates a new factory instance that uses the given metadata driver implementation. * @param EntityManager $$em
*
* @param $driver The metadata driver to use.
*/ */
public function __construct(EntityManager $em) public function setEntityManager(EntityManager $em)
{ {
$this->em = $em; $this->em = $em;
} }
@ -262,15 +260,19 @@ class ClassMetadataFactory
$class = $this->newClassMetadataInstance($className); $class = $this->newClassMetadataInstance($className);
if ($parent) { if ($parent) {
$class->setInheritanceType($parent->inheritanceType); if (!$parent->isMappedSuperclass) {
$class->setDiscriminatorColumn($parent->discriminatorColumn); $class->setInheritanceType($parent->inheritanceType);
$class->setDiscriminatorColumn($parent->discriminatorColumn);
}
$class->setIdGeneratorType($parent->generatorType); $class->setIdGeneratorType($parent->generatorType);
$this->addInheritedFields($class, $parent); $this->addInheritedFields($class, $parent);
$this->addInheritedRelations($class, $parent); $this->addInheritedRelations($class, $parent);
$class->setIdentifier($parent->identifier); $class->setIdentifier($parent->identifier);
$class->setVersioned($parent->isVersioned); $class->setVersioned($parent->isVersioned);
$class->setVersionField($parent->versionField); $class->setVersionField($parent->versionField);
$class->setDiscriminatorMap($parent->discriminatorMap); if (!$parent->isMappedSuperclass) {
$class->setDiscriminatorMap($parent->discriminatorMap);
}
$class->setLifecycleCallbacks($parent->lifecycleCallbacks); $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy); $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
} }
@ -302,6 +304,17 @@ class ClassMetadataFactory
$this->completeIdGeneratorMapping($class); $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 // verify inheritance
if (!$parent && !$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) { if (!$parent && !$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
if (count($class->discriminatorMap) == 0) { 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; $this->loadedMetadata[$className] = $class;
$parent = $class; $parent = $class;
@ -379,6 +381,13 @@ class ClassMetadataFactory
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass) private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
{ {
foreach ($parentClass->associationMappings as $field => $mapping) { 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; //$subclassMapping = $mapping;
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name; $mapping['inherited'] = $parentClass->name;

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@ class DatabaseDriver implements Driver
*/ */
private $tables = null; private $tables = null;
private $classes = array(); private $classToTableNames = array();
/** /**
* @var array * @var array
@ -73,11 +73,11 @@ class DatabaseDriver implements Driver
} }
foreach ($this->_sm->listTableNames() as $tableName) { foreach ($this->_sm->listTableNames() as $tableName) {
$tables[strtolower($tableName)] = $this->_sm->listTableDetails($tableName); $tables[$tableName] = $this->_sm->listTableDetails($tableName);
} }
$this->tables = array(); $this->tables = array();
foreach ($tables AS $name => $table) { foreach ($tables AS $tableName => $table) {
/* @var $table Table */ /* @var $table Table */
if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) { if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$foreignKeys = $table->getForeignKeys(); $foreignKeys = $table->getForeignKeys();
@ -96,14 +96,16 @@ class DatabaseDriver implements Driver
if ($pkColumns == $allForeignKeyColumns) { if ($pkColumns == $allForeignKeyColumns) {
if (count($table->getForeignKeys()) > 2) { 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 { } else {
$className = Inflector::classify($name); // lower-casing is necessary because of Oracle Uppercase Tablenames,
$this->tables[$name] = $table; // assumption is lower-case + underscore separated.
$this->classes[$className] = $name; $className = Inflector::classify(strtolower($tableName));
$this->tables[$tableName] = $table;
$this->classToTableNames[$className] = $tableName;
} }
} }
} }
@ -115,11 +117,11 @@ class DatabaseDriver implements Driver
{ {
$this->reverseEngineerMappingFromDatabase(); $this->reverseEngineerMappingFromDatabase();
if (!isset($this->classes[$className])) { if (!isset($this->classToTableNames[$className])) {
throw new \InvalidArgumentException("Unknown class " . $className); throw new \InvalidArgumentException("Unknown class " . $className);
} }
$tableName = Inflector::tableize($className); $tableName = $this->classToTableNames[$className];
$metadata->name = $className; $metadata->name = $className;
$metadata->table['name'] = $tableName; $metadata->table['name'] = $tableName;
@ -183,7 +185,7 @@ class DatabaseDriver implements Driver
foreach ($this->manyToManyTables AS $manyTable) { foreach ($this->manyToManyTables AS $manyTable) {
foreach ($manyTable->getForeignKeys() AS $foreignKey) { foreach ($manyTable->getForeignKeys() AS $foreignKey) {
if ($tableName == strtolower($foreignKey->getForeignTableName())) { if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) {
$myFk = $foreignKey; $myFk = $foreignKey;
foreach ($manyTable->getForeignKeys() AS $foreignKey) { foreach ($manyTable->getForeignKeys() AS $foreignKey) {
if ($foreignKey != $myFk) { if ($foreignKey != $myFk) {
@ -269,6 +271,6 @@ class DatabaseDriver implements Driver
{ {
$this->reverseEngineerMappingFromDatabase(); $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 { final class JoinTable extends Annotation {
public $name; public $name;
public $schema; public $schema;
public $joinColumns; public $joinColumns = array();
public $inverseJoinColumns; public $inverseJoinColumns = array();
} }
final class SequenceGenerator extends Annotation { final class SequenceGenerator extends Annotation {
public $sequenceName; public $sequenceName;

View File

@ -88,10 +88,15 @@ class DriverChain implements Driver
public function getAllClassNames() public function getAllClassNames()
{ {
$classNames = array(); $classNames = array();
foreach ($this->_drivers AS $driver) { foreach ($this->_drivers AS $namespace => $driver) {
$classNames = array_merge($classNames, $driver->getAllClassNames()); $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) { foreach ($this->_paths as $path) {
if ( ! is_dir($path)) { if ( ! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath(); throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
} }
$iterator = new \RecursiveIteratorIterator( $iterator = new \RecursiveIteratorIterator(

View File

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

View File

@ -79,6 +79,8 @@ class YamlDriver extends AbstractFileDriver
'type' => $discrColumn['type'], 'type' => $discrColumn['type'],
'length' => $discrColumn['length'] 'length' => $discrColumn['length']
)); ));
} else {
$metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255));
} }
// Evaluate discriminatorMap // 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'."); 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) 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."); 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 $className
* @param string $columnName * @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."); 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() { public static function invalidResultCacheDriver() {
return new self("Invalid result cache driver; it must implement \Doctrine\Common\Cache\Cache."); 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 * Initializes the collection by loading its contents from the database
* if the collection is not yet initialized. * if the collection is not yet initialized.
*/ */
private function initialize() public function initialize()
{ {
if ( ! $this->initialized && $this->association) { if ( ! $this->initialized && $this->association) {
if ($this->isDirty) { if ($this->isDirty) {

View File

@ -33,11 +33,18 @@ use Doctrine\ORM\Mapping\ClassMetadata,
abstract class AbstractEntityInheritancePersister extends BasicEntityPersister 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 * @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} * {@inheritdoc}
@ -70,8 +77,8 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
unset($sqlResult[$discrColumnName]); unset($sqlResult[$discrColumnName]);
foreach ($sqlResult as $column => $value) { foreach ($sqlResult as $column => $value) {
$realColumnName = $this->_resultColumnNames[$column]; $realColumnName = $this->_resultColumnNames[$column];
if (isset($this->_declaringClassMap[$column])) { if (isset($this->declaringClassMap[$column])) {
$class = $this->_declaringClassMap[$column]; $class = $this->declaringClassMap[$column];
if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) { if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
$field = $class->fieldNames[$realColumnName]; $field = $class->fieldNames[$realColumnName];
if (isset($data[$field])) { if (isset($data[$field])) {
@ -81,6 +88,10 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
->convertToPHPValue($value, $this->_platform); ->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 { } else {
$data[$realColumnName] = $value; $data[$realColumnName] = $value;
} }
@ -99,9 +110,21 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++); $columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
if ( ! isset($this->_resultColumnNames[$columnAlias])) { if ( ! isset($this->_resultColumnNames[$columnAlias])) {
$this->_resultColumnNames[$columnAlias] = $columnName; $this->_resultColumnNames[$columnAlias] = $columnName;
$this->_declaringClassMap[$columnAlias] = $class; $this->declaringClassMap[$columnAlias] = $class;
} }
return "$sql AS $columnAlias"; 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) $sql = "SELECT " . $versionFieldColumnName . " FROM " . $class->getQuotedTableName($this->_platform)
. " WHERE " . implode(' = ? AND ', $identifier) . " = ?"; . " WHERE " . implode(' = ? AND ', $identifier) . " = ?";
$value = $this->_conn->fetchColumn($sql, array_values((array)$id)); $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); $this->_class->setFieldValue($entity, $versionField, $value);
} }
@ -273,7 +275,15 @@ class BasicEntityPersister
$updateData = $this->_prepareUpdateData($entity); $updateData = $this->_prepareUpdateData($entity);
$tableName = $this->_class->table['name']; $tableName = $this->_class->table['name'];
if (isset($updateData[$tableName]) && $updateData[$tableName]) { 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. * The UPDATE can optionally be versioned, which requires the entity to have a version field.
* *
* @param object $entity The entity object being updated. * @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 array $updateData The map of columns to update (column => value).
* @param boolean $versioned Whether the UPDATE should be versioned. * @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(); $set = $params = $types = array();
@ -317,7 +327,7 @@ class BasicEntityPersister
if ($versioned) { if ($versioned) {
$versionField = $this->_class->versionField; $versionField = $this->_class->versionField;
$versionFieldType = $this->_class->getTypeOfField($versionField); $versionFieldType = $this->_class->fieldMappings[$versionField]['type'];
$versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform); $versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform);
if ($versionFieldType == Type::INTEGER) { if ($versionFieldType == Type::INTEGER) {
$set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1';
@ -329,7 +339,7 @@ class BasicEntityPersister
$types[] = $this->_class->fieldMappings[$versionField]['type']; $types[] = $this->_class->fieldMappings[$versionField]['type'];
} }
$sql = "UPDATE $tableName SET " . implode(', ', $set) $sql = "UPDATE $quotedTableName SET " . implode(', ', $set)
. ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?';
$result = $this->_conn->executeUpdate($sql, $params, $types); $result = $this->_conn->executeUpdate($sql, $params, $types);
@ -393,17 +403,7 @@ class BasicEntityPersister
$this->deleteJoinTableRecords($identifier); $this->deleteJoinTableRecords($identifier);
$id = array_combine($this->_class->getIdentifierColumnNames(), $identifier); $id = array_combine($this->_class->getIdentifierColumnNames(), $identifier);
$this->_conn->delete($this->_class->table['name'], $id); $this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $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;
} }
/** /**
@ -660,7 +660,7 @@ class BasicEntityPersister
if ($found = $this->_em->getUnitOfWork()->tryGetById($joinColumnValues, $targetClass->rootEntityName)) { if ($found = $this->_em->getUnitOfWork()->tryGetById($joinColumnValues, $targetClass->rootEntityName)) {
$this->_class->reflFields[$field]->setValue($entity, $found); $this->_class->reflFields[$field]->setValue($entity, $found);
// Complete inverse side, if necessary. // Complete inverse side, if necessary.
if ($assoc['inversedBy']) { if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
$inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']]; $inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($found, $entity); $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($found, $entity);
} }
@ -681,6 +681,9 @@ class BasicEntityPersister
} }
} else if ($value instanceof PersistentCollection && $value->isInitialized()) { } else if ($value instanceof PersistentCollection && $value->isInitialized()) {
$value->setInitialized(false); $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; $newData[$field] = $value;
} }
} }
@ -848,8 +851,8 @@ class BasicEntityPersister
} }
return 'SELECT ' . $this->_getSelectColumnListSQL() return 'SELECT ' . $this->_getSelectColumnListSQL()
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $this->_platform->appendLockHint(' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name) . $this->_getSQLTableAlias($this->_class->name), $lockMode)
. $joinSql . $joinSql
. ($conditionSql ? ' WHERE ' . $conditionSql : '') . ($conditionSql ? ' WHERE ' . $conditionSql : '')
. $orderBySql . $orderBySql
@ -1086,7 +1089,7 @@ class BasicEntityPersister
} }
$sql = 'SELECT 1 ' $sql = 'SELECT 1 '
. $this->getLockTablesSql() . $this->_platform->appendLockHint($this->getLockTablesSql(), $lockMode)
. ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql; . ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' ' . $lockSql;
$params = array_values($criteria); $params = array_values($criteria);
$this->_conn->executeQuery($sql, $params); $this->_conn->executeQuery($sql, $params);
@ -1128,9 +1131,16 @@ class BasicEntityPersister
} }
$conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform); $conditionSql .= $this->_class->getQuotedColumnName($field, $this->_platform);
} else if (isset($this->_class->associationMappings[$field])) { } 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 ;) // TODO: Composite Keys as Foreign Key PK? That would be super ugly! And should probably be disallowed ;)
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.'; 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']; $conditionSql .= $this->_class->associationMappings[$field]['joinColumns'][0]['name'];
} else if ($assoc !== null) { } 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. * Map that maps column names to the table names that own them.
* This is mainly a temporary cache, used during a single request. * This is mainly a temporary cache, used during a single request.
*
* @var array
*/ */
private $_owningTableMap = array(); private $_owningTableMap = array();
/**
* Map of table to quoted table names.
*
* @var array
*/
private $_quotedTableMap = array();
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -74,18 +83,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
*/ */
public function getOwningTable($fieldName) public function getOwningTable($fieldName)
{ {
if ( ! isset($this->_owningTableMap[$fieldName])) { if (!isset($this->_owningTableMap[$fieldName])) {
if (isset($this->_class->associationMappings[$fieldName]['inherited'])) { if (isset($this->_class->associationMappings[$fieldName]['inherited'])) {
$this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata( $cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']);
$this->_class->associationMappings[$fieldName]['inherited']
)->table['name'];
} else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) { } else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) {
$this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata( $cm = $this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited']);
$this->_class->fieldMappings[$fieldName]['inherited']
)->table['name'];
} else { } 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]; return $this->_owningTableMap[$fieldName];
@ -186,17 +193,21 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$updateData = $this->_prepareUpdateData($entity); $updateData = $this->_prepareUpdateData($entity);
if ($isVersioned = $this->_class->isVersioned) { if ($isVersioned = $this->_class->isVersioned) {
$versionedTable = $this->_getVersionedClassMetadata()->table['name']; $versionedClass = $this->_getVersionedClassMetadata();
$versionedTable = $versionedClass->table['name'];
} }
if ($updateData) { if ($updateData) {
foreach ($updateData as $tableName => $data) { 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 // Make sure the table with the version column is updated even if no columns on that
// table were affected. // table were affected.
if ($isVersioned && ! isset($updateData[$versionedTable])) { 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']) $this->_getSQLTableAlias($assoc2['inherited'])
: $baseTableAlias; : $baseTableAlias;
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
$columnAlias = $srcColumn . $this->_sqlAliasCounter++; if ($columnList != '') $columnList .= ', ';
$columnList .= ", $tableAlias.$srcColumn AS $columnAlias"; $columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name
if ( ! isset($this->_resultColumnNames[$resultColumnName])) { );
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
}
} }
} }
} }
@ -307,12 +316,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE
&& ! isset($assoc2['inherited'])) { && ! isset($assoc2['inherited'])) {
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) { foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
$columnAlias = $srcColumn . $this->_sqlAliasCounter++; if ($columnList != '') $columnList .= ', ';
$columnList .= ', ' . $tableAlias . ".$srcColumn AS $columnAlias"; $columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias); isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name
if ( ! isset($this->_resultColumnNames[$resultColumnName])) { );
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
}
} }
} }
} }
@ -354,6 +361,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
*/ */
public function getLockTablesSql() public function getLockTablesSql()
{ {
$idColumns = $this->_class->getIdentifierColumnNames();
$baseTableAlias = $this->_getSQLTableAlias($this->_class->name); $baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
// INNER JOIN parent tables // INNER JOIN parent tables

View File

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

View File

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

View File

@ -242,10 +242,14 @@ final class Query extends AbstractQuery
} }
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) { if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
//TODO: Check that $value is MANAGED? if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
$values = $this->_em->getUnitOfWork()->getEntityIdentifier($value); $idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
} else {
$class = $this->_em->getClassMetadata(get_class($value));
$idValues = $class->getIdentifierValues($value);
}
$sqlPositions = $paramMappings[$key]; $sqlPositions = $paramMappings[$key];
$sqlParams += array_combine((array)$sqlPositions, $values); $sqlParams += array_combine((array)$sqlPositions, $idValues);
} else { } else {
foreach ($paramMappings[$key] as $position) { foreach ($paramMappings[$key] as $position) {
$sqlParams[$position] = $value; $sqlParams[$position] = $value;
@ -544,4 +548,15 @@ final class Query extends AbstractQuery
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT' '&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) public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{ {
//TODO: Use platform to get SQL return $sqlWalker->getConnection()->getDatabasePlatform()->getLengthExpression(
return 'LENGTH(' . $sqlWalker->walkStringPrimary($this->stringPrimary) . ')'; $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary)
);
} }
/** /**

View File

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

View File

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

View File

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

View File

@ -125,6 +125,11 @@ class Parser
*/ */
private $_customOutputWalker; private $_customOutputWalker;
/**
* @var array
*/
private $_identVariableExpressions = array();
/** /**
* Creates a new query parser object. * Creates a new query parser object.
* *
@ -272,6 +277,9 @@ class Parser
{ {
$AST = $this->getAST(); $AST = $this->getAST();
$this->fixIdentificationVariableOrder($AST);
$this->assertSelectEntityRootAliasRequirement();
if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) { if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
$this->_customTreeWalkers = $customWalkers; $this->_customTreeWalkers = $customWalkers;
} }
@ -313,6 +321,46 @@ class Parser
return $this->_parserResult; 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. * Generates a new syntax error.
* *
@ -1628,6 +1676,7 @@ class Parser
public function SelectExpression() public function SelectExpression()
{ {
$expression = null; $expression = null;
$identVariable = null;
$fieldAliasIdentificationVariable = null; $fieldAliasIdentificationVariable = null;
$peek = $this->_lexer->glimpse(); $peek = $this->_lexer->glimpse();
@ -1639,7 +1688,7 @@ class Parser
$expression = $this->ScalarExpression(); $expression = $this->ScalarExpression();
} else { } else {
$supportsAlias = false; $supportsAlias = false;
$expression = $this->IdentificationVariable(); $expression = $identVariable = $this->IdentificationVariable();
} }
} else if ($this->_lexer->lookahead['value'] == '(') { } else if ($this->_lexer->lookahead['value'] == '(') {
if ($peek['type'] == Lexer::T_SELECT) { if ($peek['type'] == Lexer::T_SELECT) {
@ -1666,6 +1715,7 @@ class Parser
} else if ($this->_lexer->lookahead['type'] == Lexer::T_PARTIAL) { } else if ($this->_lexer->lookahead['type'] == Lexer::T_PARTIAL) {
$supportsAlias = false; $supportsAlias = false;
$expression = $this->PartialObjectExpression(); $expression = $this->PartialObjectExpression();
$identVariable = $expression->identificationVariable;
} else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER || } else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER ||
$this->_lexer->lookahead['type'] == Lexer::T_FLOAT) { $this->_lexer->lookahead['type'] == Lexer::T_FLOAT) {
// Shortcut: ScalarExpression => SimpleArithmeticExpression // 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]; $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'] : ''); . (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 * Gets a string representation of this QueryBuilder which corresponds to
* the final DQL query being constructed. * the final DQL query being constructed.
@ -898,4 +932,24 @@ class QueryBuilder
{ {
return $this->getDQL(); 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.') ->setDescription('Clear result cache of the various cache drivers.')
->setDefinition(array( ->setDefinition(array(
new InputOption( 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() 'ID(s) of the cache entry to delete (accepts * wildcards).', array()
), ),
new InputOption( 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() 'Delete cache entries that match the given regular expression(s).', array()
), ),
new InputOption( 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() 'Delete cache entries that have the given prefix(es).', array()
), ),
new InputOption( 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() '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.' 'The path to generate your Doctrine 2.X mapping information.'
), ),
new InputOption( 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.', 'Optional paths of Doctrine 1.X schema information.',
array() array()
), ),
new InputOption( new InputOption(
'extend', null, InputOption::PARAMETER_OPTIONAL, 'extend', null, InputOption::VALUE_OPTIONAL,
'Defines a base class to be extended by generated entity classes.' 'Defines a base class to be extended by generated entity classes.'
), ),
new InputOption( new InputOption(
'num-spaces', null, InputOption::PARAMETER_OPTIONAL, 'num-spaces', null, InputOption::VALUE_OPTIONAL,
'Defines the number of indentation spaces', 4 '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.') ->setDescription('Convert mapping information between supported formats.')
->setDefinition(array( ->setDefinition(array(
new InputOption( 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.' 'A string pattern used to match entities that should be processed.'
), ),
new InputArgument( 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.' 'from-database', null, null, 'Whether or not to convert mapping information from existing database.'
), ),
new InputOption( new InputOption(
'extend', null, InputOption::PARAMETER_OPTIONAL, 'extend', null, InputOption::VALUE_OPTIONAL,
'Defines a base class to be extended by generated entity classes.' 'Defines a base class to be extended by generated entity classes.'
), ),
new InputOption( new InputOption(
'num-spaces', null, InputOption::PARAMETER_OPTIONAL, 'num-spaces', null, InputOption::VALUE_OPTIONAL,
'Defines the number of indentation spaces', 4 '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 = $cmf->getAllMetadata();
$metadata = MetadataFilter::filter($metadata, $input->getOption('filter')); $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.') ->setDescription('Verify that Doctrine is properly configured for a production environment.')
->setDefinition(array( ->setDefinition(array(
new InputOption( new InputOption(
'complete', null, InputOption::PARAMETER_NONE, 'complete', null, InputOption::VALUE_NONE,
'Flag to also inspect database connection existance.' '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.') ->setDescription('Generate entity classes and method stubs from your mapping information.')
->setDefinition(array( ->setDefinition(array(
new InputOption( 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.' 'A string pattern used to match entities that should be processed.'
), ),
new InputArgument( new InputArgument(
'dest-path', InputArgument::REQUIRED, 'The path to generate your entity classes.' 'dest-path', InputArgument::REQUIRED, 'The path to generate your entity classes.'
), ),
new InputOption( 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 'Flag to define if generator should generate annotation metadata on entities.', false
), ),
new InputOption( 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 'Flag to define if generator should generate stub methods on entities.', true
), ),
new InputOption( 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 'Flag to define if generator should regenerate entity if it exists.', false
), ),
new InputOption( 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 'Flag to define if generator should only update entity if it exists.', true
), ),
new InputOption( new InputOption(
'extend', null, InputOption::PARAMETER_OPTIONAL, 'extend', null, InputOption::VALUE_OPTIONAL,
'Defines a base class to be extended by generated entity classes.' 'Defines a base class to be extended by generated entity classes.'
), ),
new InputOption( new InputOption(
'num-spaces', null, InputOption::PARAMETER_OPTIONAL, 'num-spaces', null, InputOption::VALUE_OPTIONAL,
'Defines the number of indentation spaces', 4 'Defines the number of indentation spaces', 4
) )
)) ))
@ -96,7 +96,8 @@ EOT
{ {
$em = $this->getHelper('em')->getEntityManager(); $em = $this->getHelper('em')->getEntityManager();
$cmf = new DisconnectedClassMetadataFactory($em); $cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadatas = $cmf->getAllMetadata(); $metadatas = $cmf->getAllMetadata();
$metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); $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.') ->setDescription('Generates proxy classes for entity classes.')
->setDefinition(array( ->setDefinition(array(
new InputOption( 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.' 'A string pattern used to match entities that should be processed.'
), ),
new InputArgument( new InputArgument(

View File

@ -51,7 +51,7 @@ class GenerateRepositoriesCommand extends Console\Command\Command
->setDescription('Generate repository classes from your mapping information.') ->setDescription('Generate repository classes from your mapping information.')
->setDefinition(array( ->setDefinition(array(
new InputOption( 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.' 'A string pattern used to match entities that should be processed.'
), ),
new InputArgument( new InputArgument(

View File

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

View File

@ -53,7 +53,7 @@ class CreateCommand extends AbstractCommand
) )
->setDefinition(array( ->setDefinition(array(
new InputOption( 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.' '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) 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) { if ($input->getOption('dump-sql') === true) {
$sqls = $schemaTool->getCreateSchemaSql($metadatas); $sqls = $schemaTool->getCreateSchemaSql($metadatas);
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL); $output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);

View File

@ -49,13 +49,21 @@ class DropCommand extends AbstractCommand
$this $this
->setName('orm:schema-tool:drop') ->setName('orm:schema-tool:drop')
->setDescription( ->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( ->setDefinition(array(
new InputOption( 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.' '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 ->setHelp(<<<EOT
Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. 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) protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas)
{ {
$isFullDatabaseDrop = ($input->getOption('full-database'));
if ($input->getOption('dump-sql') === true) { if ($input->getOption('dump-sql') === true) {
$sqls = $schemaTool->getDropSchemaSql($metadatas); if ($isFullDatabaseDrop) {
$sqls = $schemaTool->getDropDatabaseSQL();
} else {
$sqls = $schemaTool->getDropSchemaSQL($metadatas);
}
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL); $output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
} else { } else if ($input->getOption('force') === true) {
$output->write('Dropping database schema...' . PHP_EOL); $output->write('Dropping database schema...' . PHP_EOL);
$schemaTool->dropSchema($metadatas); if ($isFullDatabaseDrop) {
$schemaTool->dropDatabase();
} else {
$schemaTool->dropSchema($metadatas);
}
$output->write('Database schema dropped successfully!' . PHP_EOL); $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( ->setDefinition(array(
new InputOption( 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.' 'If defined, all assets of the database which are not relevant to the current metadata will be dropped.'
), ),
new InputOption( 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.' '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 ->setHelp(<<<EOT
Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output. 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) protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas)
{ {
// Defining if update is complete or not (--complete not defined means $saveMode = true) // 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) { if ($input->getOption('dump-sql') === true) {
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode); $sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL); $output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
} else { } else if ($input->getOption('force') === true) {
$output->write('Updating database schema...' . PHP_EOL); $output->write('Updating database schema...' . PHP_EOL);
$schemaTool->updateSchema($metadatas, $saveMode); $schemaTool->updateSchema($metadatas, $saveMode);
$output->write('Database schema updated successfully!' . PHP_EOL); $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); $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
} else if (isset($column['sequence'])) { } else if (isset($column['sequence'])) {
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE); $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE);
$metadata->setSequenceGeneratorDefinition($definition);
$definition = array( $definition = array(
'sequenceName' => is_array($column['sequence']) ? $column['sequence']['name']:$column['sequence'] 'sequenceName' => is_array($column['sequence']) ? $column['sequence']['name']:$column['sequence']
); );
@ -198,6 +197,7 @@ class ConvertDoctrine1Schema
if (isset($column['sequence']['value'])) { if (isset($column['sequence']['value'])) {
$definition['initialValue'] = $column['sequence']['value']; $definition['initialValue'] = $column['sequence']['value'];
} }
$metadata->setSequenceGeneratorDefinition($definition);
} }
return $fieldMapping; return $fieldMapping;
} }

View File

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * 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 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision$
* @author Benjamin Eberlei <kontakt@beberlei.de> * @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com> * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com> * @author Jonathan Wage <jonwage@gmail.com>
@ -46,7 +43,13 @@ class DisconnectedClassMetadataFactory extends ClassMetadataFactory
*/ */
protected function newClassMetadataInstance($className) 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 <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -82,7 +80,7 @@ class EntityGenerator
private static $_classTemplate = private static $_classTemplate =
'<?php '<?php
<namespace><use> <namespace>
<entityAnnotation> <entityAnnotation>
<entityClassName> <entityClassName>
@ -189,7 +187,6 @@ public function <methodName>()
{ {
$placeHolders = array( $placeHolders = array(
'<namespace>', '<namespace>',
'<use>',
'<entityAnnotation>', '<entityAnnotation>',
'<entityClassName>', '<entityClassName>',
'<entityBody>' '<entityBody>'
@ -197,7 +194,6 @@ public function <methodName>()
$replacements = array( $replacements = array(
$this->_generateEntityNamespace($metadata), $this->_generateEntityNamespace($metadata),
$this->_generateEntityUse($metadata),
$this->_generateEntityDocBlock($metadata), $this->_generateEntityDocBlock($metadata),
$this->_generateEntityClassName($metadata), $this->_generateEntityClassName($metadata),
$this->_generateEntityBody($metadata) $this->_generateEntityBody($metadata)
@ -222,7 +218,7 @@ public function <methodName>()
$body = str_replace('<spaces>', $this->_spaces, $body); $body = str_replace('<spaces>', $this->_spaces, $body);
$last = strrpos($currentCode, '}'); $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) private function _generateEntityClassName(ClassMetadataInfo $metadata)
{ {
return 'class ' . $this->_getClassName($metadata) . return 'class ' . $this->_getClassName($metadata) .
@ -379,14 +368,7 @@ public function <methodName>()
{ {
$refl = new \ReflectionClass($this->_getClassToExtend()); $refl = new \ReflectionClass($this->_getClassToExtend());
return $refl->getName(); return '\\' . $refl->getName();
}
private function _getClassToExtendNamespace()
{
$refl = new \ReflectionClass($this->_getClassToExtend());
return $refl->getNamespaceName() ? $refl->getNamespaceName():$refl->getShortName();
} }
private function _getClassName(ClassMetadataInfo $metadata) private function _getClassName(ClassMetadataInfo $metadata)

View File

@ -46,11 +46,14 @@ class XmlExporter extends AbstractExporter
*/ */
public function exportClassMetadata(ClassMetadataInfo $metadata) 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('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) { if ($metadata->isMappedSuperclass) {
$root = $xml->addChild('mapped-superclass'); $root = $xml->addChild('mapped-superclass');
@ -109,7 +112,7 @@ class XmlExporter extends AbstractExporter
foreach ($metadata->table['uniqueConstraints'] as $unique) { foreach ($metadata->table['uniqueConstraints'] as $unique) {
$uniqueConstraintXml = $uniqueConstraintsXml->addChild('unique-constraint'); $uniqueConstraintXml = $uniqueConstraintsXml->addChild('unique-constraint');
$uniqueConstraintXml->addAttribute('name', $name); $uniqueConstraintXml->addAttribute('name', $unique['name']);
$uniqueConstraintXml->addAttribute('columns', implode(',', $unique['columns'])); $uniqueConstraintXml->addAttribute('columns', implode(',', $unique['columns']));
} }
} }
@ -128,6 +131,21 @@ class XmlExporter extends AbstractExporter
$id[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType; $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) { if ($fields) {
foreach ($fields as $field) { foreach ($fields as $field) {
$fieldXml = $root->addChild('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) { foreach ($metadata->associationMappings as $name => $associationMapping) {
if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_ONE) { if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_ONE) {
$associationMappingXml = $root->addChild('one-to-one'); $associationMappingXml = $root->addChild('one-to-one');
@ -305,47 +308,16 @@ class XmlExporter extends AbstractExporter
} }
/** /**
* Code originally taken from * @param \SimpleXMLElement $simpleXml
* http://recurser.com/articles/2007/04/05/format-xml-with-php/
*
* @param string $simpleXml
* @return string $xml * @return string $xml
*/ */
private function _asXml($simpleXml) 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) $result = $dom->saveXML();
$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
}
return $result; return $result;
} }
} }

View File

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

View File

@ -1,7 +1,5 @@
<?php <?php
/* /*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@ -94,6 +92,22 @@ class SchemaTool
return $schema->toSql($this->_platform); 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. * From a given set of metadata classes this method creates a Schema instance.
* *
@ -112,19 +126,12 @@ class SchemaTool
$evm = $this->_em->getEventManager(); $evm = $this->_em->getEventManager();
foreach ($classes as $class) { foreach ($classes as $class) {
if (isset($processedClasses[$class->name]) || $class->isMappedSuperclass) { if ($this->processingNotRequired($class, $processedClasses)) {
continue; continue;
} }
$table = $schema->createTable($class->getQuotedTableName($this->_platform)); $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 $columns = array(); // table columns
if ($class->isInheritanceTypeSingleTable()) { if ($class->isInheritanceTypeSingleTable()) {
@ -171,16 +178,14 @@ class SchemaTool
$idMapping = $class->fieldMappings[$class->identifier[0]]; $idMapping = $class->fieldMappings[$class->identifier[0]];
$this->_gatherColumn($class, $idMapping, $table); $this->_gatherColumn($class, $idMapping, $table);
$columnName = $class->getQuotedColumnName($class->identifier[0], $this->_platform); $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; $pkColumns[] = $columnName;
// TODO: REMOVE
/*if ($table->isIdGeneratorIdentity()) {
$table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_NONE);
}*/
// Add a FK constraint on the ID column // Add a FK constraint on the ID column
$table->addUnnamedForeignKeyConstraint( $table->addUnnamedForeignKeyConstraint(
$this->_em->getClassMetadata($class->rootEntityName)->getQuotedTableName($this->_platform), $this->_em->getClassMetadata($class->rootEntityName)->getTableName(),
array($columnName), array($columnName), array('onDelete' => 'CASCADE') array($columnName), array($columnName), array('onDelete' => 'CASCADE')
); );
} }
@ -318,6 +323,9 @@ class SchemaTool
$options = array(); $options = array();
$options['length'] = isset($mapping['length']) ? $mapping['length'] : null; $options['length'] = isset($mapping['length']) ? $mapping['length'] : null;
$options['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : true; $options['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : true;
if ($class->isInheritanceTypeSingleTable() && count($class->parentClasses) > 0) {
$options['notnull'] = false;
}
$options['platformOptions'] = array(); $options['platformOptions'] = array();
$options['platformOptions']['version'] = $class->isVersioned && $class->versionField == $mapping['fieldName'] ? true : false; $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'])) { if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == array($mapping['fieldName'])) {
$options['autoincrement'] = true; $options['autoincrement'] = true;
} }
if ($class->isInheritanceTypeJoined() && $class->name != $class->rootEntityName) {
$options['autoincrement'] = false;
}
if ($table->hasColumn($columnName)) { if ($table->hasColumn($columnName)) {
// required in some inheritance scenarios // required in some inheritance scenarios
@ -459,6 +470,12 @@ class SchemaTool
if (isset($joinColumn['nullable'])) { if (isset($joinColumn['nullable'])) {
$columnOptions['notnull'] = !$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( $theJoinTable->addColumn(
$columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions $columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions
@ -479,7 +496,7 @@ class SchemaTool
} }
$theJoinTable->addUnnamedForeignKeyConstraint( $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) 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(); $conn = $this->_em->getConnection();
foreach ($dropSchemaSql as $sql) { 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 * @return array
*/ */
public function getDropSchemaSql(array $classes) public function getDropDatabaseSQL()
{ {
$sm = $this->_em->getConnection()->getSchemaManager(); $sm = $this->_em->getConnection()->getSchemaManager();
$schema = $sm->createSchema(); $schema = $sm->createSchema();
@ -520,39 +555,31 @@ class SchemaTool
} }
/** /**
* Drop all tables of the database connection.
* *
* @param array $classes
* @return array * @return array
*/ */
private function _getDropSchemaTablesDatabaseMode($classes) public function getDropSchemaSQL(array $classes)
{ {
$conn = $this->_em->getConnection(); $sm = $this->_em->getConnection()->getSchemaManager();
$sm = $conn->getSchemaManager(); $sql = array();
/* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */ $orderedTables = array();
$allTables = $sm->listTables(); foreach ($classes AS $class) {
if ($class->isIdGeneratorSequence() && !$class->isMappedSuperclass && $class->name == $class->rootEntityName && $this->_platform->supportsSequences()) {
$orderedTables = $this->_getDropSchemaTablesMetadataMode($classes); $sql[] = $this->_platform->getDropSequenceSQL($class->sequenceGeneratorDefinition['sequenceName']);
foreach($allTables AS $tableName) {
if(!in_array($tableName, $orderedTables)) {
$orderedTables[] = $tableName;
} }
} }
return $orderedTables;
}
private function _getDropSchemaTablesMetadataMode(array $classes)
{
$orderedTables = array();
$commitOrder = $this->_getCommitOrder($classes); $commitOrder = $this->_getCommitOrder($classes);
$associationTables = $this->_getAssociationTables($commitOrder); $associationTables = $this->_getAssociationTables($commitOrder);
// Drop association tables first // Drop association tables first
foreach ($associationTables as $associationTable) { foreach ($associationTables as $associationTable) {
$orderedTables[] = $associationTable; if (!in_array($associationTable, $orderedTables)) {
$orderedTables[] = $associationTable;
}
} }
// Drop tables in reverse commit order // Drop tables in reverse commit order
@ -564,17 +591,27 @@ class SchemaTool
continue; continue;
} }
$orderedTables[] = $class->getTableName(); 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 * 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 * @param array $classes
* @return void * @return void
@ -645,7 +682,7 @@ class SchemaTool
foreach ($classes as $class) { foreach ($classes as $class) {
foreach ($class->associationMappings as $assoc) { foreach ($class->associationMappings as $assoc) {
if ($assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY) { 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); $this->computeChangeSet($targetClass, $entry);
} else if ($state == self::STATE_REMOVED) { } else if ($state == self::STATE_REMOVED) {
return new InvalidArgumentException("Removed entity detected during flush: " 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) { } else if ($state == self::STATE_DETACHED) {
// Can actually not happen right now as we assume STATE_NEW, // Can actually not happen right now as we assume STATE_NEW,
// so the exception will be raised from the DBAL layer (constraint violation). // so the exception will be raised from the DBAL layer (constraint violation).
@ -1437,7 +1437,16 @@ class UnitOfWork implements PropertyChangedListener
$prop->setValue($managedCopy, $managedCol); $prop->setValue($managedCopy, $managedCol);
$this->originalEntityData[$oid][$name] = $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()) { if ($class->isChangeTrackingNotify()) {
@ -1456,7 +1465,7 @@ class UnitOfWork implements PropertyChangedListener
if ($assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['type'] & ClassMetadata::TO_ONE) {
$prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy); $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy);
} else { } else {
$prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->unwrap()->add($managedCopy); $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy);
if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
$class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy); $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy);
} }
@ -1500,7 +1509,9 @@ class UnitOfWork implements PropertyChangedListener
switch ($this->getEntityState($entity, self::STATE_DETACHED)) { switch ($this->getEntityState($entity, self::STATE_DETACHED)) {
case self::STATE_MANAGED: case self::STATE_MANAGED:
$this->removeFromIdentityMap($entity); if ($this->isInIdentityMap($entity)) {
$this->removeFromIdentityMap($entity);
}
unset($this->entityInsertions[$oid], $this->entityUpdates[$oid], unset($this->entityInsertions[$oid], $this->entityUpdates[$oid],
$this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->entityDeletions[$oid], $this->entityIdentifiers[$oid],
$this->entityStates[$oid], $this->originalEntityData[$oid]); $this->entityStates[$oid], $this->originalEntityData[$oid]);
@ -1889,7 +1900,7 @@ class UnitOfWork implements PropertyChangedListener
if ($assoc['isOwningSide']) { if ($assoc['isOwningSide']) {
$associatedId = array(); $associatedId = array();
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) { foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
$joinColumnValue = $data[$srcColumn]; $joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
if ($joinColumnValue !== null) { if ($joinColumnValue !== null) {
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
} }
@ -1937,14 +1948,12 @@ class UnitOfWork implements PropertyChangedListener
} }
} else { } else {
// Inject collection // Inject collection
$reflField = $class->reflFields[$field]; $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
$pColl = new PersistentCollection(
$this->em, $targetClass,
//TODO: getValue might be superfluous once DDC-79 is implemented.
$reflField->getValue($entity) ?: new ArrayCollection
);
$pColl->setOwner($entity, $assoc); $pColl->setOwner($entity, $assoc);
$reflField = $class->reflFields[$field];
$reflField->setValue($entity, $pColl); $reflField->setValue($entity, $pColl);
if ($assoc['fetch'] == ClassMetadata::FETCH_LAZY) { if ($assoc['fetch'] == ClassMetadata::FETCH_LAZY) {
$pColl->setInitialized(false); $pColl->setInitialized(false);
} else { } else {

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -21,510 +21,492 @@ use Symfony\Component\Console\Application;
/** /**
* Base class for all commands. * Base class for all commands.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class Command class Command
{ {
protected $name; protected $name;
protected $namespace; protected $namespace;
protected $aliases; protected $aliases;
protected $definition; protected $definition;
protected $help; protected $help;
protected $application; protected $application;
protected $description; protected $description;
protected $ignoreValidationErrors; protected $ignoreValidationErrors;
protected $formatter; protected $applicationDefinitionMerged;
protected $applicationDefinitionMerged; protected $code;
protected $code;
/** /**
* Constructor. * Constructor.
* *
* @param string $name The name of the command * @param string $name The name of the command
*/ *
public function __construct($name = null) * @throws \LogicException When the command name is empty
{ */
$this->definition = new InputDefinition(); public function __construct($name = null)
$this->ignoreValidationErrors = false;
$this->applicationDefinitionMerged = false;
$this->aliases = array();
if (null !== $name)
{ {
$this->setName($name); $this->definition = new InputDefinition();
$this->ignoreValidationErrors = false;
$this->applicationDefinitionMerged = false;
$this->aliases = array();
if (null !== $name) {
$this->setName($name);
}
$this->configure();
if (!$this->name) {
throw new \LogicException('The command name cannot be empty.');
}
} }
$this->configure(); /**
* Sets the application instance for this command.
if (!$this->name) *
* @param Application $application An Application instance
*/
public function setApplication(Application $application = null)
{ {
throw new \LogicException('The command name cannot be empty.'); $this->application = $application;
}
}
/**
* Sets the application instance for this command.
*
* @param Application $application An Application instance
*/
public function setApplication(Application $application = null)
{
$this->application = $application;
}
/**
* Configures the current command.
*/
protected function configure()
{
}
/**
* Executes the current command.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*
* @return integer 0 if everything went fine, or an error code
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
throw new \LogicException('You must override the execute() method in the concrete command class.');
}
/**
* Interacts with the user.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
}
/**
* Initializes the command just after the input has been validated.
*
* This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
}
/**
* Runs the command.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
public function run(InputInterface $input, OutputInterface $output)
{
// add the application arguments and options
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
try
{
$input->bind($this->definition);
}
catch (\Exception $e)
{
if (!$this->ignoreValidationErrors)
{
throw $e;
}
} }
$this->initialize($input, $output); /**
* Configures the current command.
if ($input->isInteractive()) */
protected function configure()
{ {
$this->interact($input, $output);
} }
$input->validate(); /**
* Executes the current command.
if ($this->code) *
* @param InputInterface $input An InputInterface instance
* @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)
{ {
return call_user_func($this->code, $input, $output); throw new \LogicException('You must override the execute() method in the concrete command class.');
}
else
{
return $this->execute($input, $output);
}
}
/**
* Sets the code to execute when running this command.
*
* @param \Closure $code A \Closure
*
* @return Command The current instance
*/
public function setCode(\Closure $code)
{
$this->code = $code;
return $this;
}
/**
* Merges the application definition with the command definition.
*/
protected function mergeApplicationDefinition()
{
if (null === $this->application || true === $this->applicationDefinitionMerged)
{
return;
} }
$this->definition->setArguments(array_merge( /**
$this->application->getDefinition()->getArguments(), * Interacts with the user.
$this->definition->getArguments() *
)); * @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
$this->definition->addOptions($this->application->getDefinition()->getOptions()); */
protected function interact(InputInterface $input, OutputInterface $output)
$this->applicationDefinitionMerged = true;
}
/**
* Sets an array of argument and option instances.
*
* @param array|Definition $definition An array of argument and option instances or a definition instance
*
* @return Command The current instance
*/
public function setDefinition($definition)
{
if ($definition instanceof InputDefinition)
{ {
$this->definition = $definition;
}
else
{
$this->definition->setDefinition($definition);
} }
$this->applicationDefinitionMerged = false; /**
* Initializes the command just after the input has been validated.
return $this; *
} * This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
/** *
* Gets the InputDefinition attached to this Command. * @param InputInterface $input An InputInterface instance
* * @param OutputInterface $output An OutputInterface instance
* @return InputDefinition $definition An InputDefinition instance */
*/ protected function initialize(InputInterface $input, OutputInterface $output)
public function getDefinition()
{
return $this->definition;
}
/**
* Adds an argument.
*
* @param string $name The argument name
* @param integer $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
*
* @return Command The current instance
*/
public function addArgument($name, $mode = null, $description = '', $default = null)
{
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
return $this;
}
/**
* Adds an option.
*
* @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 string $description A description text
* @param mixed $default The default value (must be null for self::PARAMETER_REQUIRED or self::PARAMETER_NONE)
*
* @return Command The current instance
*/
public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
return $this;
}
/**
* Sets the name of the command.
*
* This method can set both the namespace and the name if
* you separate them by a colon (:)
*
* $command->setName('foo:bar');
*
* @param string $name The command name
*
* @return Command The current instance
*/
public function setName($name)
{
if (false !== $pos = strrpos($name, ':'))
{ {
$namespace = substr($name, 0, $pos);
$name = substr($name, $pos + 1);
}
else
{
$namespace = $this->namespace;
} }
if (!$name) /**
* Runs the command.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
public function run(InputInterface $input, OutputInterface $output)
{ {
throw new \InvalidArgumentException('A command name cannot be empty'); // add the application arguments and options
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
try {
$input->bind($this->definition);
} catch (\Exception $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
}
$this->initialize($input, $output);
if ($input->isInteractive()) {
$this->interact($input, $output);
}
$input->validate();
if ($this->code) {
return call_user_func($this->code, $input, $output);
} else {
return $this->execute($input, $output);
}
} }
$this->namespace = $namespace; /**
$this->name = $name; * Sets the code to execute when running this command.
*
return $this; * @param \Closure $code A \Closure
} *
* @return Command The current instance
/** */
* Returns the command namespace. public function setCode(\Closure $code)
*
* @return string The command namespace
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Returns the command name
*
* @return string The command name
*/
public function getName()
{
return $this->name;
}
/**
* Returns the fully qualified command name.
*
* @return string The fully qualified command name
*/
public function getFullName()
{
return $this->getNamespace() ? $this->getNamespace().':'.$this->getName() : $this->getName();
}
/**
* Sets the description for the command.
*
* @param string $description The description for the command
*
* @return Command The current instance
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Returns the description for the command.
*
* @return string The description for the command
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the help for the command.
*
* @param string $help The help for the command
*
* @return Command The current instance
*/
public function setHelp($help)
{
$this->help = $help;
return $this;
}
/**
* Returns the help for the command.
*
* @return string The help for the command
*/
public function getHelp()
{
return $this->help;
}
/**
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*
* @return string The processed help for the command
*/
public function getProcessedHelp()
{
$name = $this->namespace.':'.$this->name;
$placeholders = array(
'%command.name%',
'%command.full_name%'
);
$replacements = array(
$name,
$_SERVER['PHP_SELF'].' '.$name
);
return str_replace($placeholders, $replacements, $this->getHelp());
}
/**
* Sets the aliases for the command.
*
* @param array $aliases An array of aliases for the command
*
* @return Command The current instance
*/
public function setAliases($aliases)
{
$this->aliases = $aliases;
return $this;
}
/**
* Returns the aliases for the command.
*
* @return array An array of aliases for the command
*/
public function getAliases()
{
return $this->aliases;
}
/**
* Returns the synopsis for the command.
*
* @return string The synopsis
*/
public function getSynopsis()
{
return sprintf('%s %s', $this->getFullName(), $this->definition->getSynopsis());
}
/**
* Gets a helper instance by name.
*
* @param string $name The helper name
*
* @return mixed The helper value
*
* @throws \InvalidArgumentException if the helper is not defined
*/
protected function getHelper($name)
{
return $this->application->getHelperSet()->get($name);
}
/**
* Gets a helper instance by name.
*
* @param string $name The helper name
*
* @return mixed The helper value
*
* @throws \InvalidArgumentException if the helper is not defined
*/
public function __get($name)
{
return $this->application->getHelperSet()->get($name);
}
/**
* Returns a text representation of the command.
*
* @return string A string representing the command
*/
public function asText()
{
$messages = array(
'<comment>Usage:</comment>',
' '.$this->getSynopsis(),
'',
);
if ($this->getAliases())
{ {
$messages[] = '<comment>Aliases:</comment> <info>'.implode(', ', $this->getAliases()).'</info>'; $this->code = $code;
return $this;
} }
$messages[] = $this->definition->asText(); /**
* Merges the application definition with the command definition.
if ($help = $this->getProcessedHelp()) */
protected function mergeApplicationDefinition()
{ {
$messages[] = '<comment>Help:</comment>'; if (null === $this->application || true === $this->applicationDefinitionMerged) {
$messages[] = ' '.implode("\n ", explode("\n", $help))."\n"; return;
}
$this->definition->setArguments(array_merge(
$this->application->getDefinition()->getArguments(),
$this->definition->getArguments()
));
$this->definition->addOptions($this->application->getDefinition()->getOptions());
$this->applicationDefinitionMerged = true;
} }
return implode("\n", $messages); /**
} * Sets an array of argument and option instances.
*
/** * @param array|Definition $definition An array of argument and option instances or a definition instance
* Returns an XML representation of the command. *
* * @return Command The current instance
* @param Boolean $asDom Whether to return a DOM or an XML string */
* public function setDefinition($definition)
* @return string|DOMDocument An XML string representing the command
*/
public function asXml($asDom = false)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$dom->appendChild($commandXML = $dom->createElement('command'));
$commandXML->setAttribute('id', $this->getFullName());
$commandXML->setAttribute('namespace', $this->getNamespace() ? $this->getNamespace() : '_global');
$commandXML->setAttribute('name', $this->getName());
$commandXML->appendChild($usageXML = $dom->createElement('usage'));
$usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), '')));
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $this->getDescription()))));
$commandXML->appendChild($helpXML = $dom->createElement('help'));
$help = $this->help;
$helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help))));
$commandXML->appendChild($aliasesXML = $dom->createElement('aliases'));
foreach ($this->getAliases() as $alias)
{ {
$aliasesXML->appendChild($aliasXML = $dom->createElement('alias')); if ($definition instanceof InputDefinition) {
$aliasXML->appendChild($dom->createTextNode($alias)); $this->definition = $definition;
} else {
$this->definition->setDefinition($definition);
}
$this->applicationDefinitionMerged = false;
return $this;
} }
$definition = $this->definition->asXml(true); /**
$commandXML->appendChild($dom->importNode($definition->getElementsByTagName('arguments')->item(0), true)); * Gets the InputDefinition attached to this Command.
$commandXML->appendChild($dom->importNode($definition->getElementsByTagName('options')->item(0), true)); *
* @return InputDefinition An InputDefinition instance
*/
public function getDefinition()
{
return $this->definition;
}
return $asDom ? $dom : $dom->saveXml(); /**
} * Adds an argument.
*
* @param string $name The argument name
* @param integer $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string $description A description text
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
*
* @return Command The current instance
*/
public function addArgument($name, $mode = null, $description = '', $default = null)
{
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
return $this;
}
/**
* Adds an option.
*
* @param string $name The option name
* @param string $shortcut The shortcut (can be null)
* @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 InputOption::VALUE_REQUIRED or self::VALUE_NONE)
*
* @return Command The current instance
*/
public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
return $this;
}
/**
* Sets the name of the command.
*
* This method can set both the namespace and the name if
* you separate them by a colon (:)
*
* $command->setName('foo:bar');
*
* @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, ':')) {
$namespace = substr($name, 0, $pos);
$name = substr($name, $pos + 1);
} else {
$namespace = $this->namespace;
}
if (!$name) {
throw new \InvalidArgumentException('A command name cannot be empty.');
}
$this->namespace = $namespace;
$this->name = $name;
return $this;
}
/**
* Returns the command namespace.
*
* @return string The command namespace
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Returns the command name
*
* @return string The command name
*/
public function getName()
{
return $this->name;
}
/**
* Returns the fully qualified command name.
*
* @return string The fully qualified command name
*/
public function getFullName()
{
return $this->getNamespace() ? $this->getNamespace().':'.$this->getName() : $this->getName();
}
/**
* Sets the description for the command.
*
* @param string $description The description for the command
*
* @return Command The current instance
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Returns the description for the command.
*
* @return string The description for the command
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the help for the command.
*
* @param string $help The help for the command
*
* @return Command The current instance
*/
public function setHelp($help)
{
$this->help = $help;
return $this;
}
/**
* Returns the help for the command.
*
* @return string The help for the command
*/
public function getHelp()
{
return $this->help;
}
/**
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*
* @return string The processed help for the command
*/
public function getProcessedHelp()
{
$name = $this->namespace.':'.$this->name;
$placeholders = array(
'%command.name%',
'%command.full_name%'
);
$replacements = array(
$name,
$_SERVER['PHP_SELF'].' '.$name
);
return str_replace($placeholders, $replacements, $this->getHelp());
}
/**
* Sets the aliases for the command.
*
* @param array $aliases An array of aliases for the command
*
* @return Command The current instance
*/
public function setAliases($aliases)
{
$this->aliases = $aliases;
return $this;
}
/**
* Returns the aliases for the command.
*
* @return array An array of aliases for the command
*/
public function getAliases()
{
return $this->aliases;
}
/**
* Returns the synopsis for the command.
*
* @return string The synopsis
*/
public function getSynopsis()
{
return sprintf('%s %s', $this->getFullName(), $this->definition->getSynopsis());
}
/**
* Gets a helper instance by name.
*
* @param string $name The helper name
*
* @return mixed The helper value
*
* @throws \InvalidArgumentException if the helper is not defined
*/
protected function getHelper($name)
{
return $this->application->getHelperSet()->get($name);
}
/**
* Gets a helper instance by name.
*
* @param string $name The helper name
*
* @return mixed The helper value
*
* @throws \InvalidArgumentException if the helper is not defined
*/
public function __get($name)
{
return $this->application->getHelperSet()->get($name);
}
/**
* Returns a text representation of the command.
*
* @return string A string representing the command
*/
public function asText()
{
$messages = array(
'<comment>Usage:</comment>',
' '.$this->getSynopsis(),
'',
);
if ($this->getAliases()) {
$messages[] = '<comment>Aliases:</comment> <info>'.implode(', ', $this->getAliases()).'</info>';
}
$messages[] = $this->definition->asText();
if ($help = $this->getProcessedHelp()) {
$messages[] = '<comment>Help:</comment>';
$messages[] = ' '.implode("\n ", explode("\n", $help))."\n";
}
return implode("\n", $messages);
}
/**
* Returns an XML representation of the command.
*
* @param Boolean $asDom Whether to return a DOM or an XML string
*
* @return string|DOMDocument An XML string representing the command
*/
public function asXml($asDom = false)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$dom->appendChild($commandXML = $dom->createElement('command'));
$commandXML->setAttribute('id', $this->getFullName());
$commandXML->setAttribute('namespace', $this->getNamespace() ? $this->getNamespace() : '_global');
$commandXML->setAttribute('name', $this->getName());
$commandXML->appendChild($usageXML = $dom->createElement('usage'));
$usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), '')));
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $this->getDescription()))));
$commandXML->appendChild($helpXML = $dom->createElement('help'));
$help = $this->help;
$helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help))));
$commandXML->appendChild($aliasesXML = $dom->createElement('aliases'));
foreach ($this->getAliases() as $alias) {
$aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
$aliasXML->appendChild($dom->createTextNode($alias));
}
$definition = $this->definition->asXml(true);
$commandXML->appendChild($dom->importNode($definition->getElementsByTagName('arguments')->item(0), true));
$commandXML->appendChild($dom->importNode($definition->getElementsByTagName('options')->item(0), true));
return $asDom ? $dom : $dom->saveXml();
}
} }

View File

@ -10,7 +10,7 @@ use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Command\Command; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -21,63 +21,57 @@ use Symfony\Component\Console\Command\Command;
/** /**
* HelpCommand displays the help for a given command. * HelpCommand displays the help for a given command.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class HelpCommand extends Command class HelpCommand extends Command
{ {
protected $command; protected $command;
/** /**
* @see Command * @see Command
*/ */
protected function configure() protected function configure()
{ {
$this->ignoreValidationErrors = true; $this->ignoreValidationErrors = true;
$this $this
->setDefinition(array( ->setDefinition(array(
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), 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') ->setName('help')
->setAliases(array('?')) ->setAliases(array('?'))
->setDescription('Displays help for a command') ->setDescription('Displays help for a command')
->setHelp(<<<EOF ->setHelp(<<<EOF
The <info>help</info> command displays help for a given command: 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: 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 EOF
); );
}
public function setCommand(Command $command)
{
$this->command = $command;
}
/**
* @see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (null === $this->command)
{
$this->command = $this->application->getCommand($input->getArgument('command_name'));
} }
if ($input->getOption('xml')) public function setCommand(Command $command)
{ {
$output->writeln($this->command->asXml(), Output::OUTPUT_RAW); $this->command = $command;
} }
else
/**
* @see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{ {
$output->writeln($this->command->asText()); if (null === $this->command) {
$this->command = $this->application->get($input->getArgument('command_name'));
}
if ($input->getOption('xml')) {
$output->writeln($this->command->asXml(), Output::OUTPUT_RAW);
} else {
$output->writeln($this->command->asText());
}
} }
}
} }

View File

@ -10,7 +10,7 @@ use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Command\Command; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -21,25 +21,23 @@ use Symfony\Component\Console\Command\Command;
/** /**
* ListCommand displays the list of all available commands for the application. * ListCommand displays the list of all available commands for the application.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class ListCommand extends Command class ListCommand extends Command
{ {
/** /**
* @see Command * @see Command
*/ */
protected function configure() protected function configure()
{ {
$this $this
->setDefinition(array( ->setDefinition(array(
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), 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') ->setName('list')
->setDescription('Lists commands') ->setDescription('Lists commands')
->setHelp(<<<EOF ->setHelp(<<<EOF
The <info>list</info> command lists all commands: The <info>list</info> command lists all commands:
<info>./symfony list</info> <info>./symfony list</info>
@ -52,21 +50,18 @@ You can also output the information as XML by using the <comment>--xml</comment>
<info>./symfony list --xml</info> <info>./symfony list --xml</info>
EOF EOF
); );
} }
/** /**
* @see Command * @see Command
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('xml'))
{ {
$output->writeln($this->application->asXml($input->getArgument('namespace')), Output::OUTPUT_RAW); if ($input->getOption('xml')) {
$output->writeln($this->application->asXml($input->getArgument('namespace')), Output::OUTPUT_RAW);
} else {
$output->writeln($this->application->asText($input->getArgument('namespace')));
}
} }
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; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -16,104 +16,95 @@ use Symfony\Component\Console\Output\OutputInterface;
/** /**
* The Dialog class provides helpers to interact with the user. * The Dialog class provides helpers to interact with the user.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class DialogHelper extends Helper class DialogHelper extends Helper
{ {
/** /**
* Asks a question to the user. * Asks a question to the user.
* *
* @param OutputInterface $output * @param OutputInterface $output
* @param string|array $question The question to ask * @param string|array $question The question to ask
* @param string $default The default answer if none is given by the user * @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) public function ask(OutputInterface $output, $question, $default = null)
{
// @codeCoverageIgnoreStart
$output->writeln($question);
$ret = trim(fgets(STDIN));
return $ret ? $ret : $default;
// @codeCoverageIgnoreEnd
}
/**
* Asks a confirmation to the user.
*
* The question will be asked until the user answer by nothing, yes, or no.
*
* @param OutputInterface $output
* @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
*/
public function askConfirmation(OutputInterface $output, $question, $default = true)
{
// @codeCoverageIgnoreStart
$answer = 'z';
while ($answer && !in_array(strtolower($answer[0]), array('y', 'n')))
{ {
$answer = $this->ask($output, $question); // @codeCoverageIgnoreStart
$output->writeln($question);
$ret = trim(fgets(STDIN));
return $ret ? $ret : $default;
// @codeCoverageIgnoreEnd
} }
if (false === $default) /**
* Asks a confirmation to the user.
*
* The question will be asked until the user answer by nothing, yes, or no.
*
* @param OutputInterface $output
* @param string|array $question The question to ask
* @param Boolean $default The default answer if the user enters nothing
*
* @return Boolean true if the user has confirmed, false otherwise
*/
public function askConfirmation(OutputInterface $output, $question, $default = true)
{ {
return $answer && 'y' == strtolower($answer[0]); // @codeCoverageIgnoreStart
} $answer = 'z';
else while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) {
{ $answer = $this->ask($output, $question);
return !$answer || 'y' == strtolower($answer[0]); }
}
// @codeCoverageIgnoreEnd
}
/** if (false === $default) {
* Asks for a value and validates the response. return $answer && 'y' == strtolower($answer[0]);
* } else {
* @param OutputInterface $output return !$answer || 'y' == strtolower($answer[0]);
* @param string|array $question }
* @param Closure $validator // @codeCoverageIgnoreEnd
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
*
* @return mixed
*/
public function askAndValidate(OutputInterface $output, $question, \Closure $validator, $attempts = false)
{
// @codeCoverageIgnoreStart
$error = null;
while (false === $attempts || $attempts--)
{
if (null !== $error)
{
$output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
}
$value = $this->ask($output, $question, null);
try
{
return $validator($value);
}
catch (\Exception $error)
{
}
} }
throw $error; /**
// @codeCoverageIgnoreEnd * Asks for a value and validates the response.
} *
* @param OutputInterface $output
* @param string|array $question
* @param Closure $validator
* @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) {
$output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
}
/** $value = $this->ask($output, $question, null);
* Returns the helper's canonical name
*/ try {
public function getName() return $validator($value);
{ } catch (\Exception $error) {
return 'dialog'; }
} }
throw $error;
// @codeCoverageIgnoreEnd
}
/**
* Returns the helper's canonical name
*/
public function getName()
{
return 'dialog';
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Helper; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -14,76 +14,69 @@ namespace Symfony\Component\Console\Helper;
/** /**
* The Formatter class provides helpers to format messages. * The Formatter class provides helpers to format messages.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class FormatterHelper extends Helper class FormatterHelper extends Helper
{ {
/** /**
* Formats a message within a section. * Formats a message within a section.
* *
* @param string $section The section name * @param string $section The section name
* @param string $message The message * @param string $message The message
* @param string $style The style to apply to the section * @param string $style The style to apply to the section
*/ */
public function formatSection($section, $message, $style = 'info') public function formatSection($section, $message, $style = 'info')
{
return sprintf("<%s>[%s]</%s> %s", $style, $section, $style, $message);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
* @param string $style The style to apply to the whole block
* @param Boolean $large Whether to return a large block
*
* @return string The formatter message
*/
public function formatBlock($messages, $style, $large = false)
{
if (!is_array($messages))
{ {
$messages = array($messages); return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
} }
$len = 0; /**
$lines = array(); * Formats a message as a block of text.
foreach ($messages as $message) *
* @param string|array $messages The message to write in the block
* @param string $style The style to apply to the whole block
* @param Boolean $large Whether to return a large block
*
* @return string The formatter message
*/
public function formatBlock($messages, $style, $large = false)
{ {
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message); if (!is_array($messages)) {
$len = max($this->strlen($message) + ($large ? 4 : 2), $len); $messages = array($messages);
}
$len = 0;
$lines = array();
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) {
$messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
}
if ($large) {
$messages[] = str_repeat(' ', $len);
}
foreach ($messages as &$message) {
$message = sprintf('<%s>%s</%s>', $style, $message, $style);
}
return implode("\n", $messages);
} }
$messages = $large ? array(str_repeat(' ', $len)) : array(); protected function strlen($string)
foreach ($lines as $line)
{ {
$messages[] = $line.str_repeat(' ', $len - $this->strlen($line)); return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
}
if ($large)
{
$messages[] = str_repeat(' ', $len);
} }
foreach ($messages as &$message) /**
* Returns the helper's canonical name
*/
public function getName()
{ {
$message = sprintf('<%s>%s</%s>', $style, $message, $style); return 'formatter';
} }
return implode("\n", $messages);
}
protected function strlen($string)
{
return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
}
/**
* Returns the helper's canonical name
*/
public function getName()
{
return 'formatter';
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Helper; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -14,32 +14,29 @@ namespace Symfony\Component\Console\Helper;
/** /**
* Helper is the base class for all helper classes. * Helper is the base class for all helper classes.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
abstract class Helper implements HelperInterface abstract class Helper implements HelperInterface
{ {
protected protected $helperSet = null;
$helperSet = null;
/** /**
* Sets the helper set associated with this helper. * Sets the helper set associated with this helper.
* *
* @param HelperSet $helperSet A HelperSet instance * @param HelperSet $helperSet A HelperSet instance
*/ */
public function setHelperSet(HelperSet $helperSet = null) public function setHelperSet(HelperSet $helperSet = null)
{ {
$this->helperSet = $helperSet; $this->helperSet = $helperSet;
} }
/** /**
* Gets the helper set associated with this helper. * Gets the helper set associated with this helper.
* *
* @return HelperSet A HelperSet instance * @return HelperSet A HelperSet instance
*/ */
public function getHelperSet() public function getHelperSet()
{ {
return $this->helperSet; return $this->helperSet;
} }
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Helper; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -14,30 +14,28 @@ namespace Symfony\Component\Console\Helper;
/** /**
* HelperInterface is the interface all helpers must implement. * HelperInterface is the interface all helpers must implement.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
interface HelperInterface interface HelperInterface
{ {
/** /**
* Sets the helper set associated with this helper. * Sets the helper set associated with this helper.
* *
* @param HelperSet $helperSet A HelperSet instance * @param HelperSet $helperSet A HelperSet instance
*/ */
function setHelperSet(HelperSet $helperSet = null); function setHelperSet(HelperSet $helperSet = null);
/** /**
* Gets the helper set associated with this helper. * Gets the helper set associated with this helper.
* *
* @return HelperSet A HelperSet instance * @return HelperSet A HelperSet instance
*/ */
function getHelperSet(); function getHelperSet();
/** /**
* Returns the canonical name of this helper. * Returns the canonical name of this helper.
* *
* @return string The canonical name * @return string The canonical name
*/ */
function getName(); function getName();
} }

View File

@ -5,7 +5,7 @@ namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Command\Command; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -16,89 +16,87 @@ use Symfony\Component\Console\Command\Command;
/** /**
* HelperSet represents a set of helpers to be used with a command. * HelperSet represents a set of helpers to be used with a command.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class HelperSet class HelperSet
{ {
protected protected $helpers;
$helpers = array(), protected $command;
$command = null;
public function __construct(array $helpers = array()) /**
{ * @param Helper[] $helpers An array of helper.
foreach ($helpers as $alias => $helper) */
public function __construct(array $helpers = array())
{ {
$this->set($helper, is_int($alias) ? null : $alias); $this->helpers = array();
} foreach ($helpers as $alias => $helper) {
} $this->set($helper, is_int($alias) ? null : $alias);
}
/**
* Sets a helper.
*
* @param HelperInterface $value The helper instance
* @param string $alias An alias
*/
public function set(HelperInterface $helper, $alias = null)
{
$this->helpers[$helper->getName()] = $helper;
if (null !== $alias)
{
$this->helpers[$alias] = $helper;
} }
$helper->setHelperSet($this); /**
} * Sets a helper.
*
/** * @param HelperInterface $value The helper instance
* Returns true if the helper if defined. * @param string $alias An alias
* */
* @param string $name The helper name public function set(HelperInterface $helper, $alias = null)
*
* @return Boolean true if the helper is defined, false otherwise
*/
public function has($name)
{
return isset($this->helpers[$name]);
}
/**
* Gets a helper value.
*
* @param string $name The helper name
*
* @return HelperInterface The helper instance
*
* @throws \InvalidArgumentException if the helper is not defined
*/
public function get($name)
{
if (!$this->has($name))
{ {
throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); $this->helpers[$helper->getName()] = $helper;
if (null !== $alias) {
$this->helpers[$alias] = $helper;
}
$helper->setHelperSet($this);
} }
return $this->helpers[$name]; /**
} * Returns true if the helper if defined.
*
* @param string $name The helper name
*
* @return Boolean true if the helper is defined, false otherwise
*/
public function has($name)
{
return isset($this->helpers[$name]);
}
/** /**
* Sets the command associated with this helper set. * Gets a helper value.
* *
* @param Command $command A Command instance * @param string $name The helper name
*/ *
public function setCommand(Command $command = null) * @return HelperInterface The helper instance
{ *
$this->command = $command; * @throws \InvalidArgumentException if the helper is not defined
} */
public function get($name)
{
if (!$this->has($name)) {
throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
}
/** return $this->helpers[$name];
* Gets the command associated with this helper set. }
*
* @return Command A Command instance /**
*/ * Sets the command associated with this helper set.
public function getCommand() *
{ * @param Command $command A Command instance
return $this->command; */
} public function setCommand(Command $command = null)
{
$this->command = $command;
}
/**
* Gets the command associated with this helper set.
*
* @return Command A Command instance
*/
public function getCommand()
{
return $this->command;
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input; 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> * (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. * 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']); * $input = new ArgvInput($_SERVER['argv']);
* *
@ -31,254 +31,225 @@ namespace Symfony\Component\Console\Input;
* the same rules as the argv one. It's almost always better to use the * the same rules as the argv one. It's almost always better to use the
* `StringInput` when you want to provide your own input. * `StringInput` when you want to provide your own input.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* *
* @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
* @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
*/ */
class ArgvInput extends Input class ArgvInput extends Input
{ {
protected $tokens; protected $tokens;
protected $parsed; protected $parsed;
/** /**
* Constructor. * Constructor.
* *
* @param array $argv An array of parameters from the CLI (in the argv format) * @param array $argv An array of parameters from the CLI (in the argv format)
* @param InputDefinition $definition A InputDefinition instance * @param InputDefinition $definition A InputDefinition instance
*/ */
public function __construct(array $argv = null, InputDefinition $definition = null) public function __construct(array $argv = null, InputDefinition $definition = null)
{
if (null === $argv)
{ {
$argv = $_SERVER['argv']; if (null === $argv) {
$argv = $_SERVER['argv'];
}
// strip the program name
array_shift($argv);
$this->tokens = $argv;
parent::__construct($definition);
} }
// strip the program name /**
array_shift($argv); * Processes command line arguments.
*/
$this->tokens = $argv; protected function parse()
parent::__construct($definition);
}
/**
* Processes command line arguments.
*/
protected function parse()
{
$this->parsed = $this->tokens;
while (null !== ($token = array_shift($this->parsed)))
{ {
if ('--' === substr($token, 0, 2)) $this->parsed = $this->tokens;
{ while (null !== $token = array_shift($this->parsed)) {
$this->parseLongOption($token); if ('--' === substr($token, 0, 2)) {
} $this->parseLongOption($token);
elseif ('-' === $token[0]) } elseif ('-' === $token[0]) {
{ $this->parseShortOption($token);
$this->parseShortOption($token); } else {
} $this->parseArgument($token);
else }
{ }
$this->parseArgument($token);
}
}
}
/**
* Parses a short option.
*
* @param string $token The current token.
*/
protected function parseShortOption($token)
{
$name = substr($token, 1);
if (strlen($name) > 1)
{
if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptParameter())
{
// an option with a value (with no space)
$this->addShortOption($name[0], substr($name, 1));
}
else
{
$this->parseShortOptionSet($name);
}
}
else
{
$this->addShortOption($name, null);
}
}
/**
* Parses a short option set.
*
* @param string $token The current token
*/
protected function parseShortOptionSet($name)
{
$len = strlen($name);
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())
{
$this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
break;
}
else
{
$this->addLongOption($option->getName(), true);
}
}
}
/**
* Parses a long option.
*
* @param string $token The current token
*/
protected function parseLongOption($token)
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '='))
{
$this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
}
else
{
$this->addLongOption($name, null);
}
}
/**
* Parses an argument.
*
* @param string $token The current token
*/
protected function parseArgument($token)
{
if (!$this->definition->hasArgument(count($this->arguments)))
{
throw new \RuntimeException('Too many arguments.');
} }
$this->arguments[$this->definition->getArgument(count($this->arguments))->getName()] = $token; /**
} * Parses a short option.
*
/** * @param string $token The current token.
* Adds a short option value. */
* protected function parseShortOption($token)
* @param string $shortcut The short option key
* @param mixed $value The value for the option
*/
protected function addShortOption($shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut))
{ {
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); $name = substr($token, 1);
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 {
$this->parseShortOptionSet($name);
}
} else {
$this->addShortOption($name, null);
}
} }
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); /**
} * Parses a short option set.
*
/** * @param string $token The current token
* Adds a long option value. *
* * @throws \RuntimeException When option given doesn't exist
* @param string $name The long option key */
* @param mixed $value The value for the option protected function parseShortOptionSet($name)
*/
protected function addLongOption($name, $value)
{
if (!$this->definition->hasOption($name))
{ {
throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); $len = strlen($name);
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->acceptValue()) {
$this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
break;
} else {
$this->addLongOption($option->getName(), true);
}
}
} }
$option = $this->definition->getOption($name); /**
* Parses a long option.
if (null === $value && $option->acceptParameter()) *
* @param string $token The current token
*/
protected function parseLongOption($token)
{ {
// if option accepts an optional or mandatory argument $name = substr($token, 2);
// let's see if there is one provided
$next = array_shift($this->parsed); if (false !== $pos = strpos($name, '=')) {
if ('-' !== $next[0]) $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
{ } else {
$value = $next; $this->addLongOption($name, null);
} }
else
{
array_unshift($this->parsed, $next);
}
} }
if (null === $value) /**
* Parses an argument.
*
* @param string $token The current token
*
* @throws \RuntimeException When too many arguments are given
*/
protected function parseArgument($token)
{ {
if ($option->isParameterRequired()) if (!$this->definition->hasArgument(count($this->arguments))) {
{ throw new \RuntimeException('Too many arguments.');
throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); }
}
$value = $option->isParameterOptional() ? $option->getDefault() : true; $this->arguments[$this->definition->getArgument(count($this->arguments))->getName()] = $token;
} }
$this->options[$name] = $value; /**
} * Adds a short option value.
*
/** * @param string $shortcut The short option key
* Returns the first argument from the raw parameters (not parsed). * @param mixed $value The value for the option
* *
* @return string The value of the first argument or null otherwise * @throws \RuntimeException When option given doesn't exist
*/ */
public function getFirstArgument() protected function addShortOption($shortcut, $value)
{
foreach ($this->tokens as $token)
{ {
if ($token && '-' === $token[0]) if (!$this->definition->hasShortcut($shortcut)) {
{ throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
continue; }
}
return $token; $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
}
}
/**
* Returns true if the raw parameters (not parsed) contains a value.
*
* This method is to be used to introspect the input parameters
* before it has been validated. It must be used carefully.
*
* @param string|array $values The value(s) to look for in the raw parameters (can be an array)
*
* @return Boolean true if the value is contained in the raw parameters
*/
public function hasParameterOption($values)
{
if (!is_array($values))
{
$values = array($values);
} }
foreach ($this->tokens as $v) /**
* Adds a long option value.
*
* @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 (in_array($v, $values)) if (!$this->definition->hasOption($name)) {
{ throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
return true; }
}
$option = $this->definition->getOption($name);
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]) {
$value = $next;
} else {
array_unshift($this->parsed, $next);
}
}
if (null === $value) {
if ($option->isValueRequired()) {
throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
}
$value = $option->isValueOptional() ? $option->getDefault() : true;
}
$this->options[$name] = $value;
} }
return false; /**
} * Returns the first argument from the raw parameters (not parsed).
*
* @return string The value of the first argument or null otherwise
*/
public function getFirstArgument()
{
foreach ($this->tokens as $token) {
if ($token && '-' === $token[0]) {
continue;
}
return $token;
}
}
/**
* Returns true if the raw parameters (not parsed) contains a value.
*
* This method is to be used to introspect the input parameters
* before it has been validated. It must be used carefully.
*
* @param string|array $values The value(s) to look for in the raw parameters (can be an array)
*
* @return Boolean true if the value is contained in the raw parameters
*/
public function hasParameterOption($values)
{
if (!is_array($values)) {
$values = array($values);
}
foreach ($this->tokens as $v) {
if (in_array($v, $values)) {
return true;
}
}
return false;
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -18,157 +18,145 @@ namespace Symfony\Component\Console\Input;
* *
* $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar'));
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class ArrayInput extends Input class ArrayInput extends Input
{ {
protected $parameters; protected $parameters;
/** /**
* Constructor. * Constructor.
* *
* @param array $param An array of parameters * @param array $param An array of parameters
* @param InputDefinition $definition A InputDefinition instance * @param InputDefinition $definition A InputDefinition instance
*/ */
public function __construct(array $parameters, InputDefinition $definition = null) public function __construct(array $parameters, InputDefinition $definition = null)
{
$this->parameters = $parameters;
parent::__construct($definition);
}
/**
* Returns the first argument from the raw parameters (not parsed).
*
* @return string The value of the first argument or null otherwise
*/
public function getFirstArgument()
{
foreach ($this->parameters as $key => $value)
{ {
if ($key && '-' === $key[0]) $this->parameters = $parameters;
{
continue;
}
return $value; parent::__construct($definition);
}
}
/**
* Returns true if the raw parameters (not parsed) contains a value.
*
* This method is to be used to introspect the input parameters
* before it has been validated. It must be used carefully.
*
* @param string|array $value The values to look for in the raw parameters (can be an array)
*
* @return Boolean true if the value is contained in the raw parameters
*/
public function hasParameterOption($values)
{
if (!is_array($values))
{
$values = array($values);
} }
foreach ($this->parameters as $k => $v) /**
* Returns the first argument from the raw parameters (not parsed).
*
* @return string The value of the first argument or null otherwise
*/
public function getFirstArgument()
{ {
if (!is_int($k)) foreach ($this->parameters as $key => $value) {
{ if ($key && '-' === $key[0]) {
$v = $k; continue;
} }
if (in_array($v, $values)) return $value;
{ }
return true;
}
} }
return false; /**
} * Returns true if the raw parameters (not parsed) contains a value.
*
/** * This method is to be used to introspect the input parameters
* Processes command line arguments. * before it has been validated. It must be used carefully.
*/ *
protected function parse() * @param string|array $value The values to look for in the raw parameters (can be an array)
{ *
foreach ($this->parameters as $key => $value) * @return Boolean true if the value is contained in the raw parameters
*/
public function hasParameterOption($values)
{ {
if ('--' === substr($key, 0, 2)) if (!is_array($values)) {
{ $values = array($values);
$this->addLongOption(substr($key, 2), $value); }
}
elseif ('-' === $key[0])
{
$this->addShortOption(substr($key, 1), $value);
}
else
{
$this->addArgument($key, $value);
}
}
}
/** foreach ($this->parameters as $k => $v) {
* Adds a short option value. if (!is_int($k)) {
* $v = $k;
* @param string $shortcut The short option key }
* @param mixed $value The value for the option
*/ if (in_array($v, $values)) {
protected function addShortOption($shortcut, $value) return true;
{ }
if (!$this->definition->hasShortcut($shortcut)) }
{
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); return false;
} }
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); /**
} * Processes command line arguments.
*/
/** protected function parse()
* Adds a long option value.
*
* @param string $name The long option key
* @param mixed $value The value for the option
*/
protected function addLongOption($name, $value)
{
if (!$this->definition->hasOption($name))
{ {
throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); foreach ($this->parameters as $key => $value) {
if ('--' === substr($key, 0, 2)) {
$this->addLongOption(substr($key, 2), $value);
} elseif ('-' === $key[0]) {
$this->addShortOption(substr($key, 1), $value);
} else {
$this->addArgument($key, $value);
}
}
} }
$option = $this->definition->getOption($name); /**
* Adds a short option value.
if (null === $value) *
* @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 ($option->isParameterRequired()) if (!$this->definition->hasShortcut($shortcut)) {
{ throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); }
}
$value = $option->isParameterOptional() ? $option->getDefault() : true; $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
} }
$this->options[$name] = $value; /**
} * Adds a long option value.
*
/** * @param string $name The long option key
* Adds an argument value. * @param mixed $value The value for the option
* *
* @param string $name The argument name * @throws \InvalidArgumentException When option given doesn't exist
* @param mixed $value The value for the argument * @throws \InvalidArgumentException When a required value is missing
*/ */
protected function addArgument($name, $value) protected function addLongOption($name, $value)
{
if (!$this->definition->hasArgument($name))
{ {
throw new \RuntimeException(sprintf('The "%s" argument 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->isValueRequired()) {
throw new \InvalidArgumentException(sprintf('The "--%s" option requires a value.', $name));
}
$value = $option->isValueOptional() ? $option->getDefault() : true;
}
$this->options[$name] = $value;
} }
$this->arguments[$name] = $value; /**
} * Adds an argument value.
*
* @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 \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -20,179 +20,180 @@ namespace Symfony\Component\Console\Input;
* * `StringInput`: The input is provided as a string * * `StringInput`: The input is provided as a string
* * `ArrayInput`: The input is provided as an array * * `ArrayInput`: The input is provided as an array
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
abstract class Input implements InputInterface abstract class Input implements InputInterface
{ {
protected $definition; protected $definition;
protected $options; protected $options;
protected $arguments; protected $arguments;
protected $interactive = true; protected $interactive = true;
/** /**
* Constructor. * Constructor.
* *
* @param InputDefinition $definition A InputDefinition instance * @param InputDefinition $definition A InputDefinition instance
*/ */
public function __construct(InputDefinition $definition = null) public function __construct(InputDefinition $definition = null)
{
if (null === $definition)
{ {
$this->definition = new InputDefinition(); if (null === $definition) {
} $this->definition = new InputDefinition();
else } else {
{ $this->bind($definition);
$this->bind($definition); $this->validate();
$this->validate(); }
}
}
/**
* Binds the current Input instance with the given arguments and options.
*
* @param InputDefinition $definition A InputDefinition instance
*/
public function bind(InputDefinition $definition)
{
$this->arguments = array();
$this->options = array();
$this->definition = $definition;
$this->parse();
}
/**
* Processes command line arguments.
*/
abstract protected function parse();
public function validate()
{
if (count($this->arguments) < $this->definition->getArgumentRequiredCount())
{
throw new \RuntimeException('Not enough arguments.');
}
}
public function isInteractive()
{
return $this->interactive;
}
public function setInteractive($interactive)
{
$this->interactive = (Boolean) $interactive;
}
/**
* Returns the argument values.
*
* @return array An array of argument values
*/
public function getArguments()
{
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
}
/**
* Returns the argument value for a given argument name.
*
* @param string $name The argument name
*
* @return mixed The argument value
*/
public function getArgument($name)
{
if (!$this->definition->hasArgument($name))
{
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
} }
return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); /**
} * Binds the current Input instance with the given arguments and options.
*
/** * @param InputDefinition $definition A InputDefinition instance
* Sets an argument value by name. */
* public function bind(InputDefinition $definition)
* @param string $name The argument name
* @param string $value The argument value
*/
public function setArgument($name, $value)
{
if (!$this->definition->hasArgument($name))
{ {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); $this->arguments = array();
$this->options = array();
$this->definition = $definition;
$this->parse();
} }
$this->arguments[$name] = $value; /**
} * Processes command line arguments.
*/
abstract protected function parse();
/** /**
* Returns true if an InputArgument object exists by name or position. * @throws \RuntimeException When not enough arguments are given
* */
* @param string|integer $name The InputArgument name or position public function validate()
*
* @return Boolean true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name)
{
return $this->definition->hasArgument($name);
}
/**
* Returns the options values.
*
* @return array An array of option values
*/
public function getOptions()
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
/**
* Returns the option value for a given option name.
*
* @param string $name The option name
*
* @return mixed The option value
*/
public function getOption($name)
{
if (!$this->definition->hasOption($name))
{ {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
throw new \RuntimeException('Not enough arguments.');
}
} }
return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); public function isInteractive()
}
/**
* Sets an option value by name.
*
* @param string $name The option name
* @param string $value The option value
*/
public function setOption($name, $value)
{
if (!$this->definition->hasOption($name))
{ {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); return $this->interactive;
} }
$this->options[$name] = $value; public function setInteractive($interactive)
} {
$this->interactive = (Boolean) $interactive;
}
/** /**
* Returns true if an InputOption object exists by name. * Returns the argument values.
* *
* @param string $name The InputOption name * @return array An array of argument values
* */
* @return Boolean true if the InputOption object exists, false otherwise public function getArguments()
*/ {
public function hasOption($name) return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
{ }
return $this->definition->hasOption($name);
} /**
* Returns the argument value for a given argument name.
*
* @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)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
}
/**
* Sets an argument value by name.
*
* @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)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|integer $name The InputArgument name or position
*
* @return Boolean true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name)
{
return $this->definition->hasArgument($name);
}
/**
* Returns the options values.
*
* @return array An array of option values
*/
public function getOptions()
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
/**
* Returns the option value for a given option name.
*
* @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)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
}
/**
* Sets an option value by name.
*
* @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)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
$this->options[$name] = $value;
}
/**
* Returns true if an InputOption object exists by name.
*
* @param string $name The InputOption name
*
* @return Boolean true if the InputOption object exists, false otherwise
*/
public function hasOption($name)
{
return $this->definition->hasOption($name);
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -14,121 +14,115 @@ namespace Symfony\Component\Console\Input;
/** /**
* Represents a command line argument. * Represents a command line argument.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class InputArgument class InputArgument
{ {
const REQUIRED = 1; const REQUIRED = 1;
const OPTIONAL = 2; const OPTIONAL = 2;
const IS_ARRAY = 4; const IS_ARRAY = 4;
protected $name; protected $name;
protected $mode; protected $mode;
protected $default; protected $default;
protected $description; protected $description;
/** /**
* Constructor. * Constructor.
* *
* @param string $name The argument name * @param string $name The argument name
* @param integer $mode The argument mode: self::REQUIRED or self::OPTIONAL * @param integer $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text * @param string $description A description text
* @param mixed $default The default value (for self::OPTIONAL mode only) * @param mixed $default The default value (for self::OPTIONAL mode only)
*/ *
public function __construct($name, $mode = null, $description = '', $default = null) * @throws \InvalidArgumentException When argument mode is not valid
{ */
if (null === $mode) public function __construct($name, $mode = null, $description = '', $default = null)
{ {
$mode = self::OPTIONAL; 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));
throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); }
$this->name = $name;
$this->mode = $mode;
$this->description = $description;
$this->setDefault($default);
} }
$this->name = $name; /**
$this->mode = $mode; * Returns the argument name.
$this->description = $description; *
* @return string The argument name
$this->setDefault($default); */
} public function getName()
/**
* Returns the argument name.
*
* @return string The argument name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the argument is required.
*
* @return Boolean true if parameter mode is self::REQUIRED, false otherwise
*/
public function isRequired()
{
return self::REQUIRED === (self::REQUIRED & $this->mode);
}
/**
* Returns true if the argument can take multiple values.
*
* @return Boolean true if mode is self::IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param mixed $default The default value
*/
public function setDefault($default = null)
{
if (self::REQUIRED === $this->mode && null !== $default)
{ {
throw new \LogicException('Cannot set a default value except for Parameter::OPTIONAL mode.'); return $this->name;
} }
if ($this->isArray()) /**
* Returns true if the argument is required.
*
* @return Boolean true if parameter mode is self::REQUIRED, false otherwise
*/
public function isRequired()
{ {
if (null === $default) return self::REQUIRED === (self::REQUIRED & $this->mode);
{
$default = array();
}
else if (!is_array($default))
{
throw new \LogicException('A default value for an array argument must be an array.');
}
} }
$this->default = $default; /**
} * Returns true if the argument can take multiple values.
*
* @return Boolean true if mode is self::IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
}
/** /**
* Returns the default value. * Sets the default value.
* *
* @return mixed The default value * @param mixed $default The default value
*/ *
public function getDefault() * @throws \LogicException When incorrect default value is given
{ */
return $this->default; public function setDefault($default = null)
} {
if (self::REQUIRED === $this->mode && null !== $default) {
throw new \LogicException('Cannot set a default value except for Parameter::OPTIONAL mode.');
}
/** if ($this->isArray()) {
* Returns the description text. if (null === $default) {
* $default = array();
* @return string The description text } else if (!is_array($default)) {
*/ throw new \LogicException('A default value for an array argument must be an array.');
public function getDescription() }
{ }
return $this->description;
} $this->default = $default;
}
/**
* Returns the default value.
*
* @return mixed The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -18,490 +18,454 @@ namespace Symfony\Component\Console\Input;
* *
* $definition = new InputDefinition(array( * $definition = new InputDefinition(array(
* new InputArgument('name', InputArgument::REQUIRED), * new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::PARAMETER_REQUIRED), * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* )); * ));
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class InputDefinition class InputDefinition
{ {
protected $arguments; protected $arguments;
protected $requiredCount; protected $requiredCount;
protected $hasAnArrayArgument = false; protected $hasAnArrayArgument = false;
protected $hasOptional; protected $hasOptional;
protected $options; protected $options;
protected $shortcuts; protected $shortcuts;
/** /**
* Constructor. * Constructor.
* *
* @param array $definition An array of InputArgument and InputOption instance * @param array $definition An array of InputArgument and InputOption instance
*/ */
public function __construct(array $definition = array()) public function __construct(array $definition = array())
{
$this->setDefinition($definition);
}
public function setDefinition(array $definition)
{
$arguments = array();
$options = array();
foreach ($definition as $item)
{ {
if ($item instanceof InputOption) $this->setDefinition($definition);
{
$options[] = $item;
}
else
{
$arguments[] = $item;
}
} }
$this->setArguments($arguments); public function setDefinition(array $definition)
$this->setOptions($options);
}
/**
* Sets the InputArgument objects.
*
* @param array $arguments An array of InputArgument objects
*/
public function setArguments($arguments = array())
{
$this->arguments = array();
$this->requiredCount = 0;
$this->hasOptional = false;
$this->addArguments($arguments);
}
/**
* Add an array of InputArgument objects.
*
* @param array $arguments An array of InputArgument objects
*/
public function addArguments($arguments = array())
{
if (null !== $arguments)
{ {
foreach ($arguments as $argument) $arguments = array();
{ $options = array();
$this->addArgument($argument); foreach ($definition as $item) {
} if ($item instanceof InputOption) {
} $options[] = $item;
} } else {
$arguments[] = $item;
/** }
* Add an InputArgument object.
*
* @param InputArgument $argument An InputArgument object
*/
public function addArgument(InputArgument $argument)
{
if (isset($this->arguments[$argument->getName()]))
{
throw new \LogicException(sprintf('An argument with name "%s" already exist.', $argument->getName()));
}
if ($this->hasAnArrayArgument)
{
throw new \LogicException('Cannot add an argument after an array argument.');
}
if ($argument->isRequired() && $this->hasOptional)
{
throw new \LogicException('Cannot add a required argument after an optional one.');
}
if ($argument->isArray())
{
$this->hasAnArrayArgument = true;
}
if ($argument->isRequired())
{
++$this->requiredCount;
}
else
{
$this->hasOptional = true;
}
$this->arguments[$argument->getName()] = $argument;
}
/**
* Returns an InputArgument by name or by position.
*
* @param string|integer $name The InputArgument name or position
*
* @return InputArgument An InputArgument object
*/
public function getArgument($name)
{
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
if (!$this->hasArgument($name))
{
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return $arguments[$name];
}
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|integer $name The InputArgument name or position
*
* @return Boolean true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name)
{
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
return isset($arguments[$name]);
}
/**
* Gets the array of InputArgument objects.
*
* @return array An array of InputArgument objects
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Returns the number of InputArguments.
*
* @return integer The number of InputArguments
*/
public function getArgumentCount()
{
return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
}
/**
* Returns the number of required InputArguments.
*
* @return integer The number of required InputArguments
*/
public function getArgumentRequiredCount()
{
return $this->requiredCount;
}
/**
* Gets the default values.
*
* @return array An array of default values
*/
public function getArgumentDefaults()
{
$values = array();
foreach ($this->arguments as $argument)
{
$values[$argument->getName()] = $argument->getDefault();
}
return $values;
}
/**
* Sets the InputOption objects.
*
* @param array $options An array of InputOption objects
*/
public function setOptions($options = array())
{
$this->options = array();
$this->shortcuts = array();
$this->addOptions($options);
}
/**
* Add an array of InputOption objects.
*
* @param array $options An array of InputOption objects
*/
public function addOptions($options = array())
{
foreach ($options as $option)
{
$this->addOption($option);
}
}
/**
* Add an InputOption object.
*
* @param InputOption $option An InputOption object
*/
public function addOption(InputOption $option)
{
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()]))
{
throw new \LogicException(sprintf('An option with shortcut "%s" already exist.', $option->getShortcut()));
}
$this->options[$option->getName()] = $option;
if ($option->getShortcut())
{
$this->shortcuts[$option->getShortcut()] = $option->getName();
}
}
/**
* Returns an InputOption by name.
*
* @param string $name The InputOption name
*
* @return InputOption A InputOption object
*/
public function getOption($name)
{
if (!$this->hasOption($name))
{
throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
}
return $this->options[$name];
}
/**
* Returns true if an InputOption object exists by name.
*
* @param string $name The InputOption name
*
* @return Boolean true if the InputOption object exists, false otherwise
*/
public function hasOption($name)
{
return isset($this->options[$name]);
}
/**
* Gets the array of InputOption objects.
*
* @return array An array of InputOption objects
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns true if an InputOption object exists by shortcut.
*
* @param string $name The InputOption shortcut
*
* @return Boolean true if the InputOption object exists, false otherwise
*/
public function hasShortcut($name)
{
return isset($this->shortcuts[$name]);
}
/**
* Gets an InputOption by shortcut.
*
* @return InputOption An InputOption object
*/
public function getOptionForShortcut($shortcut)
{
return $this->getOption($this->shortcutToName($shortcut));
}
/**
* Gets an array of default values.
*
* @return array An array of all default values
*/
public function getOptionDefaults()
{
$values = array();
foreach ($this->options as $option)
{
$values[$option->getName()] = $option->getDefault();
}
return $values;
}
/**
* Returns the InputOption name given a shortcut.
*
* @param string $shortcut The shortcut
*
* @return string The InputOption name
*/
protected function shortcutToName($shortcut)
{
if (!isset($this->shortcuts[$shortcut]))
{
throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
}
return $this->shortcuts[$shortcut];
}
/**
* Gets the synopsis.
*
* @return string The synopsis
*/
public function getSynopsis()
{
$elements = array();
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());
}
foreach ($this->getArguments() as $argument)
{
$elements[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : ''));
if ($argument->isArray())
{
$elements[] = sprintf('... [%sN]', $argument->getName());
}
}
return implode(' ', $elements);
}
/**
* Returns a textual representation of the InputDefinition.
*
* @return string A string representing the InputDefinition
*/
public function asText()
{
// find the largest option or argument name
$max = 0;
foreach ($this->getOptions() as $option)
{
$max = strlen($option->getName()) + 2 > $max ? strlen($option->getName()) + 2 : $max;
}
foreach ($this->getArguments() as $argument)
{
$max = strlen($argument->getName()) > $max ? strlen($argument->getName()) : $max;
}
++$max;
$text = array();
if ($this->getArguments())
{
$text[] = '<comment>Arguments:</comment>';
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
{
$default = '';
} }
$text[] = sprintf(" <info>%-${max}s</info> %s%s", $argument->getName(), $argument->getDescription(), $default); $this->setArguments($arguments);
} $this->setOptions($options);
$text[] = '';
} }
if ($this->getOptions()) /**
* Sets the InputArgument objects.
*
* @param array $arguments An array of InputArgument objects
*/
public function setArguments($arguments = array())
{ {
$text[] = '<comment>Options:</comment>'; $this->arguments = array();
$this->requiredCount = 0;
$this->hasOptional = false;
$this->hasAnArrayArgument = false;
$this->addArguments($arguments);
}
foreach ($this->getOptions() as $option) /**
{ * Add an array of InputArgument objects.
if ($option->acceptParameter() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) *
{ * @param InputArgument[] $arguments An array of InputArgument objects
$default = sprintf('<comment> (default: %s)</comment>', is_array($option->getDefault()) ? str_replace("\n", '', print_r($option->getDefault(), true)): $option->getDefault()); */
public function addArguments($arguments = array())
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
$this->addArgument($argument);
}
} }
else }
{
$default = ''; /**
* 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()])) {
throw new \LogicException(sprintf('An argument with name "%s" already exist.', $argument->getName()));
} }
$multiple = $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''; if ($this->hasAnArrayArgument) {
$text[] = sprintf(' %-'.$max.'s %s%s%s%s', '<info>--'.$option->getName().'</info>', $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '', $option->getDescription(), $default, $multiple); throw new \LogicException('Cannot add an argument after an array argument.');
}
$text[] = '';
}
return implode("\n", $text);
}
/**
* Returns an XML representation of the InputDefinition.
*
* @param Boolean $asDom Whether to return a DOM or an XML string
*
* @return string|DOMDocument An XML string representing the InputDefinition
*/
public function asXml($asDom = false)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($this->getArguments() as $argument)
{
$argumentsXML->appendChild($argumentXML = $dom->createElement('argument'));
$argumentXML->setAttribute('name', $argument->getName());
$argumentXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
$argumentXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
$argumentXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
$argumentXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = is_array($argument->getDefault()) ? $argument->getDefault() : ($argument->getDefault() ? array($argument->getDefault()) : array());
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)
{
$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('is_multiple', $option->isArray() ? 1 : 0);
$optionXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
if ($option->acceptParameter())
{
$optionXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = is_array($option->getDefault()) ? $option->getDefault() : ($option->getDefault() ? array($option->getDefault()) : array());
foreach ($defaults as $default)
{
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
} }
}
if ($argument->isRequired() && $this->hasOptional) {
throw new \LogicException('Cannot add a required argument after an optional one.');
}
if ($argument->isArray()) {
$this->hasAnArrayArgument = true;
}
if ($argument->isRequired()) {
++$this->requiredCount;
} else {
$this->hasOptional = true;
}
$this->arguments[$argument->getName()] = $argument;
} }
return $asDom ? $dom : $dom->saveXml(); /**
} * Returns an InputArgument by name or by position.
*
* @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)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return $arguments[$name];
}
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|integer $name The InputArgument name or position
*
* @return Boolean true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name)
{
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
return isset($arguments[$name]);
}
/**
* Gets the array of InputArgument objects.
*
* @return array An array of InputArgument objects
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Returns the number of InputArguments.
*
* @return integer The number of InputArguments
*/
public function getArgumentCount()
{
return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
}
/**
* Returns the number of required InputArguments.
*
* @return integer The number of required InputArguments
*/
public function getArgumentRequiredCount()
{
return $this->requiredCount;
}
/**
* Gets the default values.
*
* @return array An array of default values
*/
public function getArgumentDefaults()
{
$values = array();
foreach ($this->arguments as $argument) {
$values[$argument->getName()] = $argument->getDefault();
}
return $values;
}
/**
* Sets the InputOption objects.
*
* @param array $options An array of InputOption objects
*/
public function setOptions($options = array())
{
$this->options = array();
$this->shortcuts = array();
$this->addOptions($options);
}
/**
* Add an array of InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function addOptions($options = array())
{
foreach ($options as $option) {
$this->addOption($option);
}
}
/**
* 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()])) {
throw new \LogicException(sprintf('An option named "%s" already exist.', $option->getName()));
} 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()) {
$this->shortcuts[$option->getShortcut()] = $option->getName();
}
}
/**
* Returns an InputOption by name.
*
* @param string $name The InputOption name
*
* @return InputOption A InputOption object
*/
public function getOption($name)
{
if (!$this->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
}
return $this->options[$name];
}
/**
* Returns true if an InputOption object exists by name.
*
* @param string $name The InputOption name
*
* @return Boolean true if the InputOption object exists, false otherwise
*/
public function hasOption($name)
{
return isset($this->options[$name]);
}
/**
* Gets the array of InputOption objects.
*
* @return array An array of InputOption objects
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns true if an InputOption object exists by shortcut.
*
* @param string $name The InputOption shortcut
*
* @return Boolean true if the InputOption object exists, false otherwise
*/
public function hasShortcut($name)
{
return isset($this->shortcuts[$name]);
}
/**
* Gets an InputOption by shortcut.
*
* @return InputOption An InputOption object
*/
public function getOptionForShortcut($shortcut)
{
return $this->getOption($this->shortcutToName($shortcut));
}
/**
* Gets an array of default values.
*
* @return array An array of all default values
*/
public function getOptionDefaults()
{
$values = array();
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
}
return $values;
}
/**
* Returns the InputOption name given a shortcut.
*
* @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])) {
throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
}
return $this->shortcuts[$shortcut];
}
/**
* Gets the synopsis.
*
* @return string The synopsis
*/
public function getSynopsis()
{
$elements = array();
foreach ($this->getOptions() as $option) {
$shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
$elements[] = sprintf('['.($option->isValueRequired() ? '%s--%s="..."' : ($option->isValueOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName());
}
foreach ($this->getArguments() as $argument) {
$elements[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : ''));
if ($argument->isArray()) {
$elements[] = sprintf('... [%sN]', $argument->getName());
}
}
return implode(' ', $elements);
}
/**
* Returns a textual representation of the InputDefinition.
*
* @return string A string representing the InputDefinition
*/
public function asText()
{
// find the largest option or argument name
$max = 0;
foreach ($this->getOptions() as $option) {
$max = strlen($option->getName()) + 2 > $max ? strlen($option->getName()) + 2 : $max;
}
foreach ($this->getArguments() as $argument) {
$max = strlen($argument->getName()) > $max ? strlen($argument->getName()) : $max;
}
++$max;
$text = array();
if ($this->getArguments()) {
$text[] = '<comment>Arguments:</comment>';
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 {
$default = '';
}
$text[] = sprintf(" <info>%-${max}s</info> %s%s", $argument->getName(), $argument->getDescription(), $default);
}
$text[] = '';
}
if ($this->getOptions()) {
$text[] = '<comment>Options:</comment>';
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 {
$default = '';
}
$multiple = $option->isArray() ? '<comment> (multiple values allowed)</comment>' : '';
$text[] = sprintf(' %-'.$max.'s %s%s%s%s', '<info>--'.$option->getName().'</info>', $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '', $option->getDescription(), $default, $multiple);
}
$text[] = '';
}
return implode("\n", $text);
}
/**
* Returns an XML representation of the InputDefinition.
*
* @param Boolean $asDom Whether to return a DOM or an XML string
*
* @return string|DOMDocument An XML string representing the InputDefinition
*/
public function asXml($asDom = false)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($this->getArguments() as $argument) {
$argumentsXML->appendChild($argumentXML = $dom->createElement('argument'));
$argumentXML->setAttribute('name', $argument->getName());
$argumentXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
$argumentXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
$argumentXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
$argumentXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = is_array($argument->getDefault()) ? $argument->getDefault() : ($argument->getDefault() ? array($argument->getDefault()) : array());
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) {
$optionsXML->appendChild($optionXML = $dom->createElement('option'));
$optionXML->setAttribute('name', '--'.$option->getName());
$optionXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
$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->acceptValue()) {
$optionXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = is_array($option->getDefault()) ? $option->getDefault() : ($option->getDefault() ? array($option->getDefault()) : array());
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
}
}
return $asDom ? $dom : $dom->saveXml();
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -14,45 +14,43 @@ namespace Symfony\Component\Console\Input;
/** /**
* InputInterface is the interface implemented by all input classes. * InputInterface is the interface implemented by all input classes.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
interface InputInterface interface InputInterface
{ {
/** /**
* Returns the first argument from the raw parameters (not parsed). * Returns the first argument from the raw parameters (not parsed).
* *
* @return string The value of the first argument or null otherwise * @return string The value of the first argument or null otherwise
*/ */
function getFirstArgument(); function getFirstArgument();
/** /**
* Returns true if the raw parameters (not parsed) contains a value. * Returns true if the raw parameters (not parsed) contains a value.
* *
* This method is to be used to introspect the input parameters * This method is to be used to introspect the input parameters
* before it has been validated. It must be used carefully. * before it has been validated. It must be used carefully.
* *
* @param string $value The value to look for in the raw parameters * @param string $value The value to look for in the raw parameters
* *
* @return Boolean true if the value is contained in the raw parameters * @return Boolean true if the value is contained in the raw parameters
*/ */
function hasParameterOption($value); function hasParameterOption($value);
/** /**
* Binds the current Input instance with the given arguments and options. * Binds the current Input instance with the given arguments and options.
* *
* @param InputDefinition $definition A InputDefinition instance * @param InputDefinition $definition A InputDefinition instance
*/ */
function bind(InputDefinition $definition); function bind(InputDefinition $definition);
function validate(); function validate();
function getArguments(); function getArguments();
function getArgument($name); function getArgument($name);
function getOptions(); function getOptions();
function getOption($name); function getOption($name);
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -14,178 +14,165 @@ namespace Symfony\Component\Console\Input;
/** /**
* Represents a command line option. * Represents a command line option.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class InputOption class InputOption
{ {
const PARAMETER_NONE = 1; const VALUE_NONE = 1;
const PARAMETER_REQUIRED = 2; const VALUE_REQUIRED = 2;
const PARAMETER_OPTIONAL = 4; const VALUE_OPTIONAL = 4;
const PARAMETER_IS_ARRAY = 8; const VALUE_IS_ARRAY = 8;
protected $name; protected $name;
protected $shortcut; protected $shortcut;
protected $mode; protected $mode;
protected $default; protected $default;
protected $description; protected $description;
/** /**
* Constructor. * Constructor.
* *
* @param string $name The option name * @param string $name The option name
* @param string $shortcut The shortcut (can be null) * @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 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)
*/ *
public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) * @throws \InvalidArgumentException If option mode is invalid or incompatible
{ */
if ('--' === substr($name, 0, 2)) public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
{ {
$name = substr($name, 2); if ('--' === substr($name, 0, 2)) {
$name = substr($name, 2);
}
if (empty($shortcut)) {
$shortcut = null;
}
if (null !== $shortcut) {
if ('-' === $shortcut[0]) {
$shortcut = substr($shortcut, 1);
}
}
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));
}
$this->name = $name;
$this->shortcut = $shortcut;
$this->mode = $mode;
$this->description = $description;
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);
} }
if (empty($shortcut)) /**
* Returns the shortcut.
*
* @return string The shortcut
*/
public function getShortcut()
{ {
$shortcut = null; return $this->shortcut;
} }
if (null !== $shortcut) /**
* Returns the name.
*
* @return string The name
*/
public function getName()
{ {
if ('-' === $shortcut[0]) return $this->name;
{
$shortcut = substr($shortcut, 1);
}
} }
if (null === $mode) /**
* Returns true if the option accepts a value.
*
* @return Boolean true if value mode is not self::VALUE_NONE, false otherwise
*/
public function acceptValue()
{ {
$mode = self::PARAMETER_NONE; return $this->isValueRequired() || $this->isValueOptional();
}
else if (!is_int($mode) || $mode > 15)
{
throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
} }
$this->name = $name; /**
$this->shortcut = $shortcut; * Returns true if the option requires a value.
$this->mode = $mode; *
$this->description = $description; * @return Boolean true if value mode is self::VALUE_REQUIRED, false otherwise
*/
if ($this->isArray() && !$this->acceptParameter()) public function isValueRequired()
{ {
throw new \InvalidArgumentException('Impossible to have an option mode PARAMETER_IS_ARRAY if the option does not accept a parameter.'); return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
} }
$this->setDefault($default); /**
} * Returns true if the option takes an optional value.
*
/** * @return Boolean true if value mode is self::VALUE_OPTIONAL, false otherwise
* Returns the shortcut. */
* public function isValueOptional()
* @return string The shortcut
*/
public function getShortcut()
{
return $this->shortcut;
}
/**
* Returns the name.
*
* @return string The name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the option accept a parameter.
*
* @return Boolean true if parameter mode is not self::PARAMETER_NONE, false otherwise
*/
public function acceptParameter()
{
return $this->isParameterRequired() || $this->isParameterOptional();
}
/**
* Returns true if the option requires a parameter.
*
* @return Boolean true if parameter mode is self::PARAMETER_REQUIRED, false otherwise
*/
public function isParameterRequired()
{
return self::PARAMETER_REQUIRED === (self::PARAMETER_REQUIRED & $this->mode);
}
/**
* Returns true if the option takes an optional parameter.
*
* @return Boolean true if parameter mode is self::PARAMETER_OPTIONAL, false otherwise
*/
public function isParameterOptional()
{
return self::PARAMETER_OPTIONAL === (self::PARAMETER_OPTIONAL & $this->mode);
}
/**
* Returns true if the option can take multiple values.
*
* @return Boolean true if mode is self::PARAMETER_IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::PARAMETER_IS_ARRAY === (self::PARAMETER_IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param mixed $default The default value
*/
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.'); return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
} }
if ($this->isArray()) /**
* Returns true if the option can take multiple values.
*
* @return Boolean true if mode is self::VALUE_IS_ARRAY, false otherwise
*/
public function isArray()
{ {
if (null === $default) return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
{
$default = array();
}
elseif (!is_array($default))
{
throw new \LogicException('A default value for an array option must be an array.');
}
} }
$this->default = $this->acceptParameter() ? $default : false; /**
} * Sets the default value.
*
* @param mixed $default The default value
*/
public function setDefault($default = null)
{
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()) {
* Returns the default value. if (null === $default) {
* $default = array();
* @return mixed The default value } elseif (!is_array($default)) {
*/ throw new \LogicException('A default value for an array option must be an array.');
public function getDefault() }
{ }
return $this->default;
}
/** $this->default = $this->acceptValue() ? $default : false;
* Returns the description text. }
*
* @return string The description text /**
*/ * Returns the default value.
public function getDescription() *
{ * @return mixed The default value
return $this->description; */
} public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Input; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -18,63 +18,54 @@ namespace Symfony\Component\Console\Input;
* *
* $input = new StringInput('foo --bar="foobar"'); * $input = new StringInput('foo --bar="foobar"');
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class StringInput extends ArgvInput class StringInput extends ArgvInput
{ {
const REGEX_STRING = '([^ ]+?)(?: |(?<!\\\\)"|(?<!\\\\)\'|$)'; const REGEX_STRING = '([^ ]+?)(?: |(?<!\\\\)"|(?<!\\\\)\'|$)';
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')'; const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
/** /**
* Constructor. * Constructor.
* *
* @param string $input An array of parameters from the CLI (in the argv format) * @param string $input An array of parameters from the CLI (in the argv format)
* @param InputDefinition $definition A InputDefinition instance * @param InputDefinition $definition A InputDefinition instance
*/ */
public function __construct($input, InputDefinition $definition = null) public function __construct($input, InputDefinition $definition = null)
{
parent::__construct(array(), $definition);
$this->tokens = $this->tokenize($input);
}
protected function tokenize($input)
{
$input = preg_replace('/(\r\n|\r|\n|\t)/', ' ', $input);
$tokens = array();
$length = strlen($input);
$cursor = 0;
while ($cursor < $length)
{ {
if (preg_match('/\s+/A', $input, $match, null, $cursor)) parent::__construct(array(), $definition);
{
}
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))
{
$tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
}
elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor))
{
$tokens[] = stripcslashes($match[1]);
}
else
{
// should never happen
// @codeCoverageIgnoreStart
throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
// @codeCoverageIgnoreEnd
}
$cursor += strlen($match[0]); $this->tokens = $this->tokenize($input);
} }
return $tokens; /**
} * @throws \InvalidArgumentException When unable to parse input (should never happen)
*/
protected function tokenize($input)
{
$input = preg_replace('/(\r\n|\r|\n|\t)/', ' ', $input);
$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)) {
$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)) {
$tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
} elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes($match[1]);
} else {
// should never happen
// @codeCoverageIgnoreStart
throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
// @codeCoverageIgnoreEnd
}
$cursor += strlen($match[0]);
}
return $tokens;
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Output; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -22,20 +22,18 @@ namespace Symfony\Component\Console\Output;
* *
* $output = new StreamOutput(fopen('php://stdout', 'w')); * $output = new StreamOutput(fopen('php://stdout', 'w'));
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class ConsoleOutput extends StreamOutput class ConsoleOutput extends StreamOutput
{ {
/** /**
* Constructor. * Constructor.
* *
* @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE) * @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) * @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing)
*/ */
public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null) public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null)
{ {
parent::__construct(fopen('php://stdout', 'w'), $verbosity, $decorated); parent::__construct(fopen('php://stdout', 'w'), $verbosity, $decorated);
} }
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Output; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -16,19 +16,17 @@ namespace Symfony\Component\Console\Output;
* *
* $output = new NullOutput(); * $output = new NullOutput();
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class NullOutput extends Output class NullOutput extends Output
{ {
/** /**
* Writes a message to the output. * Writes a message to the output.
* *
* @param string $message A message to write to the output * @param string $message A message to write to the output
* @param Boolean $newline Whether to add a newline or not * @param Boolean $newline Whether to add a newline or not
*/ */
public function doWrite($message, $newline) public function doWrite($message, $newline)
{ {
} }
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Output; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -20,211 +20,212 @@ namespace Symfony\Component\Console\Output;
* * verbose: -v (more output - debug) * * verbose: -v (more output - debug)
* * quiet: -q (no output) * * quiet: -q (no output)
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
abstract class Output implements OutputInterface abstract class Output implements OutputInterface
{ {
const VERBOSITY_QUIET = 0; const VERBOSITY_QUIET = 0;
const VERBOSITY_NORMAL = 1; const VERBOSITY_NORMAL = 1;
const VERBOSITY_VERBOSE = 2; const VERBOSITY_VERBOSE = 2;
const OUTPUT_NORMAL = 0; const OUTPUT_NORMAL = 0;
const OUTPUT_RAW = 1; const OUTPUT_RAW = 1;
const OUTPUT_PLAIN = 2; const OUTPUT_PLAIN = 2;
protected $verbosity; protected $verbosity;
protected $decorated; protected $decorated;
static protected $styles = array( static protected $styles = array(
'error' => array('bg' => 'red', 'fg' => 'white'), 'error' => array('bg' => 'red', 'fg' => 'white'),
'info' => array('fg' => 'green'), 'info' => array('fg' => 'green'),
'comment' => array('fg' => 'yellow'), 'comment' => array('fg' => 'yellow'),
'question' => array('bg' => 'cyan', 'fg' => 'black'), 'question' => array('bg' => 'cyan', 'fg' => 'black'),
); );
static protected $options = array('bold' => 1, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, 'conceal' => 8); static protected $options = array('bold' => 1, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, 'conceal' => 8);
static protected $foreground = array('black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'white' => 37); static protected $foreground = array('black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'white' => 37);
static protected $background = array('black' => 40, 'red' => 41, 'green' => 42, 'yellow' => 43, 'blue' => 44, 'magenta' => 45, 'cyan' => 46, 'white' => 47); static protected $background = array('black' => 40, 'red' => 41, 'green' => 42, 'yellow' => 43, 'blue' => 44, 'magenta' => 45, 'cyan' => 46, 'white' => 47);
/** /**
* Constructor. * Constructor.
* *
* @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE) * @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) * @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing)
*/ */
public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null) public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null)
{
$this->decorated = (Boolean) $decorated;
$this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
}
/**
* Sets a new style.
*
* @param string $name The style name
* @param array $options An array of options
*/
static public function setStyle($name, $options = array())
{
static::$styles[strtolower($name)] = $options;
}
/**
* Sets the decorated flag.
*
* @param Boolean $decorated Whether to decorated the messages or not
*/
public function setDecorated($decorated)
{
$this->decorated = (Boolean) $decorated;
}
/**
* Gets the decorated flag.
*
* @return Boolean true if the output will decorate messages, false otherwise
*/
public function isDecorated()
{
return $this->decorated;
}
/**
* Sets the verbosity of the output.
*
* @param integer $level The level of verbosity
*/
public function setVerbosity($level)
{
$this->verbosity = (int) $level;
}
/**
* Gets the current verbosity of the output.
*
* @return integer The current level of verbosity
*/
public function getVerbosity()
{
return $this->verbosity;
}
/**
* Writes a message to the output and adds a newline at the end.
*
* @param string|array $messages The message as an array of lines of a single string
* @param integer $type The type of output
*/
public function writeln($messages, $type = 0)
{
$this->write($messages, true, $type);
}
/**
* 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
*/
public function write($messages, $newline = false, $type = 0)
{
if (self::VERBOSITY_QUIET === $this->verbosity)
{ {
return; $this->decorated = (Boolean) $decorated;
$this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
} }
if (!is_array($messages)) /**
* Sets a new style.
*
* @param string $name The style name
* @param array $options An array of options
*/
static public function setStyle($name, $options = array())
{ {
$messages = array($messages); static::$styles[strtolower($name)] = $options;
} }
foreach ($messages as $message) /**
* Sets the decorated flag.
*
* @param Boolean $decorated Whether to decorated the messages or not
*/
public function setDecorated($decorated)
{ {
switch ($type) $this->decorated = (Boolean) $decorated;
{
case Output::OUTPUT_NORMAL:
$message = $this->format($message);
break;
case Output::OUTPUT_RAW:
break;
case Output::OUTPUT_PLAIN:
$message = strip_tags($this->format($message));
break;
default:
throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type));
}
$this->doWrite($message, $newline);
}
}
/**
* Writes a message to the output.
*
* @param string $message A message to write to the output
* @param Boolean $newline Whether to add a newline or not
*/
abstract public function doWrite($message, $newline);
/**
* Formats a message according to the given styles.
*
* @param string $message The message to style
*
* @return string The styled message
*/
protected function format($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);
}
protected function replaceStartStyle($match)
{
if (!$this->decorated)
{
return '';
} }
if (!isset(static::$styles[strtolower($match[1])])) /**
* Gets the decorated flag.
*
* @return Boolean true if the output will decorate messages, false otherwise
*/
public function isDecorated()
{ {
throw new \InvalidArgumentException(sprintf('Unknown style "%s".', $match[1])); return $this->decorated;
} }
$parameters = static::$styles[strtolower($match[1])]; /**
$codes = array(); * Sets the verbosity of the output.
*
if (isset($parameters['fg'])) * @param integer $level The level of verbosity
*/
public function setVerbosity($level)
{ {
$codes[] = static::$foreground[$parameters['fg']]; $this->verbosity = (int) $level;
} }
if (isset($parameters['bg'])) /**
* Gets the current verbosity of the output.
*
* @return integer The current level of verbosity
*/
public function getVerbosity()
{ {
$codes[] = static::$background[$parameters['bg']]; return $this->verbosity;
} }
foreach (static::$options as $option => $value) /**
* Writes a message to the output and adds a newline at the end.
*
* @param string|array $messages The message as an array of lines of a single string
* @param integer $type The type of output
*/
public function writeln($messages, $type = 0)
{ {
if (isset($parameters[$option]) && $parameters[$option]) $this->write($messages, true, $type);
{
$codes[] = $value;
}
} }
return "\033[".implode(';', $codes)."m"; /**
} * Writes a message to the output.
*
protected function replaceEndStyle($match) * @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
if (!$this->decorated) * @param integer $type The type of output
*
* @throws \InvalidArgumentException When unknown output type is given
*/
public function write($messages, $newline = false, $type = 0)
{ {
return ''; if (self::VERBOSITY_QUIET === $this->verbosity) {
return;
}
if (!is_array($messages)) {
$messages = array($messages);
}
foreach ($messages as $message) {
switch ($type) {
case Output::OUTPUT_NORMAL:
$message = $this->format($message);
break;
case Output::OUTPUT_RAW:
break;
case Output::OUTPUT_PLAIN:
$message = strip_tags($this->format($message));
break;
default:
throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type));
}
$this->doWrite($message, $newline);
}
} }
return "\033[0m"; /**
} * Writes a message to the output.
*
* @param string $message A message to write to the output
* @param Boolean $newline Whether to add a newline or not
*/
abstract public function doWrite($message, $newline);
/**
* Formats a message according to the given styles.
*
* @param string $message The message to style
*
* @return string The styled message
*/
protected function format($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);
}
/**
* @throws \InvalidArgumentException When style is unknown
*/
protected function replaceStartStyle($match)
{
if (!$this->decorated) {
return '';
}
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 = array();
foreach ($matches as $match) {
$parameters[$match[1]] = $match[2];
}
}
$codes = array();
if (isset($parameters['fg'])) {
$codes[] = static::$foreground[$parameters['fg']];
}
if (isset($parameters['bg'])) {
$codes[] = static::$background[$parameters['bg']];
}
foreach (static::$options as $option => $value) {
if (isset($parameters[$option]) && $parameters[$option]) {
$codes[] = $value;
}
}
return "\033[".implode(';', $codes).'m';
}
protected function replaceEndStyle($match)
{
if (!$this->decorated) {
return '';
}
return "\033[0m";
}
} }

View File

@ -3,7 +3,7 @@
namespace Symfony\Component\Console\Output; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -14,31 +14,32 @@ namespace Symfony\Component\Console\Output;
/** /**
* OutputInterface is the interface implemented by all Output classes. * OutputInterface is the interface implemented by all Output classes.
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
interface OutputInterface interface OutputInterface
{ {
/** /**
* Writes a message to the output. * Writes a message to the output.
* *
* @param string|array $messages The message as an array of lines of a single string * @param string|array $messages The message as an array of lines of a single string
* @param integer $type The type of output * @param Boolean $newline Whether to add a newline or not
*/ * @param integer $type The type of output
public function write($messages, $type = 0); *
* @throws \InvalidArgumentException When unknown output type is given
*/
function write($messages, $newline = false, $type = 0);
/** /**
* Sets the verbosity of the output. * Sets the verbosity of the output.
* *
* @param integer $level The level of verbosity * @param integer $level The level of verbosity
*/ */
public function setVerbosity($level); function setVerbosity($level);
/** /**
* Sets the decorated flag. * Sets the decorated flag.
* *
* @param Boolean $decorated Whether to decorated the messages or not * @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; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -22,88 +22,84 @@ namespace Symfony\Component\Console\Output;
* *
* $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage console
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class StreamOutput extends Output class StreamOutput extends Output
{ {
protected $stream; protected $stream;
/** /**
* Constructor. * Constructor.
* *
* @param mixed $stream A stream resource * @param mixed $stream A stream resource
* @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE) * @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) * @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing)
*/ *
public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null) * @throws \InvalidArgumentException When first argument is not a real stream
{ */
if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null)
{ {
throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); 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) {
$decorated = $this->hasColorSupport($decorated);
}
parent::__construct($verbosity, $decorated);
} }
$this->stream = $stream; /**
* Gets the stream attached to this StreamOutput instance.
if (null === $decorated) *
* @return resource A stream resource
*/
public function getStream()
{ {
$decorated = $this->hasColorSupport($decorated); return $this->stream;
} }
parent::__construct($verbosity, $decorated); /**
} * Writes a message to the output.
*
/** * @param string $message A message to write to the output
* Gets the stream attached to this StreamOutput instance. * @param Boolean $newline Whether to add a newline or not
* *
* @return resource A stream resource * @throws \RuntimeException When unable to write output (should never happen)
*/ */
public function getStream() public function doWrite($message, $newline)
{
return $this->stream;
}
/**
* Writes a message to the output.
*
* @param string $message A message to write to the output
* @param Boolean $newline Whether to add a newline or not
*/
public function doWrite($message, $newline)
{
if (false === @fwrite($this->stream, $message.($newline ? PHP_EOL : '')))
{ {
// @codeCoverageIgnoreStart if (false === @fwrite($this->stream, $message.($newline ? PHP_EOL : ''))) {
// should never happen // @codeCoverageIgnoreStart
throw new \RuntimeException('Unable to write output.'); // should never happen
// @codeCoverageIgnoreEnd throw new \RuntimeException('Unable to write output.');
// @codeCoverageIgnoreEnd
}
flush();
} }
flush(); /**
} * Returns true if the stream supports colorization.
*
/** * Colorization is disabled if not supported by the stream:
* Returns true if the stream supports colorization. *
* * - windows without ansicon
* Colorization is disabled if not supported by the stream: * - non tty consoles
* *
* - windows without ansicon * @return Boolean true if the stream supports colorization, false otherwise
* - non tty consoles */
* protected function hasColorSupport()
* @return Boolean true if the stream supports colorization, false otherwise
*/
protected function hasColorSupport()
{
// @codeCoverageIgnoreStart
if (DIRECTORY_SEPARATOR == '\\')
{ {
return false !== getenv('ANSICON'); // @codeCoverageIgnoreStart
if (DIRECTORY_SEPARATOR == '\\') {
return false !== getenv('ANSICON');
} else {
return function_exists('posix_isatty') && @posix_isatty($this->stream);
}
// @codeCoverageIgnoreEnd
} }
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; 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> * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
* *
@ -21,118 +21,108 @@ use Symfony\Component\Console\Output\ConsoleOutput;
* This class only works with a PHP compiled with readline support * This class only works with a PHP compiled with readline support
* (either --with-readline or --with-libedit) * (either --with-readline or --with-libedit)
* *
* @package symfony * @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @subpackage cli
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/ */
class Shell class Shell
{ {
protected $application; protected $application;
protected $history; protected $history;
protected $output; protected $output;
/** /**
* Constructor. * Constructor.
* *
* If there is no readline support for the current PHP executable * If there is no readline support for the current PHP executable
* a \RuntimeException exception is thrown. * a \RuntimeException exception is thrown.
* *
* @param Application $application An application instance * @param Application $application An application instance
*/ *
public function __construct(Application $application) * @throws \RuntimeException When Readline extension is not enabled
{ */
if (!function_exists('readline')) public function __construct(Application $application)
{ {
throw new \RuntimeException('Unable to start the shell as the Readline extension is not enabled.'); if (!function_exists('readline')) {
throw new \RuntimeException('Unable to start the shell as the Readline extension is not enabled.');
}
$this->application = $application;
$this->history = getenv('HOME').'/.history_'.$application->getName();
$this->output = new ConsoleOutput();
} }
$this->application = $application; /**
$this->history = getenv('HOME').'/.history_'.$application->getName(); * Runs the shell.
$this->output = new ConsoleOutput(); */
} public function run()
/**
* Runs the shell.
*/
public function run()
{
$this->application->setAutoExit(false);
$this->application->setCatchExceptions(true);
readline_read_history($this->history);
readline_completion_function(array($this, 'autocompleter'));
$this->output->writeln($this->getHeader());
while (true)
{ {
$command = readline($this->application->getName().' > '); $this->application->setAutoExit(false);
$this->application->setCatchExceptions(true);
if (false === $command) readline_read_history($this->history);
{ readline_completion_function(array($this, 'autocompleter'));
$this->output->writeln("\n");
break; $this->output->writeln($this->getHeader());
} while (true) {
$command = readline($this->application->getName().' > ');
readline_add_history($command); if (false === $command) {
readline_write_history($this->history); $this->output->writeln("\n");
if (0 !== $ret = $this->application->run(new StringInput($command), $this->output)) break;
{ }
$this->output->writeln(sprintf('<error>The command terminated with an error status (%s)</error>', $ret));
}
}
}
/** readline_add_history($command);
* Tries to return autocompletion for the current entered text. readline_write_history($this->history);
*
* @param string $text The last segment of the entered text
* @param integer $position The current position
*/
protected function autocompleter($text, $position)
{
$info = readline_info();
$text = substr($info['line_buffer'], 0, $info['end']);
if ($info['point'] !== $info['end']) 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));
return true; }
}
} }
// task name? /**
if (false === strpos($text, ' ') || !$text) * Tries to return autocompletion for the current entered text.
*
* @param string $text The last segment of the entered text
* @param integer $position The current position
*/
protected function autocompleter($text, $position)
{ {
return array_keys($this->application->getCommands()); $info = readline_info();
$text = substr($info['line_buffer'], 0, $info['end']);
if ($info['point'] !== $info['end']) {
return true;
}
// task name?
if (false === strpos($text, ' ') || !$text) {
return array_keys($this->application->all());
}
// options and arguments?
try {
$command = $this->application->findCommand(substr($text, 0, strpos($text, ' ')));
} catch (\Exception $e) {
return true;
}
$list = array('--help');
foreach ($command->getDefinition()->getOptions() as $option) {
$list[] = '--'.$option->getName();
}
return $list;
} }
// options and arguments? /**
try * Returns the shell header.
*
* @return string The header string
*/
protected function getHeader()
{ {
$command = $this->application->findCommand(substr($text, 0, strpos($text, ' '))); return <<<EOF
}
catch (\Exception $e)
{
return true;
}
$list = array('--help');
foreach ($command->getDefinition()->getOptions() as $option)
{
$list[] = '--'.$option->getName();
}
return $list;
}
/**
* Returns the shell header.
*
* @return string The header string
*/
protected function getHeader()
{
return <<<EOF
Welcome to the <info>{$this->application->getName()}</info> shell (<comment>{$this->application->getVersion()}</comment>). Welcome to the <info>{$this->application->getName()}</info> shell (<comment>{$this->application->getVersion()}</comment>).
@ -142,5 +132,5 @@ or <comment>list</comment> to get a list available commands.
To exit the shell, type <comment>^D</comment>. To exit the shell, type <comment>^D</comment>.
EOF; EOF;
} }
} }

View File

@ -6,87 +6,96 @@ use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput; 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 class ApplicationTester
{ {
protected $application; protected $application;
protected $display; protected $display;
protected $input; protected $input;
protected $output; protected $output;
/** /**
* Constructor. * Constructor.
* *
* @param Application $application A Application instance to test. * @param Application $application A Application instance to test.
*/ */
public function __construct(Application $application) public function __construct(Application $application)
{
$this->application = $application;
}
/**
* Executes the application.
*
* Available options:
*
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
*
* @param array $input An array of arguments and options
* @param array $options An array of options
*/
public function run(array $input, $options = array())
{
$this->input = new ArrayInput($input);
if (isset($options['interactive']))
{ {
$this->input->setInteractive($options['interactive']); $this->application = $application;
} }
$this->output = new StreamOutput(fopen('php://memory', 'w', false)); /**
if (isset($options['decorated'])) * Executes the application.
*
* Available options:
*
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
*
* @param array $input An array of arguments and options
* @param array $options An array of options
*/
public function run(array $input, $options = array())
{ {
$this->output->setDecorated($options['decorated']); $this->input = new ArrayInput($input);
} if (isset($options['interactive'])) {
if (isset($options['verbosity'])) $this->input->setInteractive($options['interactive']);
{ }
$this->output->setVerbosity($options['verbosity']);
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
if (isset($options['decorated'])) {
$this->output->setDecorated($options['decorated']);
}
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}
$ret = $this->application->run($this->input, $this->output);
rewind($this->output->getStream());
return $this->display = stream_get_contents($this->output->getStream());
} }
$ret = $this->application->run($this->input, $this->output); /**
* Gets the display returned by the last execution of the application.
*
* @return string The display
*/
public function getDisplay()
{
return $this->display;
}
rewind($this->output->getStream()); /**
* Gets the input instance used by the last execution of the application.
*
* @return InputInterface The current input instance
*/
public function getInput()
{
return $this->input;
}
return $this->display = stream_get_contents($this->output->getStream()); /**
} * Gets the output instance used by the last execution of the application.
*
/** * @return OutputInterface The current output instance
* Gets the display returned by the last execution of the application. */
* public function getOutput()
* @return string The display {
*/ return $this->output;
public function getDisplay() }
{
return $this->display;
}
/**
* Gets the input instance used by the last execution of the application.
*
* @return InputInterface The current input instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance used by the last execution of the application.
*
* @return OutputInterface The current output instance
*/
public function getOutput()
{
return $this->output;
}
} }

View File

@ -6,87 +6,96 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput; 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 class CommandTester
{ {
protected $command; protected $command;
protected $display; protected $display;
protected $input; protected $input;
protected $output; protected $output;
/** /**
* Constructor. * Constructor.
* *
* @param Command $command A Command instance to test. * @param Command $command A Command instance to test.
*/ */
public function __construct(Command $command) public function __construct(Command $command)
{
$this->command = $command;
}
/**
* Executes the command.
*
* Available options:
*
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
*
* @param array $input An array of arguments and options
* @param array $options An array of options
*/
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->setInteractive($options['interactive']); $this->command = $command;
} }
$this->output = new StreamOutput(fopen('php://memory', 'w', false)); /**
if (isset($options['decorated'])) * Executes the command.
*
* Available options:
*
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
*
* @param array $input An array of arguments and options
* @param array $options An array of options
*/
public function execute(array $input, array $options = array())
{ {
$this->output->setDecorated($options['decorated']); $this->input = new ArrayInput($input);
} if (isset($options['interactive'])) {
if (isset($options['verbosity'])) $this->input->setInteractive($options['interactive']);
{ }
$this->output->setVerbosity($options['verbosity']);
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
if (isset($options['decorated'])) {
$this->output->setDecorated($options['decorated']);
}
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}
$ret = $this->command->run($this->input, $this->output);
rewind($this->output->getStream());
return $this->display = stream_get_contents($this->output->getStream());
} }
$ret = $this->command->run($this->input, $this->output); /**
* Gets the display returned by the last execution of the command.
*
* @return string The display
*/
public function getDisplay()
{
return $this->display;
}
rewind($this->output->getStream()); /**
* Gets the input instance used by the last execution of the command.
*
* @return InputInterface The current input instance
*/
public function getInput()
{
return $this->input;
}
return $this->display = stream_get_contents($this->output->getStream()); /**
} * Gets the output instance used by the last execution of the command.
*
/** * @return OutputInterface The current output instance
* Gets the display returned by the last execution of the command. */
* public function getOutput()
* @return string The display {
*/ return $this->output;
public function getDisplay() }
{
return $this->display;
}
/**
* Gets the input instance used by the last execution of the command.
*
* @return InputInterface The current input instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance used by the last execution of the command.
*
* @return OutputInterface The current output instance
*/
public function getOutput()
{
return $this->output;
}
} }

@ -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 class CompanyFixContract extends CompanyContract
{ {
/** /**
* @column(type="integer", nullable=true) * @column(type="integer")
* @var int * @var int
*/ */
private $fixPrice = 0; private $fixPrice = 0;

View File

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

View File

@ -8,7 +8,7 @@ namespace Doctrine\Tests\Models\Company;
class CompanyFlexUltraContract extends CompanyFlexContract class CompanyFlexUltraContract extends CompanyFlexContract
{ {
/** /**
* @column(type="integer", nullable=true) * @column(type="integer")
* @var int * @var int
*/ */
private $maxPrice = 0; 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) public function removeProduct(ECommerceProduct $product)
{ {
$removed = $this->products->removeElement($product); $removed = $this->products->removeElement($product);
if ($removed !== null) { if ($removed) {
$removed->removeCategory($this); $product->removeCategory($this);
} }
} }
@ -114,8 +114,8 @@ class ECommerceCategory
public function removeChild(ECommerceCategory $child) public function removeChild(ECommerceCategory $child)
{ {
$removed = $this->children->removeElement($child); $removed = $this->children->removeElement($child);
if ($removed !== null) { if ($removed) {
$removed->removeParent(); $child->removeParent();
} }
} }

View File

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

View File

@ -14,6 +14,16 @@ class EntityManagerTest extends \Doctrine\Tests\OrmTestCase
$this->_em = $this->_getTestEntityManager(); $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() public function testGetConnection()
{ {
$this->assertType('\Doctrine\DBAL\Connection', $this->_em->getConnection()); $this->assertType('\Doctrine\DBAL\Connection', $this->_em->getConnection());

View File

@ -3,6 +3,7 @@
namespace Doctrine\Tests\ORM\Functional; namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Tools\SchemaTool; use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Query;
use Doctrine\Tests\Models\CMS\CmsUser; use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsPhonenumber; use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsAddress; use Doctrine\Tests\Models\CMS\CmsAddress;
@ -264,6 +265,110 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('developer', $user->status); $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() public function testAddToCollectionDoesNotInitialize()
{ {
$user = new CmsUser; $user = new CmsUser;

View File

@ -353,4 +353,61 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($manager->getId(), $dqlManager->getId()); $this->assertEquals($manager->getId(), $dqlManager->getId());
$this->assertEquals($person->getId(), $dqlManager->getSpouse()->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'; $user->status = 'developer';
$ph1 = new CmsPhonenumber; $ph1 = new CmsPhonenumber;
$ph1->phonenumber = 1234; $ph1->phonenumber = "1234";
$user->addPhonenumber($ph1); $user->addPhonenumber($ph1);
$this->_em->persist($user); $this->_em->persist($user);
@ -69,23 +69,35 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
$user = unserialize($serialized); $user = unserialize($serialized);
$this->assertEquals(1, count($user->getPhonenumbers()), "Pre-Condition: 1 Phonenumber");
$ph2 = new CmsPhonenumber; $ph2 = new CmsPhonenumber;
$ph2->phonenumber = 56789; $ph2->phonenumber = "56789";
$user->addPhonenumber($ph2); $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->assertFalse($this->_em->contains($user));
$this->_em->persist($ph2); $this->_em->persist($ph2);
// Merge back in // Merge back in
$user = $this->_em->merge($user); // merge cascaded to phonenumbers $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->_em->flush();
$this->assertTrue($this->_em->contains($user)); $this->assertTrue($this->_em->contains($user), "Failed to assert that merged user is contained inside EntityManager persistence context.");
$this->assertEquals(2, count($user->getPhonenumbers()));
$phonenumbers = $user->getPhonenumbers(); $phonenumbers = $user->getPhonenumbers();
$this->assertTrue($this->_em->contains($phonenumbers[0])); $this->assertNotSame($oldPhonenumbers, $phonenumbers, "Merge should replace the Detached Collection with a new PersistentCollection.");
$this->assertTrue($this->_em->contains($phonenumbers[1])); $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) {} } catch (\Exception $expected) {}
} }
public function testUninitializedLazyAssociationsAreIgnoredOnMerge() { public function testUninitializedLazyAssociationsAreIgnoredOnMerge()
//$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); {
$user = new CmsUser; $user = new CmsUser;
$user->name = 'Guilherme'; $user->name = 'Guilherme';
$user->username = 'gblanco'; $user->username = 'gblanco';
@ -136,5 +148,49 @@ class DetachedEntityTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertFalse($managedAddress2->user === $detachedAddress2->user); $this->assertFalse($managedAddress2->user === $detachedAddress2->user);
$this->assertFalse($managedAddress2->user->__isInitialized__); $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\CmsUser;
use Doctrine\Tests\Models\CMS\CmsPhonenumber; use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsAddress;
require_once __DIR__ . '/../../TestInit.php'; require_once __DIR__ . '/../../TestInit.php';
@ -19,7 +20,8 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
parent::setUp(); parent::setUp();
} }
public function testBasicFinders() { public function loadFixture()
{
$user = new CmsUser; $user = new CmsUser;
$user->name = 'Roman'; $user->name = 'Roman';
$user->username = 'romanb'; $user->username = 'romanb';
@ -38,35 +40,58 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
unset($user2); unset($user2);
$this->_em->clear(); $this->_em->clear();
return $user1Id;
}
public function testBasicFind()
{
$user1Id = $this->loadFixture();
$repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); $repos = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$user = $repos->find($user1Id); $user = $repos->find($user1Id);
$this->assertTrue($user instanceof CmsUser); $this->assertTrue($user instanceof CmsUser);
$this->assertEquals('Roman', $user->name); $this->assertEquals('Roman', $user->name);
$this->assertEquals('freak', $user->status); $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')); $users = $repos->findBy(array('status' => 'dev'));
$this->assertEquals(1, count($users)); $this->assertEquals(1, count($users));
$this->assertTrue($users[0] instanceof CmsUser); $this->assertTrue($users[0] instanceof CmsUser);
$this->assertEquals('Guilherme', $users[0]->name); $this->assertEquals('Guilherme', $users[0]->name);
$this->assertEquals('dev', $users[0]->status); $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'); $users = $repos->findByStatus('dev');
$this->assertEquals(1, count($users)); $this->assertEquals(1, count($users));
$this->assertTrue($users[0] instanceof CmsUser); $this->assertTrue($users[0] instanceof CmsUser);
$this->assertEquals('Guilherme', $users[0]->name); $this->assertEquals('Guilherme', $users[0]->name);
$this->assertEquals('dev', $users[0]->status); $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(); $users = $repos->findAll();
$this->assertEquals(2, count($users)); $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'); $this->_em->getConfiguration()->addEntityNamespace('CMS', 'Doctrine\Tests\Models\CMS');
@ -74,8 +99,12 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$users = $repos->findAll(); $users = $repos->findAll();
$this->assertEquals(2, count($users)); $this->assertEquals(2, count($users));
}
public function tearDown()
{
$this->_em->getConfiguration()->setEntityNamespaces(array()); $this->_em->getConfiguration()->setEntityNamespaces(array());
parent::tearDown();
} }
/** /**
@ -150,5 +179,116 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->setExpectedException('Doctrine\ORM\OptimisticLockException'); $this->setExpectedException('Doctrine\ORM\OptimisticLockException');
$this->_em->find('Doctrine\Tests\Models\Cms\CmsUser', $userId, \Doctrine\DBAL\LockMode::OPTIMISTIC); $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() public function testStandardInsertSetsInitialVersionValue()
{ {
$test = new OptimisticStandard(); $test = new OptimisticStandard();
@ -107,6 +121,7 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->persist($test); $this->_em->persist($test);
$this->_em->flush(); $this->_em->flush();
$this->assertType('int', $test->getVersion());
$this->assertEquals(1, $test->getVersion()); $this->assertEquals(1, $test->getVersion());
return $test; return $test;
@ -139,10 +154,13 @@ class OptimisticTest extends \Doctrine\Tests\OrmFunctionalTestCase
{ {
$test = new OptimisticTimestamp(); $test = new OptimisticTimestamp();
$test->name = 'Testing'; $test->name = 'Testing';
$this->assertNull($test->version, "Pre-Condition");
$this->_em->persist($test); $this->_em->persist($test);
$this->_em->flush(); $this->_em->flush();
$this->assertTrue(strtotime($test->version) > 0); $this->assertType('DateTime', $test->version);
return $test; return $test;
} }

View File

@ -247,6 +247,34 @@ class ManyToManyBasicAssociationTest extends \Doctrine\Tests\OrmFunctionalTestCa
$this->assertEquals(0, count($newUser->getGroups())); $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 * @param int $groupCount
* @return CmsUser * @return CmsUser

View File

@ -12,100 +12,35 @@ require_once __DIR__ . '/../../TestInit.php';
class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase
{ {
protected function setUp() { protected function setUp() {
$this->useModelSet('directorytree');
parent::setUp(); 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() public function testCRUD()
{ {
$e = new EntitySubClass; $root = new \Doctrine\Tests\Models\DirectoryTree\Directory();
$e->setId(1); $root->setName('Root');
$e->setName('Roman'); $root->setPath('/root');
$e->setMapped1(42);
$e->setMapped2('bar'); $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->flush();
$this->_em->clear(); $this->_em->clear();
$e2 = $this->_em->find('Doctrine\Tests\ORM\Functional\EntitySubClass', 1); $cleanFile = $this->_em->find(get_class($file), $file->getId());
$this->assertEquals(1, $e2->getId());
$this->assertEquals('Roman', $e2->getName()); $this->assertType('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent());
$this->assertNull($e2->getMappedRelated1()); $this->assertEquals($directory->getId(), $cleanFile->getParent()->getId());
$this->assertEquals(42, $e2->getMapped1()); $this->assertType('Doctrine\Tests\Models\DirectoryTree\Directory', $cleanFile->getParent()->getParent());
$this->assertEquals('bar', $e2->getMapped2()); $this->assertEquals($root->getId(), $cleanFile->getParent()->getParent()->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;
}
}
/** @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