Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,300 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\ChoiceList;
use Doctrine\Common\Util\ClassUtils;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Symfony\Component\Form\Exception\InvalidArgumentException;
use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPath;
@trigger_error(
'The '.__CLASS__.' class is deprecated since 3.24 and will be removed in 4.0.'
.' Use '.__NAMESPACE__.'\ModelChoiceLoader instead.',
E_USER_DEPRECATED
);
/**
* NEXT_MAJOR: Remove this class.
*
* @deprecated Since 3.24, to be removed in 4.0. Use Sonata\AdminBundle\ModelChoiceLoader instead
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ModelChoiceList extends SimpleChoiceList
{
/**
* @var ModelManagerInterface
*/
private $modelManager;
/**
* @var string
*/
private $class;
/**
* The entities from which the user can choose.
*
* This array is either indexed by ID (if the ID is a single field)
* or by key in the choices array (if the ID consists of multiple fields)
*
* This property is initialized by initializeChoices(). It should only
* be accessed through getEntity() and getEntities().
*
* @var mixed
*/
private $entities = [];
/**
* Contains the query builder that builds the query for fetching the
* entities.
*
* This property should only be accessed through queryBuilder.
*
* @var \Doctrine\ORM\QueryBuilder
*/
private $query;
/**
* The fields of which the identifier of the underlying class consists.
*
* This property should only be accessed through identifier.
*
* @var array
*/
private $identifier = [];
/**
* A cache for \ReflectionProperty instances for the underlying class.
*
* This property should only be accessed through getReflProperty().
*
* @var array
*/
private $reflProperties = [];
/**
* @var PropertyPath
*/
private $propertyPath;
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* @param ModelManagerInterface $modelManager
* @param string $class
* @param null $property
* @param null $query
* @param array $choices
*/
public function __construct(ModelManagerInterface $modelManager, $class, $property = null, $query = null, $choices = [], PropertyAccessorInterface $propertyAccessor = null)
{
$this->modelManager = $modelManager;
$this->class = $class;
$this->query = $query;
$this->identifier = $this->modelManager->getIdentifierFieldNames($this->class);
// The property option defines, which property (path) is used for
// displaying entities as strings
if ($property) {
$this->propertyPath = new PropertyPath($property);
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}
parent::__construct($this->load($choices));
}
/**
* @return array
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* Returns the according entities for the choices.
*
* If the choices were not initialized, they are initialized now. This
* is an expensive operation, except if the entities were passed in the
* "choices" option.
*
* @return array An array of entities
*/
public function getEntities()
{
return $this->entities;
}
/**
* Returns the entity for the given key.
*
* If the underlying entities have composite identifiers, the choices
* are initialized. The key is expected to be the index in the choices
* array in this case.
*
* If they have single identifiers, they are either fetched from the
* internal entity cache (if filled) or loaded from the database.
*
* @param string $key The choice key (for entities with composite
* identifiers) or entity ID (for entities with single
* identifiers)
*
* @return object The matching entity
*/
public function getEntity($key)
{
if (count($this->identifier) > 1) {
// $key is a collection index
$entities = $this->getEntities();
return isset($entities[$key]) ? $entities[$key] : null;
} elseif ($this->entities) {
return isset($this->entities[$key]) ? $this->entities[$key] : null;
}
return $this->modelManager->find($this->class, $key);
}
/**
* Returns the values 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
*
* @throws InvalidArgumentException If the entity does not exist in Doctrine's
* identity map
*
* @return array
*/
public function getIdentifierValues($entity)
{
try {
return $this->modelManager->getIdentifierValues($entity);
} catch (\Exception $e) {
throw new InvalidArgumentException(sprintf('Unable to retrieve the identifier values for entity %s', ClassUtils::getClass($entity)), 0, $e);
}
}
/**
* @return ModelManagerInterface
*/
public function getModelManager()
{
return $this->modelManager;
}
/**
* @return string
*/
public function getClass()
{
return $this->class;
}
/**
* Initializes the choices and returns them.
*
* The choices are generated from the entities. If the entities have a
* composite identifier, the choices are indexed using ascending integers.
* Otherwise the identifiers are used as indices.
*
* If the entities were passed in the "choices" option, this method
* does not have any significant overhead. Otherwise, if a query builder
* was passed in the "query" option, this builder is now used to construct
* a query which is executed. In the last case, all entities for the
* underlying class are fetched from the repository.
*
* If the option "property" was passed, the property path in that option
* is used as option values. Otherwise this method tries to convert
* objects to strings using __toString().
*
* @param $choices
*
* @return array An array of choices
*/
protected function load($choices)
{
if (is_array($choices) && count($choices) > 0) {
$entities = $choices;
} elseif ($this->query) {
$entities = $this->modelManager->executeQuery($this->query);
} else {
$entities = $this->modelManager->findBy($this->class);
}
if (null === $entities) {
return [];
}
$choices = [];
$this->entities = [];
foreach ($entities as $key => $entity) {
if ($this->propertyPath) {
// If the property option was given, use it
$value = $this->propertyAccessor->getValue($entity, $this->propertyPath);
} else {
// Otherwise expect a __toString() method in the entity
try {
$value = (string) $entity;
} catch (\Exception $e) {
throw new RuntimeException(sprintf('Unable to convert the entity "%s" to string, provide '
.'"property" option or implement "__toString()" method in your entity.', ClassUtils::getClass($entity)), 0, $e);
}
}
if (count($this->identifier) > 1) {
// When the identifier consists of multiple field, use
// naturally ordered keys to refer to the choices
$choices[$key] = $value;
$this->entities[$key] = $entity;
} else {
// When the identifier is a single field, index choices by
// entity ID for performance reasons
$id = current($this->getIdentifierValues($entity));
$choices[$id] = $value;
$this->entities[$id] = $entity;
}
}
return $choices;
}
/**
* Returns the \ReflectionProperty instance for a property of the
* underlying class.
*
* @param string $property The name of the property
*
* @return \ReflectionProperty The reflection instance
*/
private function getReflProperty($property)
{
if (!isset($this->reflProperties[$property])) {
$this->reflProperties[$property] = new \ReflectionProperty($this->class, $property);
$this->reflProperties[$property]->setAccessible(true);
}
return $this->reflProperties[$property];
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\ChoiceList;
use Doctrine\Common\Util\ClassUtils;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Sonata\CoreBundle\Model\Adapter\AdapterInterface;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPath;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ModelChoiceLoader implements ChoiceLoaderInterface
{
public $identifier;
/**
* @var \Sonata\AdminBundle\Model\ModelManagerInterface
*/
private $modelManager;
/**
* @var string
*/
private $class;
private $property;
private $query;
private $choices;
/**
* @var PropertyPath
*/
private $propertyPath;
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
private $choiceList;
/**
* @param ModelManagerInterface $modelManager
* @param string $class
* @param null $property
* @param null $query
* @param array $choices
* @param PropertyAccessorInterface|null $propertyAccessor
*/
public function __construct(ModelManagerInterface $modelManager, $class, $property = null, $query = null, $choices = [], PropertyAccessorInterface $propertyAccessor = null)
{
$this->modelManager = $modelManager;
$this->class = $class;
$this->property = $property;
$this->query = $query;
$this->choices = $choices;
$this->identifier = $this->modelManager->getIdentifierFieldNames($this->class);
// The property option defines, which property (path) is used for
// displaying entities as strings
if ($property) {
$this->propertyPath = new PropertyPath($property);
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}
}
/**
* {@inheritdoc}
*/
public function loadChoiceList($value = null)
{
if (!$this->choiceList) {
if ($this->query) {
$entities = $this->modelManager->executeQuery($this->query);
} elseif (is_array($this->choices) && count($this->choices) > 0) {
$entities = $this->choices;
} else {
$entities = $this->modelManager->findBy($this->class);
}
$choices = [];
foreach ($entities as $key => $entity) {
if ($this->propertyPath) {
// If the property option was given, use it
$valueObject = $this->propertyAccessor->getValue($entity, $this->propertyPath);
} else {
// Otherwise expect a __toString() method in the entity
try {
$valueObject = (string) $entity;
} catch (\Exception $e) {
throw new RuntimeException(sprintf('Unable to convert the entity "%s" to string, provide "property" option or implement "__toString()" method in your entity.', ClassUtils::getClass($entity)), 0, $e);
}
}
$id = implode(AdapterInterface::ID_SEPARATOR, $this->getIdentifierValues($entity));
if (!array_key_exists($valueObject, $choices)) {
$choices[$valueObject] = [];
}
$choices[$valueObject][] = $id;
}
$finalChoices = [];
foreach ($choices as $valueObject => $idx) {
if (count($idx) > 1) { // avoid issue with identical values ...
foreach ($idx as $id) {
$finalChoices[sprintf('%s (id: %s)', $valueObject, $id)] = $id;
}
} else {
$finalChoices[$valueObject] = current($idx);
}
}
$this->choiceList = new ArrayChoiceList($finalChoices, $value);
}
return $this->choiceList;
}
/**
* {@inheritdoc}
*/
public function loadChoicesForValues(array $values, $value = null)
{
return $this->loadChoiceList($value)->getChoicesForValues($values);
}
/**
* {@inheritdoc}
*/
public function loadValuesForChoices(array $choices, $value = null)
{
return $this->loadChoiceList($value)->getValuesForChoices($choices);
}
/**
* @param object $entity
*
* @return array
*/
private function getIdentifierValues($entity)
{
try {
return $this->modelManager->getIdentifierValues($entity);
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf('Unable to retrieve the identifier values for entity %s', ClassUtils::getClass($entity)), 0, $e);
}
}
}