Symfony2: Custom identifier in Sonata entities - php

I have an entity with a custom id (i.e. UUID) generated on __construct function.
namespace AppBundle\Entity;
use Rhumsaa\Uuid\Uuid;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Person
{
/**
* #ORM\Id
* #ORM\Column(type="string")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
public function __construct()
{
$this->id = Uuid::uuid4()->toString();
}
This entity is used in sonata and also in other part of the project. I need this entity to have id before persisting and flushing it, so I can not use a an auto-increment.
So, the problem is sonata don't let me create entities because it takes the create option as and edit on executing because that entity already has an id, but this entity does not exists at this moment, so it fails.
The problem isn't the library for generating UUID, any value for 'id' fails.
Anyone know how to solve it? Another similar approach to solve the problem?

You shouldn't set your id in the constructor, but rather use the prePersist Doctrine event to alter your entity before persisting it for the first time.
You may use annotations to do so, see the Doctrine Documentation on prePersist.
The issue with setting the id in the constructor is that you may override it when you're retrieving it from the database, in which case it will be incorrect.

Related

Doctrine ManyToMany Relationship

Very simple (I thought!), I have an Invoice entity, and a Coupon entity. Invoices can have many coupons applied to them. Coupons conversely, can be used in many invoices.
Excluding getters/setters:
Invoice
namespace Application\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="invoices")
*/
class Invoice
/**
* #ORM\ManyToMany(targetEntity="Application\Entity\Coupon")
* #ORM\JoinTable(name="invoices_coupons")
*/
protected $coupons;
public function addCoupon( Coupon $coupon ){
if( !$this->coupons )
$this->coupons = new ArrayCollection();
$this->coupons->add($coupon);
}
}
Coupon
/**
* #ORM\Entity
* #ORM\Table(name="coupons", indexes={#ORM\Index(name="code_idx", columns={"code"})})
*/
class Coupon implements CandidateInterface
{
/**
* #var \Ramsey\Uuid\Uuid
*
* #ORM\Id
* #ORM\Column(type="uuid")
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=32, unique=true)
*/
protected $code;
}
When I run the helper tool to generate the schema, as expected, it creates a join table invoices_coupons that contains coupon_id, invoice_id (perfect).
So in the code, I have an existing stored Invoice and a similarly existing Coupon.
Seems I cannot do:
// runs a QB to return the coupon, returns a Coupon Entity
$coupon = $couponMapper->getActiveByCode('SAVEBIG');
$invoice->addCoupon( $coupon );
$invoiceMapper->getEntityManager()->update( $invoice );
$invoiceMapper->getEntityManager()->flush();
I get this error:
A new entity was found through the relationship \Application\Entity\Invoice#coupons that was not configured to cascade persist operations for entity: (coupon toString). 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={\u0022persist\u0022}).
Now, I don't want this to create new coupons; any why is it trying this? The coupon already exists, it was loaded from the ER, and it is being added to an existing entity.
If I do what the error message says, it tries to duplicate a new Coupon into the coupon table.
Thanks for your suggestions.
Doctrine\ORM\EntityManager::update() doesn't appear to be a thing that exists. You shouldn't have to do anything between the addCoupon() call and the flush() call.
If simplifying your code doesn't magically fix it, your next step should be to ensure that $couponMapper->getEntityManager() === $invoiceMapper->getEntityManager().
It's not clear how you're instantiating these mapper classes, but it's important to understand that each EntityManager maintains its own internal Identity Map for entities. So if your DIC for some reason is instantiating two different EMs (one for each Mapper), then $invoiceMapper's EM doesn't recognize the $coupon as a managed entity.
It would be weird for that to be the case. Assuming you're using ZF2's ServiceManager, you'd have to explicitly set your EntityManger service to not be shared.
But somehow having two different EntityManagers is the most obvious thing I can think of given the code you've provided.

persist parent and child entities using FOSUserBundle and "JOINED" Class Table Inheritance Doctrine 2/Symfony2.7

