How to validate unique entities in an entity collection in symfony2 - validation

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.

Related

how to avoid creating a slug that already exists as a Route "Symfony 4"

I have the entity Page identified by slug. Also I have the action to view a page in the Page controler :
class PageController extends AbstractController
{
/**
* #Route("/{slug}", name="fronend_page")
*/
public function show(Page $page)
{
return $this->render("font_end/page/show.html.twig", [
"page" => $page,
]);
}
}
I am looking for good practice to validate the slug ( check if exist in routes) before save it in the database without use prefixes
Example :
route exist : #route ("/blog")
check if blog exist before create slug : /{slug} = /blog
thanks
You can use the UniqueEntity annotation for checking slug for unique.
For example, in your entity add UniqueEntity annotation with the slug field.
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #UniqueEntity("slug")
*/
class Page
{
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank
*/
private $slug;
public function __construct(string $slug)
{
$this->slug = $slug;
}
}
Then you can create some service or do validation in your controller.
/**
* #Route("/{slug}")
*/
public function show($slug, ValidatorInterface $validator)
{
$page = new Page($slug);
$errors = $validator->validate($author);
if (count($errors) > 0) {
// handle errors
}
// save entity
}
Update:
For checking already existing routes probably you can do something like this
public function isRouteExist(string $slug): bool
{
/** #var Symfony\Component\Routing\RouterInterface $router */
$routes = array_filter(array_map(function (\Symfony\Component\Routing\Route $route) {
if (!$route->compile()->getVariables()) {
return $route->getPath();
}
return null;
}, $router->getRouteCollection()->all()));
return in_array(sprintf('/%s', $slug), $routes, true);
}
I created a validation Symfony :
class ContainsCheckSlugValidator extends ConstraintValidator
{
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof ContainsCheckSlug) {
throw new UnexpectedTypeException($constraint, ContainsCheckSlug::class);
}
// custom constraints should ignore null and empty values to allow
// other constraints (NotBlank, NotNull, etc.) take care of that
if (null === $value || "" === $value) {
return;
}
$routes = $this->router->getRouteCollection()->all();
$routes = array_map(function($route){
return $route->getPath();
},$routes);
if (in_array(sprintf("/{_locale}/%s/", $value), $routes, true)) {
$this->context->buildViolation($constraint->message)
->setParameter("{{ string }}", $value)
->addViolation();
}
}
}
Thanks #Ihor_Kostrov :)

Laravel Virgin: Setting up and destroying database in phpunit integration tests

Using the nilportuguess' eloquent repository library, I made the following (with bugs) repository:
namespace App\Repositories;
use NilPortugues\Foundation\Infrastructure\Model\Repository\Eloquent\EloquentRepository;
use App\Model\Rover;
class RoverRepository extends EloquentRepository
{
/**
* {#inheritdoc}
*/
protected function modelClassName()
{
return Rover::class;
}
/**
* {#inheritdoc}
*/
public function find(Identity $id, Fields $fields = null)
{
$eloquentModel = parent::find($id, $fields);
return $eloquentModel->toArray();
}
/**
* {#inheritdoc}
*/
public function findBy(Filter $filter = null, Sort $sort = null, Fields $fields = null)
{
$eloquentModelArray = parent::findBy($filter, $sort, $fields);
return $this->fromEloquentArray($eloquentModelArray);
}
/**
* {#inheritdoc}
*/
public function findAll(Pageable $pageable = null)
{
$page = parent::findAll($pageable);
return new Page(
$this->fromEloquentArray($page->content()),
$page->totalElements(),
$page->pageNumber(),
$page->totalPages(),
$page->sortings(),
$page->filters(),
$page->fields()
);
}
/**
* #param array $eloquentModelArray
* #return array
*/
protected function fromEloquentArray(array $eloquentModelArray) :array
{
$results = [];
foreach ($eloquentModelArray as $eloquentModel) {
//This is required to handle findAll returning array, not objects.
$eloquentModel = (object) $eloquentModel;
$results[] = $eloquentModel->attributesToArray();
}
return $results;
}
}
And In order to locate them I thought to make an Integration test on an sqlite inmemory db:
namespace Test\Database\Integration\Repositories;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Repositories\RoverRepository;
use App\Model\Rover;
use App\Model\Grid;
class RoverRepositoryTest extends TestCase
{
use RefreshDatabase;
private $repository=null;
public function setUp(): void
{
parent::setUp();
$grid=factory(Grid::class)->create([
'width'=>5,
'height'=>5
]);
$rover=factory(Rover::class, 5)->create([
'grid_id' => $grid->id,
'grid_pos_x' => rand(0, $grid->width),
'grid_pos_y' => rand(0, $grid->height),
]);
//How do I run Migrations and generate the db?
$this->repository = new RoverRepository();
}
public function tearDown(): void
{
parent::tearDown();
//How I truncate and destroy Database?
}
/**
* Testing Base Search
*
* #return void
*/
public function testBasicSearch(): void
{
//Some Db test
}
}
But I have some questions:
How do I save the generated via factory Models?
How do I nuke my database in tearDown()?

