Merge branch '2.3' into 2.7

* 2.3:
  Implement the support of timezone objects in the stub IntlDateFormatter
  [DoctrineBridge][Form] Fix EntityChoiceList when indexing by primary foreign key
This commit is contained in:
Fabien Potencier 2015-08-12 11:55:25 +02:00
commit e8948671a1
9 changed files with 354 additions and 16 deletions

View File

@ -45,6 +45,13 @@ class EntityChoiceList extends ObjectChoiceList
*/
private $classMetadata;
/**
* Metadata for target class of primary key association.
*
* @var ClassMetadata
*/
private $idClassMetadata;
/**
* Contains the query builder that builds the query for fetching the
* entities.
@ -112,16 +119,21 @@ class EntityChoiceList extends ObjectChoiceList
$this->class = $this->classMetadata->getName();
$this->loaded = is_array($entities) || $entities instanceof \Traversable;
$this->preferredEntities = $preferredEntities;
list(
$this->idAsIndex,
$this->idAsValue,
$this->idField
) = $this->getIdentifierInfoForClass($this->classMetadata);
$identifier = $this->classMetadata->getIdentifierFieldNames();
if (null !== $this->idField && $this->classMetadata->hasAssociation($this->idField)) {
$this->idClassMetadata = $this->em->getClassMetadata(
$this->classMetadata->getAssociationTargetClass($this->idField)
);
if (1 === count($identifier)) {
$this->idField = $identifier[0];
$this->idAsValue = true;
if (in_array($this->classMetadata->getTypeOfField($this->idField), array('integer', 'smallint', 'bigint'))) {
$this->idAsIndex = true;
}
list(
$this->idAsIndex,
$this->idAsValue
) = $this->getIdentifierInfoForClass($this->idClassMetadata);
}
if (!$this->loaded) {
@ -233,7 +245,7 @@ class EntityChoiceList extends ObjectChoiceList
// "INDEX BY" clause to the Doctrine query in the loader,
// but I'm not sure whether that's doable in a generic fashion.
foreach ($unorderedEntities as $entity) {
$value = $this->fixValue(current($this->getIdentifierValues($entity)));
$value = $this->fixValue($this->getSingleIdentifierValue($entity));
$entitiesByValue[$value] = $entity;
}
@ -279,7 +291,7 @@ class EntityChoiceList extends ObjectChoiceList
foreach ($entities as $i => $entity) {
if ($entity instanceof $this->class) {
// Make sure to convert to the right format
$values[$i] = $this->fixValue(current($this->getIdentifierValues($entity)));
$values[$i] = $this->fixValue($this->getSingleIdentifierValue($entity));
}
}
@ -322,7 +334,7 @@ class EntityChoiceList extends ObjectChoiceList
foreach ($entities as $i => $entity) {
if ($entity instanceof $this->class) {
// Make sure to convert to the right format
$indices[$i] = $this->fixIndex(current($this->getIdentifierValues($entity)));
$indices[$i] = $this->fixIndex($this->getSingleIdentifierValue($entity));
}
}
@ -383,7 +395,7 @@ class EntityChoiceList extends ObjectChoiceList
protected function createIndex($entity)
{
if ($this->idAsIndex) {
return $this->fixIndex(current($this->getIdentifierValues($entity)));
return $this->fixIndex($this->getSingleIdentifierValue($entity));
}
return parent::createIndex($entity);
@ -403,7 +415,7 @@ class EntityChoiceList extends ObjectChoiceList
protected function createValue($entity)
{
if ($this->idAsValue) {
return (string) current($this->getIdentifierValues($entity));
return (string) $this->getSingleIdentifierValue($entity);
}
return parent::createValue($entity);
@ -426,6 +438,36 @@ class EntityChoiceList extends ObjectChoiceList
return $index;
}
/**
* Get identifier information for a class.
*
* @param ClassMetadata $classMetadata The entity metadata
*
* @return array Return an array with idAsIndex, idAsValue and identifier
*/
private function getIdentifierInfoForClass(ClassMetadata $classMetadata)
{
$identifier = null;
$idAsIndex = false;
$idAsValue = false;
$identifiers = $classMetadata->getIdentifierFieldNames();
if (1 === count($identifiers)) {
$identifier = $identifiers[0];
if (!$classMetadata->hasAssociation($identifier)) {
$idAsValue = true;
if (in_array($classMetadata->getTypeOfField($identifier), array('integer', 'smallint', 'bigint'))) {
$idAsIndex = true;
}
}
}
return array($idAsIndex, $idAsValue, $identifier);
}
/**
* Loads the list with entities.
*
@ -449,6 +491,33 @@ class EntityChoiceList extends ObjectChoiceList
$this->loaded = true;
}
/**
* Returns the first (and only) value of the identifier fields of an entity.
*
* Doctrine must know about this entity, that is, the entity must already
* be persisted or added to the identity map before. Otherwise an
* exception is thrown.
*
* @param object $entity The entity for which to get the identifier
*
* @return array The identifier values
*
* @throws RuntimeException If the entity does not exist in Doctrine's identity map
*/
private function getSingleIdentifierValue($entity)
{
$value = current($this->getIdentifierValues($entity));
if ($this->idClassMetadata) {
$class = $this->idClassMetadata->getName();
if ($value instanceof $class) {
$value = current($this->idClassMetadata->getIdentifierValues($value));
}
}
return $value;
}
/**
* Returns the values of the identifier fields of an entity.
*

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Tests\Fixtures;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\OneToOne;
/** @Entity */
class SingleAssociationToIntIdEntity
{
/** @Id @OneToOne(targetEntity="SingleIntIdNoToStringEntity", cascade={"ALL"}) */
protected $entity;
/** @Column(type="string", nullable=true) */
public $name;
public function __construct(SingleIntIdNoToStringEntity $entity, $name)
{
$this->entity = $entity;
$this->name = $name;
}
public function __toString()
{
return (string) $this->name;
}
}