I am using the FOSUserBundle in order to manage my users into my application.
But in fact, I have multiple user entities: ParticularConsumer.php and ProfessionnalConsumer.php. So I create a ParentUser.php entity who as an abstract class who extends BaseUser of FOSUserBundle. See the code here:
/**
* ParentUser
*
* #ORM\Table(name="parent_users")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"particular_consumer" = "ParticularConsumer", "professionnal_consumer" = "ProfessionnalConsumer"})
* #ORM\Entity(repositoryClass="MyBundle\EntityBundle\Repository\ParentUserRepository")
*
*/
abstract class ParentUser extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(name="pusr_id", type="integer", nullable=false)
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
// ...
So there are the two other entities who extend the Parentuser.php, following the Class Table Inheritance of Doctrine behavior and documentation:
First ParticularConsumer.php
/**
* ParticularConsumer
*
* #ORM\Table(name="particular_consumer")
* #ORM\Entity(repositoryClass="MyBundle\EntityBundle\Repository\ParticularConsumerRepository")
*
*/
class ParticularConsumer extends ParentUser
{
public function __construct()
{
parent::__construct();
// your own logic
}
}
Second ProfessionnalConsumer.php
/**
* ProfessionnalConsumer
*
* #ORM\Table(name="professionnal_consumer")
* #ORM\Entity(repositoryClass="MyBundle\EntityBundle\Repository\ProfessionnalConsumerRepository")
*
*/
class ProfessionnalConsumer extends ParentUser
{
public function __construct()
{
parent::__construct();
// your own logic
}
}
Now, that I would like to do and to know is how to persist the parent and child entities.
Indeed, as I use the FOSUserBundle, all the routes (register,login, etc...) are managed and generated by this bundle. Now I need to persist datas in Child entities, normally it persists datas in parent entity, that's right ? How can I proceed exactly ?
This how I need to proceed to register a consumer:
There is a question on a page, whish ask users if there are
professionals or particulars.
A drop down list is here for them to make the choice.
Following the choice, I need to register the user in the right entity.
If the user choose particular in the drop down list, I need to
load/display a form to persist data in ParticularConsumer.php and not only in ParentUser.php.
But as I use the FOSUSerBundle, I don't really know how to proceed exactly. As you can understand, using the FOS is a practical way in order to manage users and manage the security, I would like to keep the logic of the bundle. And I want to use the good practices.
Finally, following all the doc of the FOSUserBundle in order to install it, if I want to register a user (localhost/web/app_dev.php/register) I have this error:
Error: Cannot instantiate abstract class MyBundle\EntityBundle\Entity\ParentUser
If your ParentUser is an abstract class then it cannot be an Entity. In such case you have to make it to a MappedSuperClass. But as you can read in the documentation:
A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all. Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment. For further support of inheritance, the single or joined table inheritance features have to be used.
If you want to be able to query your ParentUser and you want this class to have its own entity repository then you will have to remove abstract and add a value for ParentUser to your #ORM\DiscriminatorMap definition:
#ORM\DiscriminatorMap({
"parent_user" = "ParentUser"
"particular_consumer" = "ParticularConsumer",
"professionnal_consumer" = "ProfessionnalConsumer"
})

Doctrine MappedSuperclass and unique constraints

I have a scenario where I need to use the MappedSuperclass functionality of Doctrine (using Symfony2), and also create a unique constraint on some superclass columns. Let's say:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\MappedSuperclass
*/
class Base
{
/**
* #ORM\Column(type="integer", nullable=false)
*/
private $someColumn;
}
/**
* #ORM\Entity
* #ORM\Table(uniqueConstraints={#ORM\UniqueConstraint(name="column_idx", columns={"someColumn"})})
*/
class Concrete extends Base
{
}
The problem is at processing of #ORM\Table annotation during schema generation:
[Doctrine\DBAL\Schema\SchemaException]
There is no column with name 'someColumn' on table 'Concrete'.
Is there a way to define a unique constraint of a mapped superclass?
Since the answer author didn't post the answer himself, let me quote him:
Try to use protected instead of private for entity field. You should always use protected or public for entity fields

FosUserBundle don't persist data in mongodb database

I use symfony2 and the mongoDb ODM. Today I have installed FosUserBundle.
My User class is like that :
use FOS\UserBundle\Document\User as BaseUser;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class User extends BaseUser
{
/**
* #MongoDB\Id(strategy="auto")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
}
My problem is after created a User with FosUserBundle create command, only the id of user is persisted in mongodb document.
If I add the following in my User class :
/**
* #MongoDB\String
*/
protected $username;
The create command persist Id and the good username.
Of course, it's the same with all the initial fields of FOS\UserBundle\Document\User (BaseUser).
It looks like the inheritance mapping is not working properly.
Check that your Doctrine configuration in config.yml is set to:
auto_mapping: true
To work around this another way you would need to add the complete mapping information to your User entity extending the one from the FOSUserBundle.
With Doctrine\ORM it is normally the #ORM\MappedSuperClass annotation which provides the mapping for the extending class. In FOSUserBundle's mongodb xml mapping it is this line:
...
<mapped-superclass name="FOS\UserBundle\Document\User" collection="fos_user_user">
...
Solution 1:
Try this:
copy the mapping xml from FOSUserBundle over to your UserBundle into Resources/config/doctrine/User.mongogb.xml
change it to fit your own Entity Class
remove mapped-superclass node
add the id field as Id with auto strategy
You can then ommit the #MongoDB annotations on your entity completely.
To save somebody's time.
I also came across the issue of non-working mapping for ODM User entity and could not understand why it doesn't work. The reason was I must have extended my user entity from FOS\UserBundle\Document\User and not from FOS\UserBundle\Model\User

Symfony2 __toString() generation

Does Symfony2 have an automatic __toString() generation based on the entity fields, or an annotation to say that the __toString() should be generated, similar to Java Roo?
I cannot find such a feature under the annotations reference, and the consensus among the Google Group seems to side with defining __toString() on the object.
If you use an IDE such as Net Beans, a simple CTRL+SPACE hotkey and click will automatically generate the __toString() for you, you'd simply need to fill out the refence to whichever attribute you want to use to represent the object.
Furthermore, you could take that one step further and define an Entity template (which is what I do in Net Beans). Something like this could save you some time, keeping in mind Doctrine2 is my ORM in this example, and I use the annotations method of defining my entities:
<?php
namespace Foo\BarBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
//use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
*/
class ${name}
{
/**
* #ORM\Id #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
public function __toString()
{
//return $this->get();
}
}
This automatically fills out the class name and has ArrayCollection commented out (so I can easily add that in if the entity requires it). This would leave you with simply needing to fill in the rest of whatever method you'd like to use for __toString();
${name} is a template variable in NetBeans.

Categories