Extra data on a collection operation

Does anybody know how to add extra data on a collection?
The doc says much about how to add extra data on an item which translates into decorating the ItemNormalizer service, and it works pretty well.
But I’m struggling in finding out which normalizer to decorate when it comes to add some data on a collection of entities. The extra data could be anything: the current user logged in, a detailed pager, some debug parameters, ... that are not related to a specific entity, but rather on the request itself.
The only working solution for now is to hook on a Kernel event but that's definitely not the code I like to write:
use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class SerializeListener implements EventSubscriberInterface
{
/**
* #var Security
*/
private $security;
/**
* #var NormalizerInterface
*/
private $normalizer;
public function __construct(
Security $security,
NormalizerInterface $normalizer
) {
$this->security = $security;
$this->normalizer = $normalizer;
}
public function addCurrentUser(GetResponseForControllerResultEvent $event)
{
$request = $event->getRequest();
if ($request->attributes->has('_api_respond')) {
$serialized = $event->getControllerResult();
$data = json_decode($serialized, true);
$data['hydra:user'] = $this->normalizer->normalize(
$this->security->getUser(),
$request->attributes->get('_format'),
$request->attributes->get('_api_normalization_context')
);
$event->setControllerResult(json_encode($data));
}
}
/**
* #inheritDoc
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => [
'addCurrentUser',
EventPriorities::POST_SERIALIZE,
],
];
}
}
Any ideas?
Thank you,
Ben
Alright, I finally managed to do this.
namespace App\Api;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class ApiCollectionNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
/**
* #var NormalizerInterface|NormalizerAwareInterface
*/
private $decorated;
public function __construct(NormalizerInterface $decorated)
{
if (!$decorated instanceof NormalizerAwareInterface) {
throw new \InvalidArgumentException(
sprintf('The decorated normalizer must implement the %s.', NormalizerAwareInterface::class)
);
}
$this->decorated = $decorated;
}
/**
* #inheritdoc
*/
public function normalize($object, $format = null, array $context = [])
{
$data = $this->decorated->normalize($object, $format, $context);
if ('collection' === $context['operation_type'] && 'get' === $context['collection_operation_name']) {
$data['hydra:meta'] = ['foo' => 'bar'];
}
return $data;
}
/**
* #inheritdoc
*/
public function supportsNormalization($data, $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
/**
* #inheritdoc
*/
public function setNormalizer(NormalizerInterface $normalizer)
{
$this->decorated->setNormalizer($normalizer);
}
}
# config/services.yaml
services:
App\Api\ApiCollectionNormalizer:
decorates: 'api_platform.hydra.normalizer.collection'
arguments: [ '#App\Api\ApiCollectionNormalizer.inner' ]
Keep it for the records :)

Laravel - Query scopes across models

