For some reason my relationship is fetching all from the corresponding table when I dump it, however dumping the result does not show these rows.
The slider ID does not match the slider_id within the settings table.
So the following works fine, as expected the settings is an empty array:
/**
* #return HasOne
*/
public function slider(): HasOne
{
return $this->hasOne(Slider::class)->withDefault(
(new Slider())->attributesToArray()
);
}
Result:
{
"name": "media-slider",
"settings": []
}
However when I dump within the attribute I get all the rows from the settings table, when this query should be getting all settings where the slider_id matches the current slider, which has a different ID.
<?php
namespace App\Models\Media;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
class Slider extends Model
{
/** #var string[] */
protected $appends = [ 'settings' ];
protected $defaults = [
'test' => [
'id' => 0,
'name' => 'default name',
]
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->attributes = $this->defaults['test'];
}
/**
* #return HasMany
*/
public function settings(): HasMany
{
return $this->hasMany(SliderSetting::class);
}
/**
* Get the slider settings, extract the value and key by the key, also
* group if multiple setting groups are required.
*
* Perform this logic here so data can be used directly by the JavaScript.
*
* #return \Illuminate\Database\Eloquent\Collection|Collection
*/
public function getSettingsAttribute()
{
dd($this->settings()->get()); // This should be empty!
return $this->settings()->get()
->groupBy('group')
->map(static function ($group) {
$group = $group->keyBy('key');
return $group->map(static function ($setting) {
return $setting->getAttribute('value');
});
});
}
}
Edit
/**
* #return HasMany
*/
public function sliderSettings(): HasMany
{
dd($this->hasMany(SliderSetting::class)->toSql());
return $this->hasMany(SliderSetting::class);
}
The above outputs:
select * from slider_settings
Shouldn't it be the following?
select * from slider_settings where slider_settings.slider_id = ?
i want make field name be written in a quadrilateral
same
"andy hosam rami entaida"
or
"حسام احممد محمد متولى"
i tray
'name' => 'regex:/^[\wء-ي]+\s[\wء-ي]+\s[\wء-ي]+\s[\wء-ي]+/
in english all true put in arabic is false
regex is true i test it hear regexr.com/57s61
i can do with php in another way , so how can write in laravel ?
if(count(explode(' ',$name)) < 4)
{
$error[] ='enter full name with 4 words';
}
You can make a custom Rule class to do custom validation, in an encapsulated manner.
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class NumWords implements Rule
{
private $attribute;
private $expected;
public function __construct(int $expected)
{
$this->expected = $expected;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
$this->attribute = $attribute;
$trimmed = trim($value);
$numWords = count(explode(' ', $trimmed));
return $numWords === $this->expected;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'The '.$this->attribute.' field must have exactly '.$this->expected.' words';
}
}
Then you can use it anywhere in validation, as below:
public function rules()
{
return [
'name' => [ 'required', 'string', new NumWords(4)],
];
}
I have custom model User and I want push custom attribute to model after fetching data. How I can do it?
I know what before fetching we can add custom attibute like this:
class User extends Model
{
protected $appends = ['callable'];
public function getCallableAttribute()
{
$callable = $this->getMeta('phone') ? true : false;
return $callable;
}
}
But how do it after fetching data, for example after:
$user = User::find(1);
And now how append custom attribute to fetched User object data?
You can define a Laravel Accessor for that
E.g.
class User extends Model
{
/**
* Get the user's first name.
*
* #param string $value
* #return string
*/
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
/**
* Get the user's full name.
*
* #return string
*/
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}
Then you can access Accessors like this:
$user = App\User::find(1);
$firstName = $user->first_name; // In studly Case
$firstName = $user->full_name; // In studly Case
You can read more about Laravel Accessors here: https://laravel.com/docs/5.8/eloquent-mutators#defining-an-accessor
Hi need some help with uniting testing a Symfony 2.8 callback.
I don't think I have set it up correctly as the test is passing when I know it should be failing
The entity setup:
The validate callback in the Contact entity:
/**
* Validation callback for contact
* #param \AppBundle\Entity\Contact $object
* #param ExecutionContextInterface $context
*/
public static function validate(Contact $object, ExecutionContextInterface $context)
{
/**
* Check if the country code is valid
*/
if ($object->isValidCountryCode() === false) {
$context->buildViolation('Cannot register in that country')
->atPath('country')
->addViolation();
}
}
The method isValidCountryCode in Contact entity:
/**
* Get a list of invalid country codes
* #return array Collection of invalid country codes
*/
public function getInvalidCountryCodes()
{
return array('IS');
}
The method that checks if the country code is invalid:
/**
* Check if the country code is valid
* #return boolean
*/
public function isValidCountryCode()
{
$invalidCountryCodes = $this->getInvalidCountryCodes();
if (in_array($this->getCountry()->getCode(), $invalidCountryCodes)) {
return false;
}
return true;
}
The validation.yml
AppBundle\Entity\Contact:
properties:
//...
country:
//..
- Callback:
callback: [ AppBundle\Entity\Contact, validate ]
groups: [ "AppBundle" ]
The test class:
//..
use Symfony\Component\Validator\Validation;
class CountryTest extends WebTestCase
{
//...
public function testValidate()
{
$country = new Country();
$country->setCode('IS');
$contact = new Contact();
$contact->setCountry($country);
$validator = Validation::createValidatorBuilder()->getValidator();
$errors = $validator->validate($contact);
$this->assertEquals(1, count($errors));
}
This test returns $errors with a count of 0 but it should be 1 as the country code 'IS' is invalid.
First problem is about the definition of the constraint in the yml files: you need to put the callback under the constraint section instead of properties, so change the validation.yml files as follow:
validation.yml
AppBundle\Entity\Contact:
constraints:
- Callback:
callback: [ AppBundle\Entity\Contact, validate ]
groups: [ "AppBundle" ]
Second in the testCase: you need to take the validator service from the container instead of create a new one with the builder: this object is not initializated with the object structure ect.
Third The callback constraints is defined for the AppBundle validation group only, so pass the validation group to the validator service (As third argument of the service).
So change the testClass as follow:
public function testValidate()
{
$country = new Country();
$country->setCode('IS');
$contact = new Contact();
$contact->setCountry($country);
// $validator = Validation::createValidatorBuilder()->getValidator();
$validator = $this->createClient()->getContainer()->get('validator');
$errors = $validator->validate($contact, null, ['AppBundle']);
$this->assertEquals(1, count($errors));
}
And the testcase became green.
Hope this help
I have an entity with a OneToMany relation to another entity, when I persist the parent entity I want to ensure the children contain no duplicates.
Here's the classes I have been using, the discounts collection should not contain two products with the same name for a given client.
I have a Client entity with a collection of discounts:
/**
* #ORM\Entity
*/
class Client {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=128, nullable="true")
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity="Discount", mappedBy="client", cascade={"persist"}, orphanRemoval="true")
*/
protected $discounts;
}
/**
* #ORM\Entity
* #UniqueEntity(fields={"product", "client"}, message="You can't create two discounts for the same product")
*/
class Discount {
/**
* #ORM\Id
* #ORM\Column(type="string", length=128, nullable="true")
*/
protected $product;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Client", inversedBy="discounts")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
protected $client;
/**
* #ORM\Column(type="decimal", scale=2)
*/
protected $percent;
}
I tried using UniqueEntity for the Discount class as you can see, the problem is that it seems the validator only checks what's loaded on the database (which is empty), so when the entities are persisted I get a "SQLSTATE[23000]: Integrity constraint violation".
I have checked the Collection constraint buy it seems to handle only collections of fields, not entities.
There's also the All validator, which lets you define constraints to be applied for each entity, but not to the collection as a whole.
I need to know if there are entity collection constraints as a whole before persisting to the database, other than writing a custom validator or writing a Callback validator each time.
I've created a custom constraint/validator for this.
It validates a form collection using the "All" assertion, and takes an optional parameter : the property path of the property to check the entity equality.
(it's for Symfony 2.1, to adapt it to Symfony 2.0 check the end of the answer) :
For more information on creating custom validation constraints, check The Cookbook
The constraint :
#src/Acme/DemoBundle/Validator/constraint/UniqueInCollection.php
<?php
namespace Acme\DemoBundle\Validator\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class UniqueInCollection extends Constraint
{
public $message = 'The error message (with %parameters%)';
// The property path used to check wether objects are equal
// If none is specified, it will check that objects are equal
public $propertyPath = null;
}
And the validator :
#src/Acme/DemoBundle/Validator/constraint/UniqueInCollectionValidator.php
<?php
namespace Acme\DemoBundle\Validator\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Form\Util\PropertyPath;
class UniqueInCollectionValidator extends ConstraintValidator
{
// We keep an array with the previously checked values of the collection
private $collectionValues = array();
// validate is new in Symfony 2.1, in Symfony 2.0 use "isValid" (see below)
public function validate($value, Constraint $constraint)
{
// Apply the property path if specified
if($constraint->propertyPath){
$propertyPath = new PropertyPath($constraint->propertyPath);
$value = $propertyPath->getValue($value);
}
// Check that the value is not in the array
if(in_array($value, $this->collectionValues))
$this->context->addViolation($constraint->message, array());
// Add the value in the array for next items validation
$this->collectionValues[] = $value;
}
}
In your case, you would use it like this :
use Acme\DemoBundle\Validator\Constraints as AcmeAssert;
// ...
/**
* #ORM\OneToMany(targetEntity="Discount", mappedBy="client", cascade={"persist"}, orphanRemoval="true")
* #Assert\All(constraints={
* #AcmeAssert\UniqueInCollection(propertyPath ="product")
* })
*/
For Symfony 2.0, change the validate function by :
public function isValid($value, Constraint $constraint)
{
$valid = true;
if($constraint->propertyPath){
$propertyPath = new PropertyPath($constraint->propertyPath);
$value = $propertyPath->getValue($value);
}
if(in_array($value, $this->collectionValues)){
$valid = false;
$this->setMessage($constraint->message, array('%string%' => $value));
}
$this->collectionValues[] = $value;
return $valid
}
Here is a version working with multiple fields just like UniqueEntity does. Validation fails if multiple objects have same values.
Usage:
/**
* ....
* #App\UniqueInCollection(fields={"name", "email"})
*/
private $contacts;
//Validation fails if multiple contacts have same name AND email
The constraint class ...
<?php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class UniqueInCollection extends Constraint
{
public $message = 'Entry is duplicated.';
public $fields;
public function validatedBy()
{
return UniqueInCollectionValidator::class;
}
}
The validator itself ....
<?php
namespace App\Validator\Constraints;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
class UniqueInCollectionValidator extends ConstraintValidator
{
/**
* #var \Symfony\Component\PropertyAccess\PropertyAccessor
*/
private $propertyAccessor;
public function __construct()
{
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
/**
* #param mixed $collection
* #param Constraint $constraint
* #throws \Exception
*/
public function validate($collection, Constraint $constraint)
{
if (!$constraint instanceof UniqueInCollection) {
throw new UnexpectedTypeException($constraint, UniqueInCollection::class);
}
if (null === $collection) {
return;
}
if (!\is_array($collection) && !$collection instanceof \IteratorAggregate) {
throw new UnexpectedValueException($collection, 'array|IteratorAggregate');
}
if ($constraint->fields === null) {
throw new \Exception('Option propertyPath can not be null');
}
if(is_array($constraint->fields)) $fields = $constraint->fields;
else $fields = [$constraint->fields];
$propertyValues = [];
foreach ($collection as $key => $element) {
$propertyValue = [];
foreach ($fields as $field) {
$propertyValue[] = $this->propertyAccessor->getValue($element, $field);
}
if (in_array($propertyValue, $propertyValues, true)) {
$this->context->buildViolation($constraint->message)
->atPath(sprintf('[%s]', $key))
->addViolation();
}
$propertyValues[] = $propertyValue;
}
}
}
For Symfony 4.3(only tested version) you can use my custom validator.
Prefered way of usage is as annotaion on validated collection:
use App\Validator\Constraints as App;
...
/**
* #ORM\OneToMany
*
* #App\UniqueProperty(
* propertyPath="entityProperty"
* )
*/
private $entities;
Difference between Julien and my solution is, that my Constraint is defined on validated Collection instead on element of Collection itself.
Constraint:
#src/Validator/Constraints/UniqueProperty.php
<?php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class UniqueProperty extends Constraint
{
public $message = 'This collection should contain only elements with uniqe value.';
public $propertyPath;
public function validatedBy()
{
return UniquePropertyValidator::class;
}
}
Validator:
#src/Validator/Constraints/UniquePropertyValidator.php
<?php
namespace App\Validator\Constraints;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
class UniquePropertyValidator extends ConstraintValidator
{
/**
* #var \Symfony\Component\PropertyAccess\PropertyAccessor
*/
private $propertyAccessor;
public function __construct()
{
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
/**
* #param mixed $value
* #param Constraint $constraint
* #throws \Exception
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof UniqueProperty) {
throw new UnexpectedTypeException($constraint, UniqueProperty::class);
}
if (null === $value) {
return;
}
if (!\is_array($value) && !$value instanceof \IteratorAggregate) {
throw new UnexpectedValueException($value, 'array|IteratorAggregate');
}
if ($constraint->propertyPath === null) {
throw new \Exception('Option propertyPath can not be null');
}
$propertyValues = [];
foreach ($value as $key => $element) {
$propertyValue = $this->propertyAccessor->getValue($element, $constraint->propertyPath);
if (in_array($propertyValue, $propertyValues, true)) {
$this->context->buildViolation($constraint->message)
->atPath(sprintf('[%s]', $key))
->addViolation();
}
$propertyValues[] = $propertyValue;
}
}
}
I can't manage to make the previous answer works on symfony 2.6. Because of the following code on l. 852 of RecursiveContextualValidator, it only goes once on the validate method when 2 items are equals.
if ($context->isConstraintValidated($cacheKey, $constraintHash)) {
continue;
}
So, here is what I've done to deals with the original issue :
On the Entity :
* #AcmeAssert\UniqueInCollection(propertyPath ="product")
Instead of
* #Assert\All(constraints={
* #AcmeAssert\UniqueInCollection(propertyPath ="product")
* })
On the validator :
public function validate($collection, Constraint $constraint){
$propertyAccessor = PropertyAccess::getPropertyAccessor();
$previousValues = array();
foreach($collection as $collectionItem){
$value = $propertyAccessor->getValue($collectionItem, $constraint->propertyPath);
$previousSimilarValuesNumber = count(array_keys($previousValues,$value));
if($previousSimilarValuesNumber == 1){
$this->context->addViolation($constraint->message, array('%email%' => $value));
}
$previousValues[] = $value;
}
}
Instead of :
public function isValid($value, Constraint $constraint)
{
$valid = true;
if($constraint->propertyPath){
$propertyAccessor = PropertyAccess::getPropertyAccessor();
$value = $propertyAccessor->getValue($value, $constraint->propertyPath);
}
if(in_array($value, $this->collectionValues)){
$valid = false;
$this->setMessage($constraint->message, array('%string%' => $value));
}
$this->collectionValues[] = $value;
return $valid
}
Can be used Unique built-in validator for Symfony >= 6.1
The fields option was introduced in Symfony 6.1.