Symfony entity validation verify foreign key exists - symfony

I would like to be able to use entity validator constraints to verify if the foreign key book_id is valid, please see below:
Book.php
/**
* Book
*
* #ORM\Table("book")
* #ORM\Entity
* #ORM\Entity(repositoryClass="AppBundle\Repository\BookRepository")
*/
class Book
{
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #ORM\Column(name="name", type="string")
* #Assert\Length(
* max = 250,
* maxMessage = "Name cannot be longer than {{ limit }} characters",
* groups={"create","update"}
* )
*/
private $name;
/**
* #ORM\OneToOne(targetEntity="Loan", mappedBy="book", fetch="LAZY")
*/
protected $loan;
}
Loan.php
/**
* Loan
*
* #ORM\Table("loan")
* #ORM\Entity
* #ORM\Entity(repositoryClass="AppBundle\Repository\LoanRepository")
*/
class Loan
{
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
* #ORM\Column(name="book_id", type="integer")
*/
protected $book_id;
/**
* #var string
* #ORM\Column(name="name", type="string")
* #Assert\Length(
* max = 500,
* maxMessage = "Person cannot be longer than {{ limit }} characters",
* groups={"create","update"}
* )
*/
private $person;
/**
* #ORM\OneToOne(targetEntity="Book", inversedBy="loan")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
*/
protected $book;
}
Here is how I am currently validating the loan entity
$loan = new Loan();
$loan->setPerson($person);
$loan->setBookId($id);
/** #var ConstraintViolation $error */
foreach ($this->get('validator')->validate($loan,null,['create'])->getIterator() as $index => $error) {
$errorMessages[] = $error->getMessage();
}
I figured maybe I can add a custom validator like this to the loan entity:
/**
* #Assert\IsTrue(message = "The book does not exist")
* #return bool
*/
public function isBookLegal(BookRepository $bookRepository)
{
return !$bookRepository->fetchById($this->book_id);
}
But I end up with the follow exception:
Type error: Too few arguments to function
AppBundle\Entity\Loan::isBookLegal(), 0 passed and exactly 1 expected

First of all, you should not have both $book_id and $book in your Loan entity. You should remove $book_id, which is enough for your entity relationship.
Then, all you need to do is add an #Assert\NotBlank() on $book:
use Symfony\Component\Validator\Constraints as Assert;
...
/**
* #ORM\OneToOne(targetEntity="Book", inversedBy="loan")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
* #Assert\NotBlank()
*/
protected $book;
I'm not sure what code you are using to get all of your loans, but as Edwin states that's not really good form. You want want something more like:
foreach ($loans as $loan) {
$errors = $this->get('validator')->validate($loan);
// do something here if there is an error
}
The assertion function you wrote isn't going to work because you can't pass in a value to your isBookLegal() function there, nor can you ever use the database connection/repository from within your Entity class.
I'm not really sure what you are trying to accomplish without greater context here. Doctrine is already going to validate your $book member because of your #ORM\OneToOne annotation in the first place. You don't have to perform any additional validation. I'm guessing you are trying to pass in a value directly to $book_id, which is incorrect. You should only be passing already-valid $book entities to your Loan class, via $loan->setBook(Book $book);

Related

Embeed forms - inserting to database

