symfony 2 unique constraint validation with association - symfony

I'm having trouble validating the "uniqueness" of two fields, being one of them an entity of an association. The logic is that there can't be two taxes with the same description for a single country.
Here's my (failed) attempt:
/**
* #ORM\Entity
* #ORM\Table(name="taxes", uniqueConstraints={#ORM\UniqueConstraint(columns={"country_id", "description"})})
* #UniqueEntity(fields={"country", "description"})
*/
class Tax
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column()
*/
protected $description;
/**
* #ORM\Column(type="float")
*/
protected $value;
/**
* #ORM\ManyToOne(targetEntity="Country", inversedBy="taxes")
*/
protected $country;
//getters and setters...
}
When I test my app with a duplicate tax entity, the form pass the validation (when it shouldn't) and Symfony throws an error:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1-ITBMS' for key 'country_id'
UPDATE: I found that this is a known bug in Doctrine 2.1 that is fixed in Doctrine 2.2. Unfortunately, Symfony 2.0.11 (my current version) ships with Doctrine 2.1 and I don't know how to update my deps file appropriately
UPDATE 2: After updating my deps and deps.lock files to get the latest Doctrine 2.2.1 files as #elnur suggested below, the problem is still there: The composite unique key is created in the database but the validation is not correctly performed. Upgrading the Doctrine files alone is not solving the problem.
UPDATE 3: I even updated Symfony core to version 2.0.12 but it doesn't solve the problem either.
UPDATE 4 (SOLVED): I found the error inside my controller. Here is my original controller code:
public function createAction($country_id)
{
//...
if($request->getMethod() == 'POST')
{
$form->bindRequest($request);
$tax->setCountry($country); //HERE IS THE ERROR...
if($form->isValid())
{
//...
}
}
//...
}
Setting the country before binding the request was the solution.
public function createAction($country_id)
{
//...
if($request->getMethod() == 'POST')
{
$tax->setCountry($country); //NOW IT WORKS...
$form->bindRequest($request);
if($form->isValid())
{
//...
}
}
//...
}

To upgrade to Doctrine 2.2.1, replace related entries in your deps file with these:
[doctrine-common]
git=http://github.com/doctrine/common.git
version=2.2.1
[doctrine-dbal]
git=http://github.com/doctrine/dbal.git
version=2.2.1
[doctrine]
git=http://github.com/doctrine/doctrine2.git
version=2.2.1
And in your deps.lock with these:
doctrine-common 2.2.1
doctrine-dbal 2.2.1
doctrine 2.2.1
Then run:
bin/vendors install
UPDATE
Since upgrading Doctrine didn't work, try the UniqueEntityCaseInsensitive constraint from my ValidatorBundle.
Install the bundle, import the constraint:
use Elnur\ValidatorBundle\Validator\Constraints\UniqueEntityCaseInsensitive;
and replace your
#UniqueEntity(fields={"country", "description"})
with
#UniqueEntityCaseInsensitive(fields={"country", "description"})

Related

The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called

I'm tying to create one to many relations
A have class
class Interview {
/**
* #OneToMany(targetEntity="Question", mappedBy="question")
*/
private $questions;
public function __construct() {
$this->questions = new ArrayCollection();
}
public function __toString() {
return $this->id;
}
/**
* #return Collection|Question[]
*/
public function getQuestions() {
return $this->questions;
}
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
......
}
another
class Question {
/**
* #ManyToOne(targetEntity="Interview", inversedBy="interview")
* #JoinColumn(name="interview_id", referencedColumnName="id")
*/
private $interview;
public function getInterview() {
return $this->interview;
}
public function setInterview(Interview $interview) {
$this->interview = $interview;
return $this;
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
*/
private $interview_id;
......
}
and Controller for all this
if ($form->isSubmitted() && $form->isValid()) {
$interview = new Interview();
$question = new Question();
$em->persist($interview);
$question->setInterview($interview);
$question->setTitle($request->get('title'));
$em->persist($question);
$em->flush();
return $this->redirectToRoute('homepage');
}
i'm receiving an error:
Entity of type AppBundle\Entity\Question is missing an assigned ID for
field 'interview_id'. The identifier generation strategy for this
entity requires the ID field to be populated before
EntityManager#persist() is called. If you want automatically generated
identifiers instead you need to adjust the metadata mapping
accordingly.
Don't understand what the problem and how to fix it.
To enforce loading objects from the database again instead of serving them from the identity map. You can call $em->clear(); after you did $em->persist($interview);, i.e.
$interview = new Interview();
$em->persist($interview);
$em->clear();
It seems like your project config have an error in doctrine mapped part.
If you want automatically generated identifiers instead you need to
adjust the metadata mapping accordingly.
Try to see full doctrine config and do some manipulation with
auto_mapping: false
to true as example or something else...
Also go this , maybe it will be useful.
I am sure, its too late to answer but maybe someone else will get this error :-D
You get this error when your linked entity (here, the Interview entity) is null.
Of course, you have already instantiate a new instance of Interview.But, as this entity contains only one field (id), before this entity is persited, its id is equal to NULL. As there is no other field, so doctrine think that this entity is NULL. You can solve it by calling flush() before linking this entity to another entity

Symfony entity no configured for cascade

Can anyone help me explaining what I am doing wrong? I try to set an entity in relation to an file. Its about Supplier and Stock.
My Stock entity looks like
/**
* #ORM\ManyToOne(targetEntity="PrClientBundle\Entity\Lieferanten")
* #ORM\JoinColumn(name="liefer_id", referencedColumnName="id")
* #var lieferant
*/
private $lieferant;
I also using getter and setter like following
/**
* Set lieferant
*
* #param \PrClientBundle\Entity\Lieferanten $lieferant
* #return Leadbase
*/
public function setLieferant(\PrClientBundle\Entity\Lieferanten $lieferant = null)
{
$this->lieferant = $lieferant;
return $this;
}
/**
* Get lieferant
*
* #return \PrClientBundle\Entity\Lieferanten
*/
public function getLieferant()
{
return $this->lieferant;
}
When I import new Stockitems like:
$lead->setLieferant($lieferant);
I get the following errormessage which I really don't understand :(
[Doctrine\ORM\ORMInvalidArgumentException]
A new entity was found through the relationship 'PrLeadBundle\Entity\Leadbase#lieferant' that was not configured to cascade persist operations for entity: PrClientBundle\Enti
ty\Lieferanten#000000002a45dae80000000002f826ff. 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"}). If you cannot find out which entity causes the problem implement 'PrClientBundle\Entity\Lieferante
n#__toString()' to get a clue.
It would be very great if you could help me understanding what am I doing wrong.

How to store historical data with symfony and doctrine?

I want to store store historical data with symfony2 and doctrine2. For example i am having 2 entities:
class Shop
{
private $id;
private $url;
private $version;
}
and the second entity:
class Version
{
private $id;
private $software;
private $version;
}
The Version entity stores specific shop-versions, for example Magento 1.2 or OXID eShop 4.7 - so a entry for a version-entity should be reusable.
Every time the version for a Shop is changed, i want to store this change to have a historical view for the version-changes.
How can i do that with symfony2 and doctrine2? I have tried many-to-many mappings, but i cant figure out the right way using the correct mapping.
Thanks for your help!
There's a few things you have to set properly in order for this to work.
First, you need to tell Doctrine that $versions is related to Version:
class Shop
{
private $id;
private $url;
/**
* #ORM\ManyToMany(targetEntity="Version", cascade={"persist"})
* #ORM\JoinTable(name="shop_version",
* joinColumns={#ORM\JoinColumn(name="shop_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="version_id", referencedColumnName="id")}
* )
*/
private $versions;
}
Since it's a ManyToMany relationship (documentation), $versions will be treated like an ArrayCollection by Symfony. Thus, you need to create methods to handle it accordingly.
Constructor
public function __construct()
{
$this->versions = new ArrayCollection();
}
Getter
public function getVersions()
{
return $this->versions;
}
Adder
public function addVersion(Version $version)
{
$this->versions[] = $version;
}
Remover
public function removeVersion(Version $version)
{
$this->versions->removeElement($version);
}
That's it. Don't forget to add the use statment for ArrayCollection!
use Doctrine\Common\Collections\ArrayCollection;
In your case instead of reinventing the wheel i would recommend Doctrine2 extension: EntityAudit that allows full versioning of entities and their associations. Usage:
$auditReader = $this->container->get("simplethings_entityaudit.reader");
// find entity state at a particular revision
$articleAudit = $auditReader->find('SimpleThings\EntityAudit\Tests\ArticleAudit', $id = 1, $rev = 10);
// find Revision History of an audited entity
$revisions = $auditReader->findRevisions('SimpleThings\EntityAudit\Tests\ArticleAudit', $id = 1);
// find Changed Entities at a specific revision
$changedEntities = $auditReader->findEntitiesChangedAtRevision( 10 );
and more on: https://github.com/simplethings/EntityAudit
Another available package for entity versioning is https://github.com/madmis/ActivityLogBundle. This package includes a revision control system that saves each state of your desired entities and properties.
To enable logging, add the following annotation to your entity class
#Gedmo\Loggable(logEntryClass="ActivityLogBundle\Entity\LogEntry")
Make sure to import the annotation
use Gedmo\Mapping\Annotation as Gedmo;
Add the following annotations to the properties where you want to log changes of
#Gedmo\Versioned
The package offers methods to easily retrieve logentries for an entity
public function getLogEntriesQuery($entity)
This will return log entries with the following methods
$logEntry->getVersion() //returns entities revision version
$logEntry->getOldData() //returns data state before updating
$logEntry->getData() //returns data state after updating
$logEntry->getLoggedAt() //returns when de log was created
In order to retrieve logEntries for a given timeframe you can extend the querybuilder that's returned from the following method, which is also available in the LogEntryRepository:
public function getLogEntriesQueryBuilder($entity)

The method name must start with either findBy or findOneBy! (uncaught exception)

I´ve checked already this but my error seems to be different.
I´m getting this error:
[2012-05-07 14:09:59] request.CRITICAL: BadMethodCallException: Undefined method 'findOperariosordenados'. The method name must start with either findBy or findOneBy! (uncaught exception) at /Users/gitek/www/uda/vendor/doctrine/lib/Doctrine/ORM/EntityRepository.php line 201 [] []
This is my OperarioRepository:
<?php
namespace Gitek\UdaBundle\Entity;
use Doctrine\ORM\EntityRepository;
/**
* OperarioRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class OperarioRepository extends EntityRepository
{
public function findOperariosordenados()
{
$em = $this->getEntityManager();
$consulta = $em->createQuery('SELECT o FROM GitekUdaBundle:Operario o
ORDER BY o.apellidos, o.nombre');
return $consulta->getResult();
}
}
This my controller, where I call the repository:
$em = $this->getDoctrine()->getEntityManager();
$operarios = $em->getRepository('GitekUdaBundle:Operario')->findOperariosordenados();
Finally, this is my Entity:
<?php
namespace Gitek\UdaBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Gitek\UdaBundle\Entity\Operario
*
* #ORM\Table(name="Operario")
* #ORM\Entity(repositoryClass="Gitek\UdaBundle\Entity\OperarioRepository")
*/
class Operario
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $nombre
*
* #ORM\Column(name="nombre", type="string", length=255)
*/
private $nombre;
----
----
Any help or clue??
Thanks in advance
EDIT: Works fine on dev environment, but no in prod environment.
You already are in a reposoritory, you do not need to re-get it.
All methods in a *Repository can be used as with $this
Also, note
Query Builder or Hand made Query is way too much work when a simple return $this->findBy(); can be used.
findBy() has three parameters, first is an array of relations and getters, the second is for ordering, see Doctrine\ORM\EntityRepository code
Instead of using Raw queries... try the query builder FIRST. Look at my sample.
Your code
I would suggest you simply do:
public function findOperariosordenados()
{
$collection = $this->findBy( array(), array('apellidos','nombre') );
return $collection;
}
You only need EntityRepository
One of my repositories:
Things to note:
Order has a relationship as $owner using the User Entity
If you REALLY need an array, in $array = $reposiroty->getOneUnhandledContainerCreate(Query::HYDRATE_ARRAY)
The ContainerCreateOrder is an extend of Order in a #ORM\InheritanceType("SINGLE_TABLE"). Quite out of scope of this question though.
It could be helpful:
<?php
namespace Client\PortalBundle\Entity\Repository;
# Internal
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query;
use Doctrine\Common\Collections\ArrayCollection;
# Specific
# Domain objects
# Entities
use Client\PortalBundle\Entity\User;
# Exceptions
/**
* Order Repository
*
*
* Where to create queries to get details
* when starting by this Entity to get info from.
*
* Possible relationship bridges:
* - User $owner Who required the task
*/
class OrderRepository extends EntityRepository
{
private function _findUnhandledOrderQuery($limit = null)
{
$q = $this->createQueryBuilder("o")
->select('o,u')
->leftJoin('o.owner', 'u')
->orderBy('o.created', 'DESC')
->where('o.status = :status')
->setParameter('status',
OrderStatusFlagValues::CREATED
)
;
if (is_numeric($limit))
{
$q->setMaxResults($limit);
}
#die(var_dump( $q->getDQL() ) );
#die(var_dump( $this->_entityName ) );
return $q;
}
/**
* Get all orders and attached status specific to an User
*
* Returns the full Order object with the
* attached relationship with the User entity
* who created it.
*/
public function findAllByOwner(User $owner)
{
return $this->findBy( array('owner'=>$owner->getId()), array('created'=>'DESC') );
}
/**
* Get all orders and attached status specific to an User
*
* Returns the full Order object with the
* attached relationship with the User entity
* who created it.
*/
public function findAll()
{
return $this->findBy( array(), array('created'=>'DESC') );
}
/**
* Get next unhandled order
*
* #return array|null $order
*/
public function getOneUnhandledContainerCreate($hydrate = null)
{
return $this->_findUnhandledOrderQuery(1)
->orderBy('o.created', 'ASC')
->getQuery()
->getOneOrNullResult($hydrate);
}
/**
* Get All Unhandled Container Create
*/
public function getAllUnhandledContainerCreate($hydrate = null)
{
return $this->_findUnhandledOrderQuery()
->orderBy('o.created', 'ASC')
->getQuery()
->getResult($hydrate);
}
}
Did you clear your cache?
php app/console cache:clear --env=prod --no-debug
My app/config/config_prod.yml has a cache driver specified for doctrine :
doctrine:
orm:
metadata_cache_driver: apc
result_cache_driver: apc
query_cache_driver: apc
I cleared APC cache using these function calls :
if (function_exists('apcu_clear_cache')) {
// clear system cache
apcu_clear_cache();
// clear user cache
apcu_clear_cache('user');
}
if (function_exists('apc_clear_cache')) {
// clear system cache
apc_clear_cache();
// clear user cache
apc_clear_cache('user');
// clear opcode cache (on old apc versions)
apc_clear_cache('opcode');
}
And emptied app/cache/ directory.
But I kept getting this error in the prod environment while everything was fine in the dev environment.
I finally rebooted my virtual server and that did the trick.
Which definitely leads me to suspect a cache problem. Next time I will try to (gracefuly) restart the web server only, as that also clears the cache (php - Does a graceful Apache restart clear APC? - Stack Overflow)
Otherwise, setting apc.stat = 1 (http://php.net/manual/en/apc.configuration.php#ini.apc.stat) in /etc/php5/apache2php.ini also seems to be a good idea as suggested here : do we need to restart apache + APC after new version deployment of app?
UPDATE
My development server has APC installed and not APCu. The first two calls to apcu_clear_cache() were causing a PHP Fatal error, which in turn prevented the APC cache from being cleared.
So check which cache your system uses before issuing calls to apcu_clear_cache() or apc_clear_cache(). After that, no need to restart the virtual machine nor the web server to clear the cache and get rid of the nasty exception.
Addded if blocks to run APC or APCu specific functions.

entities in different bundles

I'm using Symfony 2 and I have two entities in different bundles like:
//this class overrides fos_user class
//User\UserBundle\Entity\User
class User extends BaseUser
{
//..
/**
* #ORM\OneToMany(targetEntity="News\AdminBundle\Entity\News", mappedBy="author_id")
*/
protected $news_author;
//...
}
//News\AdminBundle\Entity\News
class News
{
//...
/**
* #ORM\ManyToOne(targetEntity="\User\UserBundle\Entity\User", inversedBy="news_author")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
protected $news_author;
//...
}
Both classes (entities) works fine. I have successfully setup fos_user bundle with registration and other stuff. The same if for News class. Then I build relation between those two classes OneTo Many (User -> News) as it is shown in code. This also works fine without errors and I can add news that belongs to user. The problem is when I build a form with entity class like:
->add('year', 'entity', array(
'class' => 'NewsAdminBundle:News',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->groupBy('u.year')
->orderBy('u.year', 'DESC');
},))
This form shows me a years when news are posted (like archive). Years are showing fine, but when I submit (post) a form then I've got error:
Class User\UserBundle\Entity\News does not exist
I figure out that this error is connected with sentence
$form->bindRequest($request);
The problem is because I have two entities in different bundles. How can I solve this error?
Edit:
I solved the problem. When I run
php app/console doctrine:generate:entities User
php app/console doctrine:generate:entities News
then Doctrine generate getters and setters in User and News. In entity News it generates method
/**
* Add news_author
*
* #param User\UserBundle\Entity\News $newsAuthor
*/
public function addNews(User\UserBundle\Entity\News $newsAuthor)
{
$this->news_author[] = $newsAuthor;
}
I was not paying attention to this method and I change it to this
/**
* Add news_author
*
* #param News\AdminBundle\Entity\News $newsAuthor
*/
public function addNews(News\AdminBundle\Entity\News $newsAuthor)
{
$this->news_author[] = $newsAuthor;
}
Now everything works fine. Thanks for all answers.
/**
* #ORM\ManyToOne(targetEntity="User\UserBundle\Entity\User", inversedBy="news_author")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
protected $news_author;
You have to remove prefix backslash – see note in Doctrine documentation

Resources