View File

@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
/**
* Test choices generated from an entity with a primary foreign key.
*
* @author Premi Giorgio <giosh94mhz@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class AbstractEntityChoiceListSingleAssociationToIntIdTest extends AbstractEntityChoiceListTest
{
protected function getEntityClass()
{
return 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity';
}
protected function getClassesMetadata()
{
return array(
$this->em->getClassMetadata($this->getEntityClass()),
$this->em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity'),
);
}
protected function createChoiceList()
{
return new EntityChoiceList($this->em, $this->getEntityClass(), 'name');
}
/**
* @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
protected function createObjects()
{
$innerA = new SingleIntIdNoToStringEntity(-10, 'inner_A');
$innerB = new SingleIntIdNoToStringEntity(10, 'inner_B');
$innerC = new SingleIntIdNoToStringEntity(20, 'inner_C');
$innerD = new SingleIntIdNoToStringEntity(30, 'inner_D');
$this->em->persist($innerA);
$this->em->persist($innerB);
$this->em->persist($innerC);
$this->em->persist($innerD);
return array(
new SingleAssociationToIntIdEntity($innerA, 'A'),
new SingleAssociationToIntIdEntity($innerB, 'B'),
new SingleAssociationToIntIdEntity($innerC, 'C'),
new SingleAssociationToIntIdEntity($innerD, 'D'),
);
}
protected function getChoices()
{
return array('_10' => $this->obj1, 10 => $this->obj2, 20 => $this->obj3, 30 => $this->obj4);
}
protected function getLabels()
{
return array('_10' => 'A', 10 => 'B', 20 => 'C', 30 => 'D');
}
protected function getValues()
{
return array('_10' => '-10', 10 => '10', 20 => '20', 30 => '30');
}
protected function getIndices()
{
return array('_10', 10, 20, 30);
}
}

View File

@ -39,7 +39,7 @@ abstract class AbstractEntityChoiceListTest extends AbstractChoiceListTest
$this->em = DoctrineTestHelper::createTestEntityManager();
$schemaTool = new SchemaTool($this->em);
$classes = array($this->em->getClassMetadata($this->getEntityClass()));
$classes = $this->getClassesMetadata();
try {
$schemaTool->dropSchema($classes);
@ -73,6 +73,11 @@ abstract class AbstractEntityChoiceListTest extends AbstractChoiceListTest
abstract protected function createObjects();
protected function getClassesMetadata()
{
return array($this->em->getClassMetadata($this->getEntityClass()));
}
/**
* @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/**
* @author Premi Giorgio <giosh94mhz@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LoadedEntityChoiceListSingleAssociationToIntIdTest extends AbstractEntityChoiceListSingleAssociationToIntIdTest
{
/**
* @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
protected function createChoiceList()
{
$list = parent::createChoiceList();
// load list
$list->getChoices();
return $list;
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/**
* @author Premi Giorgio <giosh94mhz@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class UnloadedEntityChoiceListSingleAssociationToIntIdTest extends AbstractEntityChoiceListSingleAssociationToIntIdTest
{
public function testGetIndicesForValuesIgnoresNonExistingValues()
{
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
{
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
}
}

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
/**
* @author Premi Giorgio <giosh94mhz@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest extends UnloadedEntityChoiceListSingleAssociationToIntIdTest
{
/**
* @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
protected function createChoiceList()
{
$qb = $this->em->createQueryBuilder()->select('s')->from($this->getEntityClass(), 's');
$loader = new ORMQueryBuilderLoader($qb);
return new EntityChoiceList($this->em, $this->getEntityClass(), null, $loader);
}
}

View File

@ -132,7 +132,7 @@ class IntlDateFormatter
* @param string $locale The locale code. The only currently supported locale is "en" (or null using the default locale, i.e. "en").
* @param int $datetype Type of date formatting, one of the format type constants
* @param int $timetype Type of time formatting, one of the format type constants
* @param string $timezone Timezone identifier
* @param mixed $timezone Timezone identifier
* @param int $calendar Calendar to use for formatting or parsing. The only currently
* supported value is IntlDateFormatter::GREGORIAN.
* @param string $pattern Optional pattern to use when formatting
@ -157,7 +157,7 @@ class IntlDateFormatter
$this->timetype = $timetype;
$this->setPattern($pattern);
$this->setTimeZoneId($timezone);
$this->setTimeZone($timezone);
}
/**
@ -583,6 +583,19 @@ class IntlDateFormatter
*/
public function setTimeZone($timeZone)
{
if ($timeZone instanceof \IntlTimeZone) {
$timeZone = $timeZone->getID();
}
if ($timeZone instanceof \DateTimeZone) {
$timeZone = $timeZone->getName();
// DateTimeZone returns the GMT offset timezones without the leading GMT, while our parsing requires it.
if (!empty($timeZone) && ('+' === $timeZone[0] || '-' === $timeZone[0])) {
$timeZone = 'GMT'.$timeZone;
}
}
return $this->setTimeZoneId($timeZone);
}