In a nutshell, I want to create a function that my query scopes can use across multiple models:
public function scopeNormaliseCurrency($query,$targetCurrency) {
return $query->normaliseCurrencyFields(
['cost_per_day','cost_per_week'],
$targetCurrency
);
}
I have got my logic working within this scope function no problem, but I want to make this code available to all my models, as there are multiple currency fields in different tables and I don't want to be replicating the code in each query scope - only specify the columns that need attention.
So, where would I make my function normaliseCurrencyFields? I have extended the Model class as well as used the newCollection keyword to extend Collection but both result in Call to undefined method Illuminate\Database\Query\Builder::normaliseCurrencyFields() errors.
I have looked into Global Scoping but this seems to be localised to a Model.
Am I along the right lines? Should I be targeting Eloquent specifically?
Create an abstract base model that extends eloquent then extend it with the classes you want to have access to it. I do this for searching functions, uuid creation, and class code functions. So that all of my saved models are required to have to certain attributes and access to my searching functions. For instance I created a static search function getobjectbyid(). So that when extended I can call it like so:
$user = User::getobjectbyid('habwiifnbrklsnbbd1938');
Thus way I know I am getting a user object back.
My base model:
<?php
/**
* Created by PhpStorm.
* User: amac
* Date: 6/5/17
* Time: 12:45 AM
*/
namespace App;
use Illuminate\Database\Eloquent\Model as Eloquent;
abstract class Model extends Eloquent
{
protected $guarded = [
'class_code',
'id'
];
public $primaryKey = 'id';
public $incrementing = false;
public function __construct($attributes = array()) {
parent::__construct($attributes); // Eloquent
$this->class_code = \App\Enums\EnumClassCode::getValueByKey(get_class($this));
$this->id = $this->class_code . uniqid();
return $this;
}
public static function getObjectById($id){
$class = get_called_class();
$results = $class::find($id);
return $results;
}
public static function getAllObjects(){
$class = get_called_class();
return $class::all();
}
my user model:
<?php
namespace App;
use Mockery\Exception;
use Illuminate\Support\Facades\Hash;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use App\Model as Model;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'contact', 'username', 'email_address'
];
/**
* The column name of the "remember me" token.
*
* #var string
*/
protected $rememberTokenName = 'remember_token';
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'remember_token', 'active'
];
/**
* the attributes that should be guarded from Mass Assignment
*
* #var array
*/
protected $guarded = [
'created_at', 'updated_at', 'password_hash'
];
/**
* Define table to be used with this model. It defaults and assumes table names will have an s added to the end.
*for instance App\User table by default would be users
*/
protected $table = "user";
/**
* We have a non incrementing primary key
*
* #var bool
*/
public $incrementing = false;
/**
* relationships
*/
public function contact(){
// return $this->hasOne(Contact::class, 'id', 'contact_id');
return $this->hasOne(Contact::class);
}
public function customers(){
// return $this->hasOne(Contact::class, 'id', 'contact_id');
return $this->hasMany(Customer::class);
}
/**
* User constructor.
* #param array $attributes
*/
public function __construct($attributes = array()) {
parent::__construct($attributes); // Eloquent
// Your construct code.
$this->active = 1;
return $this;
}
/**
* #param $password string
* set user password_hash
* #return $this
*/
public function setPassword($password){
// TODO Password Validation
try{
$this->isActive();
$this->password_hash = Hash::make($password);
$this->save();
} catch(\Exception $e) {
dump($e->getMessage());
}
return $this;
}
/**
* Returns whether or not this use is active.
*
* #return bool
*/
public function isActive(){
if($this->active) {
return true;
} else {
Throw new Exception('This user is not active. Therefore you cannot change the password', 409);
}
}
public function getEmailUsername(){
$contact = Contact::getObjectById($this->contact_id);
$email = Email::getObjectById($contact->email_id);
return $email->username_prefix;
}
/**
* #return string
*
* getFullName
* returns concatenated first and last name of user.
*/
public function getFullName(){
return $this->first_name . ' ' . $this->last_name;
}
/**
* Get the name of the unique identifier for the user.
*
* #return string
*/
public function getAuthIdentifierName(){
return $this->getKeyName();
}
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier(){
return $this->{$this->getAuthIdentifierName()};
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword(){
return $this->password_hash;
}
/**
* Get the token value for the "remember me" session.
*
* #return string
*/
public function getRememberToken(){
if (! empty($this->getRememberTokenName())) {
return $this->{$this->getRememberTokenName()};
}
}
/**
* Set the token value for the "remember me" session.
*
* #param string $value
* #return void
*/
public function setRememberToken($value){
if (! empty($this->getRememberTokenName())) {
$this->{$this->getRememberTokenName()} = $value;
}
}
/**
* Get the column name for the "remember me" token.
*
* #return string
*/
public function getRememberTokenName(){
return $this->rememberTokenName;
}
/**
* Get the e-mail address where password reset links are sent.
*
* #return string
*/
public function getEmailForPasswordReset(){
}
/**
* Send the password reset notification.
*
* #param string $token
* #return void
*/
public function sendPasswordResetNotification($token){
}
public function validateAddress(){
}
}
a TestController:
public function test(){
$user = User::getObjectById('USR594079ca59746');
$customers = array();
foreach ($user->customers as $customer){
$contact = Contact::getObjectById($customer->contact_id);
$name = PersonName::getObjectById($contact->personname_id);
$c = new \stdClass();
$c->id = $customer->id;
$c->name = $name->preferred_name;
$customers[] = $c;
}
$response = response()->json($customers);
return $response;
}
Take note on how getObjectById is extended and available to my other classes that extend my base model. Also I do not have to specify in my user model an 'id' or 'class_code' and when my user model is constructed it calls the parent constructor which is the constructor on my base model that handles 'id' and 'class_code'.