I created two entities automatically ( using this manual http://symfony.com/doc/2.8/doctrine/reverse_engineering.html) based on ER model generated in Workbench. My intention was to create one-to-one relationship but annotation show it is one-to-many relationship. I created also embeed forms. I would like to insert client and new adress to database. I still get an error:
A new entity was found through the relationship 'UlaBundle\Entity\Client#adres' that was not configured to cascade persist operations for entity: qqq. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}).
Error is shown even if i set #ManyToOne(..,cascade={"persist"}) and __toString function. What is the problem? Please help. Below my code:
///Client Entity
class Client
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=45, nullable=true)
*/
private $name;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \UlaBundle\Entity\Adres
*
* #ORM\ManyToOne(targetEntity="UlaBundle\Entity\Adres", cascade= {"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="adres_id", referencedColumnName="id")
* })
*/
private $adres;
/// Adres Entity
class Adres
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=45, nullable=true)
*/
private $name;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
///Controller
/**
* #Route("/client", name="client")
*/
public function clientAction(Request $request) {
$c = new Client();
$form = $this->createForm(ClientType::class,$c);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$m = $this->getDoctrine()->getManager();
$m->persist($c);
$m->flush();
return new Response('Added');
}
return $this->render('UlaBundle:Default:client_form.html.twig', array('form' => $form->createView()));
}
I think your problem come from the blank space in cascade= {"persist"}, you should remove it
/**
* #var \UlaBundle\Entity\Adres
*
* #ORM\ManyToOne(targetEntity="UlaBundle\Entity\Adres", cascade={"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="adres_id", referencedColumnName="id")
* })
*/
private $adres;

Doctrine2 relation for non Id column

I'm building a simple web-service using Symfony 3, Doctrine 2.5 and stuck at ORM relations described below in simplified structure.
I have an Action entity containing many actions with ManyToOne relation...
class Action
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\Status")
* #ORM\JoinColumn(referencedColumnName="code", nullable=false)
*/
private $status;
and the Status Entity with a few statuses.
class Status
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="integer", unique=true)
*/
private $code;
I cannot get proper way to set referencedColumnName="code" column (not 'Id' as usual) for Action entity.
Configured this way repo throws wxception at persist moment with "Notice: Undefined index: code";
I guess that it is mappedBy or inversedBy annotation parameter... but can't figure out "how".
Unfortunately it's not supported in Doctrine (reference).
You may edit your Status entity like this (ensure that code is set before persist):
class Status
{
/**
* #ORM\Column(name="code", type="integer", unique=true)
* #ORM\Id
*/
private $code;
}
If autoincremented field is your requirement you can take a look on this answer for possible solutions.
Just thought I'd add you can still use the non-primary keys as many to many, by using the entity itself as the join table. This will work but you still need to set your relationship keys correctly.
Example:
/**
* #ORM\Entity
*/
class Car {
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(name="registration_code", type="text", length=128, nullable=false)
* #var string
*/
public $registrationCode;
/**
* #var \Doctrine\Common\Collections\Collection
* #ORM\ManyToMany(targetEntity="Registration", mappedBy="Cars")
* #ORM\JoinTable(name="car",
* joinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="registration_code", referencedColumnName="registration_code")}
* )
*/
public $Registrations;
public function __construct() {
$this->Cars = new ArrayCollection();
}
}
/**
* #ORM\Entity
*/
class Registration {
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(name="registration_code", type="text", length=128, nullable=false)
* #var string
*/
public $registrationCode;
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="Car", mappedBy="Registrations")
* #ORM\JoinTable(name="car",
* joinColumns={#ORM\JoinColumn(name="registration_code", referencedColumnName="registration_code")},
* inverseJoinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")}
* )
*/
public $Cars;
public function __construct() {
$this->Cars = new ArrayCollection();
}
}
The upside is that it works fine as a workaround.
Keep in mind a few things:
it's a collection not a single instance;
column has to be managed manually on your end;
you must set up constraints correctly (indexes, keys, etc);
check your queries still perform!

Symfony 2 doctrine persist doesn't work after updating Relationship Mapping