View File

@ -380,6 +380,36 @@ abstract class AbstractIntlDateFormatterTest extends \PHPUnit_Framework_TestCase
);
}
public function testFormatWithDateTimeZone()
{
if (PHP_VERSION_ID < 50500) {
$this->markTestSkipped('Only in PHP 5.5+ IntlDateFormatter allows to use DateTimeZone objects.');
}
if (defined('HHVM_VERSION_ID')) {
$this->markTestSkipped('This test cannot work on HHVM. See https://github.com/facebook/hhvm/issues/5875 for the issue.');
}
$formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, new \DateTimeZone('GMT+03:00'), IntlDateFormatter::GREGORIAN, 'zzzz');
$this->assertEquals('GMT+03:00', $formatter->format(0));
}
public function testFormatWithIntlTimeZone()
{
if (PHP_VERSION_ID < 50500) {
$this->markTestSkipped('Only in PHP 5.5+ IntlDateFormatter allows to use DateTimeZone objects.');
}
if (!class_exists('IntlTimeZone')) {
$this->markTestSkipped('This test requires the IntlTimeZone class from the Intl extension.');
}
$formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, \IntlTimeZone::createTimeZone('GMT+03:00'), IntlDateFormatter::GREGORIAN, 'zzzz');
$this->assertEquals('GMT+03:00', $formatter->format(0));
}
public function testFormatWithTimezoneFromEnvironmentVariable()
{
if (PHP_VERSION_ID >= 50500) {