merged branch blogsh/dynamic_group_sequence (PR #3199)
Commits -------411a0cc
[Validator] Added GroupSequenceProvider to changelog815c769
[Validator] Renamed getValidationGroups to getGroupSequenced84a2e4
[Validator] Updated test expectations9f2310b
[Validator] Fixed typos, renamed hasGroupSequenceProvidere0d2828
[Validator] GroupSequenceProvider tests improved, configuration changedc3b04a3
[Validator] Changed GroupSequenceProvider implementation6c4455f
[Validator] Added GroupSequenceProvider Discussion ---------- [Validator] Added GroupSequenceProvider Bug fix: no Feature addition: yes Backwards compatibility break: no Symfony2 tests pass: ![](https://secure.travis-ci.org/blogsh/symfony.png?branch=dynamic_group_sequence) As discussed in #3114 I implemented the "GroupSequenceProvider" pattern for the validator component. It allows the user to select certain validation groups based on the current state of an object. Here is an example: /** * @Assert\GroupSequenceProvider("UserGroupSequnceProvider") */ class User { /** * @Assert\NotBlank(groups={"Premium"}) */ public function getAddress(); public function hasPremiumSubscription(); } class UserGroupSequenceProvider implements GroupSequenceProviderInterface { public function getValidationGroups($user) { if ($user->hasPremiumSubscription()) { return array('User', 'Premium'); } else { return array('User'); } } } With this patch there are two mechanisms to define the group sequence now. Either you can use @GroupSequence to define a static order of validation groups or you can use @GroupSequenceProvider to create dynamic validation group arrays. The ClassMetadata therefore has methods now which implement quite similar things. The question is whether it would make sense to interpret the static group sequence as a special case and create something like a DefaultGroupSequenceProvider or StaticGroupSequenceProvider which is assigned by default. This would cause a BC break inside the validator component. --------------------------------------------------------------------------- by bschussek at 2012-01-28T13:39:54Z I like the implementation, but I think we should differ a little bit from Java here. 1. `GroupSequenceProviderInterface` should be implemented by the domain classes themselves (`User`), not by a separate class. 2. As such, the parameter `$object` from `getValidationGroups($object)` can be removed 3. `ClassMetadata::setGroupSequenceProvider()` should accept a boolean to activate/deactivate this functionality. Also the check for the interface (does the underlying class implement it?) should be done here Apart from that, special cases need to be treated: * A definition of a group sequence and a group sequence provider in the same `ClassMetadata` should not be allowed. Either of them must not be set. * Metadata loaders must take care of settings made by parent classes. If `Animal` is extended by `Dog`, `Animal` defines a group sequence (or group sequence provider) and `Dog` a group sequence provider (or group sequence), only the setting of `Dog` should apply --------------------------------------------------------------------------- by blogsh at 2012-01-28T21:25:37Z Changes of the latest commit: - GroupSequenceProviderInterface has to be implemented by the domain class - The annotation/configuration options let the user define whether the provider is activated or not (is this neccessary at all?) - An error is thrown if the user wants to use static group sequences and the provider simultaneously At the moment neither the static group sequence nor the provider is inherited from parent classes or interfaces. I don't know if it would make sense to enable this feature. There could be problems if a user wants to define a static group sequence in the parent class and a sequence provider in the child class. --------------------------------------------------------------------------- by bschussek at 2012-01-30T13:07:04Z > There could be problems if a user wants to define a static group sequence in the parent class and a sequence provider in the child class. In this case, the setting in the child class should override the setting of the parent class. But we can leave this open for now. As it seems, [this issue is unresolved in Hibernate as well](https://hibernate.onjira.com/browse/HV-467). --------------------------------------------------------------------------- by blogsh at 2012-01-30T22:54:41Z Okay, finally I managed to upload the latest commit. If you got a bunch of notifications or so I'm sorry, but I had to revert some accidental changes in the commit :( I've rewritten the tests and have removed the "active" setting in the XML configuration. --------------------------------------------------------------------------- by blogsh at 2012-02-02T15:24:01Z Okay, typos are fixed now and `hasGroupSequenceProvider` has been renamed to `isGroupSequenceProvider`. I also had to adjust some tests after the rebase with master. --------------------------------------------------------------------------- by bschussek at 2012-02-03T09:25:19Z Looks good. @fabpot 👍 --------------------------------------------------------------------------- by fabpot at 2012-02-03T09:46:52Z Can you add a note in the CHANGELOG before I merge? Thanks. --------------------------------------------------------------------------- by blogsh at 2012-02-09T12:31:27Z @fabpot done
This commit is contained in:
commit
11e3516583
@ -305,6 +305,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
|
||||
* made ExecutionContext immutable
|
||||
* deprecated Constraint methods `setMessage`, `getMessageTemplate` and
|
||||
`getMessageParameters`
|
||||
* added support for dynamic group sequences with the GroupSequenceProvider pattern
|
||||
|
||||
### Yaml
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
<?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\Component\Validator\Constraints;
|
||||
|
||||
/**
|
||||
* Annotation to define a group sequence provider
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class GroupSequenceProvider
|
||||
{
|
||||
|
||||
}
|
@ -17,6 +17,7 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\Mapping\MemberMetadata;
|
||||
use Symfony\Component\Validator\GroupSequenceProviderInterface;
|
||||
|
||||
/**
|
||||
* Responsible for walking over and initializing validation on different
|
||||
@ -67,8 +68,13 @@ class GraphWalker
|
||||
$initializer->initialize($object);
|
||||
}
|
||||
|
||||
if ($group === Constraint::DEFAULT_GROUP && $metadata->hasGroupSequence()) {
|
||||
$groups = $metadata->getGroupSequence();
|
||||
if ($group === Constraint::DEFAULT_GROUP && ($metadata->hasGroupSequence() || $metadata->isGroupSequenceProvider())) {
|
||||
if ($metadata->hasGroupSequence()) {
|
||||
$groups = $metadata->getGroupSequence();
|
||||
} else {
|
||||
$groups = $object->getGroupSequence();
|
||||
}
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$this->walkObjectForGroup($metadata, $object, $group, $propertyPath, Constraint::DEFAULT_GROUP);
|
||||
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?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\Component\Validator;
|
||||
|
||||
/**
|
||||
* Defines the interface for a group sequence provider.
|
||||
*/
|
||||
interface GroupSequenceProviderInterface
|
||||
{
|
||||
/**
|
||||
* Returns which validation groups should be used for a certain state
|
||||
* of the object.
|
||||
*
|
||||
* @return array An array of validation groups
|
||||
*/
|
||||
function getGroupSequence();
|
||||
}
|
@ -29,6 +29,7 @@ class ClassMetadata extends ElementMetadata
|
||||
public $properties = array();
|
||||
public $getters = array();
|
||||
public $groupSequence = array();
|
||||
public $groupSequenceProvider = false;
|
||||
private $reflClass;
|
||||
|
||||
/**
|
||||
@ -57,6 +58,7 @@ class ClassMetadata extends ElementMetadata
|
||||
return array_merge(parent::__sleep(), array(
|
||||
'getters',
|
||||
'groupSequence',
|
||||
'groupSequenceProvider',
|
||||
'members',
|
||||
'name',
|
||||
'properties',
|
||||
@ -247,6 +249,10 @@ class ClassMetadata extends ElementMetadata
|
||||
*/
|
||||
public function setGroupSequence(array $groups)
|
||||
{
|
||||
if ($this->isGroupSequenceProvider()) {
|
||||
throw new GroupDefinitionException('Defining a static group sequence is not allowed with a group sequence provider');
|
||||
}
|
||||
|
||||
if (in_array(Constraint::DEFAULT_GROUP, $groups, true)) {
|
||||
throw new GroupDefinitionException(sprintf('The group "%s" is not allowed in group sequences', Constraint::DEFAULT_GROUP));
|
||||
}
|
||||
@ -293,4 +299,32 @@ class ClassMetadata extends ElementMetadata
|
||||
|
||||
return $this->reflClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a group sequence provider should be used
|
||||
*
|
||||
* @param boolean $active
|
||||
*/
|
||||
public function setGroupSequenceProvider($active)
|
||||
{
|
||||
if ($this->hasGroupSequence()) {
|
||||
throw new GroupDefinitionException('Defining a group sequence provider is not allowed with a static group sequence');
|
||||
}
|
||||
|
||||
if (!$this->getReflectionClass()->implementsInterface('Symfony\Component\Validator\GroupSequenceProviderInterface')) {
|
||||
throw new GroupDefinitionException(sprintf('Class "%s" must implement GroupSequenceProviderInterface', $this->name));
|
||||
}
|
||||
|
||||
$this->groupSequenceProvider = $active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the class is a group sequence provider.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isGroupSequenceProvider()
|
||||
{
|
||||
return $this->groupSequenceProvider;
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,9 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
|
||||
// Include constraints from all implemented interfaces
|
||||
foreach ($metadata->getReflectionClass()->getInterfaces() as $interface) {
|
||||
if ('Symfony\Component\Validator\GroupSequenceProviderInterface' === $interface->getName()) {
|
||||
continue;
|
||||
}
|
||||
$metadata->mergeConstraints($this->getClassMetadata($interface->getName()));
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ use Doctrine\Common\Annotations\Reader;
|
||||
use Symfony\Component\Validator\Exception\MappingException;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\Constraints\GroupSequence;
|
||||
use Symfony\Component\Validator\Constraints\GroupSequenceProvider;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
class AnnotationLoader implements LoaderInterface
|
||||
@ -38,6 +39,8 @@ class AnnotationLoader implements LoaderInterface
|
||||
foreach ($this->reader->getClassAnnotations($reflClass) as $constraint) {
|
||||
if ($constraint instanceof GroupSequence) {
|
||||
$metadata->setGroupSequence($constraint->groups);
|
||||
} elseif ($constraint instanceof GroupSequenceProvider) {
|
||||
$metadata->setGroupSequenceProvider(true);
|
||||
} elseif ($constraint instanceof Constraint) {
|
||||
$metadata->addConstraint($constraint);
|
||||
}
|
||||
|
@ -43,6 +43,10 @@ class XmlFileLoader extends FileLoader
|
||||
if (isset($this->classes[$metadata->getClassName()])) {
|
||||
$xml = $this->classes[$metadata->getClassName()];
|
||||
|
||||
foreach ($xml->{'group-sequence-provider'} as $provider) {
|
||||
$metadata->setGroupSequenceProvider(true);
|
||||
}
|
||||
|
||||
foreach ($this->parseConstraints($xml->constraint) as $constraint) {
|
||||
$metadata->addConstraint($constraint);
|
||||
}
|
||||
|
@ -54,6 +54,10 @@ class YamlFileLoader extends FileLoader
|
||||
if (isset($this->classes[$metadata->getClassName()])) {
|
||||
$yaml = $this->classes[$metadata->getClassName()];
|
||||
|
||||
if (isset($yaml['group_sequence_provider'])) {
|
||||
$metadata->setGroupSequenceProvider((bool)$yaml['group_sequence_provider']);
|
||||
}
|
||||
|
||||
if (isset($yaml['constraints'])) {
|
||||
foreach ($this->parseNodes($yaml['constraints']) as $constraint) {
|
||||
$metadata->addConstraint($constraint);
|
||||
|
@ -52,6 +52,7 @@
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="group-sequence-provider" type="group-sequence-provider" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="constraint" type="constraint" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="getter" type="getter" minOccurs="0" maxOccurs="unbounded" />
|
||||
@ -59,6 +60,14 @@
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="group-sequence-provider">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Defines the name of the group sequence provider for a class.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="property">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Component\Validator\Fixtures;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\GroupSequenceProviderInterface;
|
||||
|
||||
/**
|
||||
* @Assert\GroupSequenceProvider
|
||||
*/
|
||||
class GroupSequenceProviderEntity implements GroupSequenceProviderInterface
|
||||
{
|
||||
public $firstName;
|
||||
public $lastName;
|
||||
|
||||
protected $groups = array();
|
||||
|
||||
public function setGroups($groups)
|
||||
{
|
||||
$this->groups = $groups;
|
||||
}
|
||||
|
||||
public function getGroupSequence()
|
||||
{
|
||||
return $this->groups;
|
||||
}
|
||||
}
|
@ -14,10 +14,12 @@ namespace Symfony\Tests\Component\Validator\Mapping;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Constraints\Valid;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\Exception\GroupDefinitionException;
|
||||
use Symfony\Tests\Component\Validator\Fixtures\Entity;
|
||||
use Symfony\Tests\Component\Validator\Fixtures\ConstraintA;
|
||||
use Symfony\Tests\Component\Validator\Fixtures\ConstraintB;
|
||||
use Symfony\Tests\Component\Validator\Fixtures\PropertyConstraint;
|
||||
use Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProvider;
|
||||
|
||||
require_once __DIR__.'/../Fixtures/Entity.php';
|
||||
require_once __DIR__.'/../Fixtures/ConstraintA.php';
|
||||
@ -28,6 +30,7 @@ class ClassMetadataTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
const CLASSNAME = 'Symfony\Tests\Component\Validator\Fixtures\Entity';
|
||||
const PARENTCLASS = 'Symfony\Tests\Component\Validator\Fixtures\EntityParent';
|
||||
const PROVIDERCLASS = 'Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity';
|
||||
|
||||
protected $metadata;
|
||||
|
||||
@ -189,5 +192,40 @@ class ClassMetadataTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->metadata->setGroupSequence(array('Foo', $this->metadata->getDefaultGroup(), Constraint::DEFAULT_GROUP));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Validator\Exception\GroupDefinitionException
|
||||
*/
|
||||
public function testGroupSequenceFailsIfGroupSequenceProviderIsSet()
|
||||
{
|
||||
$metadata = new ClassMetadata(self::PROVIDERCLASS);
|
||||
$metadata->setGroupSequenceProvider(true);
|
||||
$metadata->setGroupSequence(array('GroupSequenceProviderEntity', 'Foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Validator\Exception\GroupDefinitionException
|
||||
*/
|
||||
public function testGroupSequenceProviderFailsIfGroupSequenceIsSet()
|
||||
{
|
||||
$metadata = new ClassMetadata(self::PROVIDERCLASS);
|
||||
$metadata->setGroupSequence(array('GroupSequenceProviderEntity', 'Foo'));
|
||||
$metadata->setGroupSequenceProvider(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Validator\Exception\GroupDefinitionException
|
||||
*/
|
||||
public function testGroupSequenceProviderFailsIfDomainClassIsInvalid()
|
||||
{
|
||||
$metadata = new ClassMetadata('stdClass');
|
||||
$metadata->setGroupSequenceProvider(true);
|
||||
}
|
||||
|
||||
public function testGroupSequenceProvider()
|
||||
{
|
||||
$metadata = new ClassMetadata(self::PROVIDERCLASS);
|
||||
$metadata->setGroupSequenceProvider(true);
|
||||
$this->assertTrue($metadata->isGroupSequenceProvider());
|
||||
}
|
||||
}
|
||||
|
@ -144,4 +144,17 @@ class AnnotationLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals($expected, $metadata);
|
||||
}
|
||||
|
||||
public function testLoadGroupSequenceProviderAnnotation()
|
||||
{
|
||||
$loader = new AnnotationLoader(new AnnotationReader());
|
||||
|
||||
$metadata = new ClassMetadata('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity');
|
||||
$loader->loadClassMetadata($metadata);
|
||||
|
||||
$expected = new ClassMetadata('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity');
|
||||
$expected->setGroupSequenceProvider(true);
|
||||
$expected->getReflectionClass();
|
||||
|
||||
$this->assertEquals($expected, $metadata);
|
||||
}
|
||||
}
|
||||
|
@ -70,4 +70,17 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertEquals($expected, $metadata);
|
||||
}
|
||||
|
||||
public function testLoadGroupSequenceProvider()
|
||||
{
|
||||
$loader = new XmlFileLoader(__DIR__.'/constraint-mapping.xml');
|
||||
$metadata = new ClassMetadata('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity');
|
||||
|
||||
$loader->loadClassMetadata($metadata);
|
||||
|
||||
$expected = new ClassMetadata('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity');
|
||||
$expected->setGroupSequenceProvider(true);
|
||||
|
||||
$this->assertEquals($expected, $metadata);
|
||||
}
|
||||
}
|
||||
|
@ -88,4 +88,17 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertEquals($expected, $metadata);
|
||||
}
|
||||
|
||||
public function testLoadGroupSequenceProvider()
|
||||
{
|
||||
$loader = new YamlFileLoader(__DIR__.'/constraint-mapping.yml');
|
||||
$metadata = new ClassMetadata('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity');
|
||||
|
||||
$loader->loadClassMetadata($metadata);
|
||||
|
||||
$expected = new ClassMetadata('Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity');
|
||||
$expected->setGroupSequenceProvider(true);
|
||||
|
||||
$this->assertEquals($expected, $metadata);
|
||||
}
|
||||
}
|
||||
|
@ -77,4 +77,11 @@
|
||||
<constraint name="NotNull" />
|
||||
</getter>
|
||||
</class>
|
||||
|
||||
<class name="Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity">
|
||||
|
||||
<!-- GROUP SEQUENCE PROVIDER -->
|
||||
<group-sequence-provider />
|
||||
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
@ -39,3 +39,6 @@ Symfony\Tests\Component\Validator\Fixtures\Entity:
|
||||
getters:
|
||||
lastName:
|
||||
- NotNull: ~
|
||||
|
||||
Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity:
|
||||
group_sequence_provider: true
|
||||
|
@ -17,6 +17,7 @@ require_once __DIR__.'/Fixtures/FailingConstraintValidator.php';
|
||||
require_once __DIR__.'/Fixtures/FakeClassMetadataFactory.php';
|
||||
|
||||
use Symfony\Tests\Component\Validator\Fixtures\Entity;
|
||||
use Symfony\Tests\Component\Validator\Fixtures\GroupSequenceProviderEntity;
|
||||
use Symfony\Tests\Component\Validator\Fixtures\FakeClassMetadataFactory;
|
||||
use Symfony\Tests\Component\Validator\Fixtures\FailingConstraint;
|
||||
use Symfony\Component\Validator\Validator;
|
||||
@ -122,6 +123,50 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals($violations, $result);
|
||||
}
|
||||
|
||||
public function testValidate_groupSequenceProvider()
|
||||
{
|
||||
$entity = new GroupSequenceProviderEntity();
|
||||
$metadata = new ClassMetadata(get_class($entity));
|
||||
$metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
|
||||
'groups' => 'First',
|
||||
)));
|
||||
$metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
|
||||
'groups' => 'Second',
|
||||
)));
|
||||
$metadata->setGroupSequenceProvider(true);
|
||||
$this->factory->addClassMetadata($metadata);
|
||||
|
||||
$violations = new ConstraintViolationList();
|
||||
$violations->add(new ConstraintViolation(
|
||||
'Failed',
|
||||
array(),
|
||||
$entity,
|
||||
'firstName',
|
||||
''
|
||||
));
|
||||
|
||||
$entity->setGroups(array('First'));
|
||||
$result = $this->validator->validate($entity);
|
||||
$this->assertEquals($violations, $result);
|
||||
|
||||
$violations = new ConstraintViolationList();
|
||||
$violations->add(new ConstraintViolation(
|
||||
'Failed',
|
||||
array(),
|
||||
$entity,
|
||||
'lastName',
|
||||
''
|
||||
));
|
||||
|
||||
$entity->setGroups(array('Second'));
|
||||
$result = $this->validator->validate($entity);
|
||||
$this->assertEquals($violations, $result);
|
||||
|
||||
$entity->setGroups(array());
|
||||
$result = $this->validator->validate($entity);
|
||||
$this->assertEquals(new ConstraintViolationList(), $result);
|
||||
}
|
||||
|
||||
public function testValidateProperty()
|
||||
{
|
||||
$entity = new Entity();
|
||||
|
Reference in New Issue
Block a user