I updated my entity file to include relationship mapping.
Persist worked before the update now it doesn't.
Maybe it's something I forgot to do.
namespace classes\classBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* advisersplans
*
* #ORM\Table()
* #ORM\Entity
*/
class advisersPlans
{
/**
*
* #ORM\ManyToOne(targetEntity="plans", inversedBy="adviserPlans")
* #ORM\JoinColumn(name="planid", referencedColumnName="id")
*/
public $plan;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #var integer
*
* #ORM\Column(name="userid", type="integer")
*
*
*/
public $userid;
/**
* #var integer
*
* #ORM\Column(name="adviserid", type="integer")
*
*
*/
public $adviserid;
/**
* #var integer
*
* #ORM\Column(name="planid", type="integer")
*
*
*/
public $planid;
/**
* #var string
*
* #ORM\Column(name="participantLoginWebsiteAddress", type="string", length=255)
*/
public $participantLoginWebsiteAddress;
public function __construct()
{
$class_vars = get_class_vars(get_class($this));
foreach ($class_vars as $key => $value)
{
if ($key != "plan")
$this->$key = "";
}
}
}
Perist returns error saying planid is null. If I remove the following it works.
/**
*
* #ORM\ManyToOne(targetEntity="plans", inversedBy="adviserPlans")
* #ORM\JoinColumn(name="planid", referencedColumnName="id")
*/
Here is my code while persisting.
$adviserPlan = new advisersPlans();
$adviserPlan->planid = $planid;
$adviserPlan->userid = $this->userid();
$adviserPlan->adviserid = $session->get("editadviserid");
$em->persist($adviserPlan);
Am I supposed to populate the plan field and not the planid field or is my entity file coded wrong.
You shouldn't set ids. You should set entities:
$adviserPlan = new advisersPlans();
// You should retrieve the plan before doing this, of course.
$adviserPlan->setPlan($plan);
$plans->addAdviserPlan(§adviserPlan);
$em->persist($adviserPlan);
The methods for adding an entity to a collection should be generated by doctrine when you run:
php app/console doctrine:generate:entities YourBundle

Doctrine query crashing