Can't make a new Insertion - Laravel Eloquent ORM

I can't Insert into this table and this drives me crazy
This is the error Msg I get
var_export does not handle circular references
open: /var/www/frameworks/Scout/vendor/laravel/framework/src/Illuminate/Database/Connection.php
* #param Exception $e
* #param string $query
* #param array $bindings
* #return void
*/
protected function handleQueryException(\Exception $e, $query, $bindings)
{
$bindings = var_export($bindings, true);
$message = $e->getMessage()." (SQL: {$query}) (Bindings: {$bindings})";
Here is my Full Mode
<?php
namespace Models;
use Illuminate\Database\Eloquent\Collection;
class Student extends \Eloquent
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'students';
/**
* The rules used to validate new Entry.
*
* #var array
*/
protected $newValidationRules = array(
'studentCode' => 'unique:students,code|numeric|required',
'studentName' => 'required|min:2',
'dateOfBirth' => 'date',
'mobile' => 'numeric'
);
/**
* Relation with sessions (Many To Many Relation)
* We added with Created_at to the Pivot table as it indicates the attendance time
*/
public function sessions()
{
return $this->belongsToMany('Models\Session', 'student_session')->withPivot('created_at')->orderBy('created_at', 'ASC');
}
/**
* Get Student Subjects depending on attendance,
*/
public function subjects()
{
$sessions = $this->sessions()->groupBy('subject_id')->get();
$subjects = new Collection();
foreach ($sessions as $session) {
$subject = $session->subject;
$subject->setRelation('student', $this);
$subjects->add($subject);
}
return $subjects;
}
/**
* Insert New Subject
* #return Boolean
*/
public function insertNew()
{
$this->validator = \Validator::make(\Input::all(), $this->newValidationRules);
if ($this->validator->passes()) {
$this->name = \Input::get('studentName');
$this->code = \Input::get('studentCode');
if ($this->save()) {
return \Response::make("You have registered the subject successfully !");
} else {
return \Response::make('An Error happened ');
}
} else {
Return $this->validator->messages()->first();
}
}
}
I am just trying to insert a new row with three Columns (I call the insertNew function on instance of Student)
1- ID automatically incremented
2- Special Code
3- Name
And I got this above Msg
What's I have tried till now :
removing all relations between from this model and other models
that has this one in the relation
Removed the validation step in insertNew()
Removed the all Input class calls and used literal data instead.
note that I use similar Inserting function on other Models and it works flawlessly
Any Comments , Replies are appreciated :D
Solution
I solved it and the problem was that I am accessing the validator
$this->validator = \Validator::make(\Input::all(), $this->newValidationRules);
And it was because I forgot that
/**
* The validator object.
*
* #var Illuminate\Validation\Validator
*/
protected $validator;
I had a similar problem. But to me, changing this code:
if ($this->validator->passes()) {
$this->name = \Input::get('studentName');
$this->code = \Input::get('studentCode');"
to this:
if ($this->validator->passes()) {
$this->setAttribute ("name" , \Input::get('studentName'));
$this->setAttribute ("code" , \Input::get('studentCode'));"
solved it.

Resources