Very very weird. I have used this method from doctrine hundreds of times. I have a simple controller that takes an id as parameter. The query that Doctrine generates is wrong and crash.
/**
* #Security("has_role('ROLE_ADMIN')")
* #return Response
*/
public function editSellerAction($id)
{
$em = $this->getDoctrine()->getManager();
$seller = $em->getRepository('SiteUserBundle:Seller')->find($id);
// ...
$form = $this->createForm(new SellerType(), $seller, array(
'method' => 'POST'
));
// ...
}
The query generated is the following
[2/2] DBALException: An exception occurred while executing 'SELECT t1.id AS id2, t1.username AS username3, t1.password AS password4, t1.firstname AS firstname5, t1.lastname AS lastname6 FROM seller t1 WHERE t0.id = ? LIMIT 1' with params ["2"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.id' in 'where clause' +
The error thrown makes sense because it's looking at "WHERE t0.id" when it should be looking at "WHERE t1.id". I tried the query with t1 using phpmyadmin and it works.
Any idea what might cause this issue?
/**
* Seller have access to their customer and are able to RW access to the customers
*
* #ORM\Table("seller")
* #ORM\Entity
* #author Michael Villeneuve
*/
class Seller extends User
{
/**
* #var array
*
* #ORM\OneToMany(targetEntity="Customer", mappedBy="seller", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="seller_id", referencedColumnName="id")
**/
protected $customers;
/**
* #var string
*
* #ORM\Column(name="firstname", type="string", length=255, nullable=false)
*/
protected $firstname;
/**
* #var string
*
* #ORM\Column(name="lastname", type="string", length=255, nullable=false)
*/
protected $lastname;
// Other attributes and only getters/setter
/**
*
* #ORM\Entity
*/
class User implements UserInterface
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;
I have 3 entities that extends the User (customer, admin and seller).
Updated link: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/inheritance-mapping.html
Read up a bit on mapped super classes: http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html. Basically, your abstract base user class cannot itself be an entity.
So take the #ORM\Entity line out of your User class. That is where the table 0 (t0) is coming from.
You have 2 options:
The first one is to create an abstract User entity and inherit all values from it. This is useful if you have many entities with the same behaviour. I e.g. like to create a BaseEntity with a ID field and some basic methods. All entities can extend this one and automatically have an ID. Cerad explained in his answer how this is done.
The second option are so called discriminator fields. Basically they allow you to have one User table and sub-tables for every extended entity. You can read about them in the official docs.
Which one you end up using is probably case dependent.
Try to add id field to the Seller entity instead of User
/**
* Seller have access to their customer and are able to RW access to the customers
*
* #ORM\Table("seller")
* #ORM\Entity
*/
class Seller extends User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var array
*
* #ORM\OneToMany(targetEntity="Customer", mappedBy="seller", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="seller_id", referencedColumnName="id")
**/
protected $customers;
/**
* #var string
*
* #ORM\Column(name="firstname", type="string", length=255, nullable=false)
*/
protected $firstname;
/**
* #var string
*
* #ORM\Column(name="lastname", type="string", length=255, nullable=false)
*/
protected $lastname;
// Other attributes and only getters/setter
/**
*
* #ORM\Entity
* #author Michael Villeneuve<michael#panierdachat.com>
*/
class User implements UserInterface
{
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;

JMSSerializerBundle RuntimeException: you must define a type for Entity::$field

I'm having this issue with JMSSerializerBundle. It basically gives me an exception for something that I've already done. This is my entity:
Edited to avoid confusion about annotation lines
<?php
namespace My\ProjectBundle\Entity;
use JMS\SerializerBundle\Annotation\Type;
use Doctrine\ORM\Mapping as ORM;
/**
* My\ProjectBundle\Entity\Music
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="My\ProjectBundle\Entity\MusicRepository")
*/
class Music extends Post
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string $album
*
* #ORM\Column(name="album", type="string")
* #Type("string")
*/
protected $album;
/**
* #var string $artist
*
* #ORM\Column(name="artist", type="string")
* #Type("string")
*/
protected $artist;
/**
* #var integer $duration
*
* #ORM\Column(name="duration", type="bigint")
* #Type("int")
*/
protected $duration;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string")
* #Type("string")
*/
protected $title;
/**
* #var array $genres
*
* #ORM\Column(name="genres", type="array")
* #Type("array")
*/
protected $genres;
As you can see, I've added #Type() annotations for the fields, but it still gives me the exception when I call:
$listenedMusic = $serializer->deserialize($content, 'My\ProjectBundle\Entity\Music', 'json');
I've checked and the $content variable is not empty and has all the fields mapped in JSON format.
In my Monolog files, this is the exact Exception:
[2012-11-29 23:39:07] request.CRITICAL: JMS\SerializerBundle\Exception\RuntimeException:
You must define a type for My\ProjectBundle\Entity\Music::$album. (uncaught exception)
at /vendor/jms/serializer-bundle/JMS/SerializerBundle/Serializer/GenericDeserializationVisitor.php line 177
Why does it still give me this exception?
I'm fairly certain it's because you have two comment strings with different pieces of the whole annotation. Symfony only looks at the comment string directly preceding the class member.
Try replacing:
/** #Type("string")*/
/**
* #var string $album
*
* #ORM\Column(name="album", type="string")*/
protected $album;
with:
/**
* #Type("string")
*
* #var string $album
*
* #ORM\Column(name="album", type="string")*/
protected $album;
(and in every other place you have these duplicate annotation comments)
It's only a guess, but I think it'll fix it. When I tried doing this:
class Something
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
/**
*
*/
private $id;
}
...Symfony gave me this error:
No identifier/primary key specified for Entity 'SomeApp\SomeBundle\Entity\Something'. Every Entity must have an identifier/primary key.
I have fixed this by updating my entire project to dev-master packages. It seemed it was a bug in JMSSerializer, because without modifying any code, I stopped getting this error.
/**
* #var integer $duration
*
* #ORM\Column(name="duration", type="bigint")
* #Type("int")
*/
protected $duration;
Type 'int' doesn't exist for serialization, you must use 'integer'.

Resources