Doctrine ODM MongoDB - Replicate a simple One to Many Reference with Constraint - php

I'm new to Doctrine, mongo and the ODM setup and while playing with this setup in ZF1 I'm trying to replicate a simple one to many reference with a constraint. Here is the situation and would like some advise on how to achieve this.
This is a simple user->role mapping, so in a sql situation I would have tables as follows:
Users
- id
- name
- role_id
Roles
- id
- name
Then a foreign key constraint would be set on the users role_id to map to the role id. And upon deleting a role a foreign key constraint would be triggered stopping the operation.
How could I achieve the same goal in Doctrines MongoDB ODM?
So far I have played with different types of annotations on the User entity including #ReferenceOne #ReferenceMany with different cascade options...
The choice left to me now is to implement #PreUpdate, #PreRemove lifecycle events on the 'role' entity and then check that no users are using the role, if they are then on update change the reference to match or on remove throw an exception.
Am I right here or lost ?
Thank you,
Si

For something like that I wouldn't have two separate 'tables' like you would in SQL. You would just have the role type as a property of the user. And then if you wanted to remove a role type you can just manipulate the role field of all users with that role.
But to answer your question, I would do it's like so.
<?php
class User {
/** #MongoDB\Id */
protected $id;
/** #MongoDB\String */
protected $name;
/** #MongoDB\ReferenceOne(targetDocument="Role", mappedBy="user") */
protected $role;
//Getters/Setters
}
class Role {
/** #MongoDB\Id */
protected $id;
/** #MongoDB\String */
protected $name;
/** #MongoDB\ReferenceMany(targetDocument="User", inversedBy="role") */
protected $users;
public function _construct() {
$this->users = new Doctrine\Common\Collections\ArrayCollection;
}
// Getters/Setters
public function hasUsers() {
return !$this->users->isEmpty();
}
}
Then I would create a service class for working with my document manager.
class RoleService {
public function deleteRole(Role $role) {
if (!$role->hasUsers()) {
// Setup the documentManager either in a base class or the init method. Or if your über fancy and have php 5.4, a trait.
$this->documentManager->remove($role);
// I wouldn't always do this in the service classes as you can't chain
// calls without a performance hit.
$this->documentManager->flush();
}
}
}

Related

How do you create ManyToOne relation within single supermapped class

I am creating a simple CMS Bundle for my headless symfony backend and I'm trying to map Page to Page with parent and child relation(Many children to one parent) and I have this class mapped superclass to create reusable code, this is a minified sample on what I'm trying to archive:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\MappedSuperclass()
*/
class Test
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function getId()
{
return $this->id;
}
/**
* #ORM\ManyToOne(targetEntity="Ziebura\CMSBundle\Entity\Test")
*/
protected $parent;
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
}
Then I'm extending this class as a normal entity to create DB table
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Ziebura\CMSBundle\Entity\Test as BaseTest;
/**
* #ORM\Table(name="test")
* #ORM\Entity(repositoryClass="App\Repository\TestRepository")
*/
class Test extends BaseTest
{
}
The issue is that I'm getting this doctrine exception
Column name `id` referenced for relation from App\Entity\Test towards Ziebura\CMSBundle\Entity\Test does not exist.
I don't quite understand why it produces this error or is the thing that I'm trying to archive impossible, I already did relations on mapped superclasses but it was 2 or more tables and not just a single on. I already tried creating $children field but it didnt worked and still produced above error. Did anyone try to create something simmilar? I couldn't find anything about this in doctrine docs, only found how to map 2 different superclasses. I suppose the easy way out would be to specify the relation in App namespace not in the Bundle but that pretty much destroys the purpose of reusable code if I'd have to declare that in every project I use the bundle. I believe in stack let's figure this out. Thanks!
Lets read Doctrine docs about this: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html#inheritance-mapping
A mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information for its subclasses, but which is not itself an entity. Typically, the purpose of such a mapped superclass is to define state and mapping information that is common to multiple entity classes.
...
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.
According to this:
MappedSuperclass cannot be Entity
Cannot have One-To-Many relationship - so if you are defining ManyToOne to same class then it creates also OneToMany on same class - which, as you read above, is forbidden.
For some reason only changing the full entity path in BaseTest resolved app throwing the exception and it works, if anyone would face same issue try changing
/**
* #ORM\ManyToOne(targetEntity="Ziebura\CMSBundle\Entity\Test")
*/
protected $parent;
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
To
/**
* #ORM\ManyToOne(targetEntity="Test")
*/
protected $parent;
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
If someone knows why it has to be like this I'd much appreciate a comment to my answer.

Symfony where clause on the entity level

I've got two entities that are linked together by a one-to-many relationship and I'm using soft deletes on both entities. Because I'm using soft deletes however, reading data is a little bit more tricky because I need to check if the deleted flag is set to false before reading it out.
The basic setup of the entities are:
class Division extends MasterData {
...
/**
* #var Asset
*
* #ORM\OneToMany(targetEntity="Asset", mappedBy="division")
*/
private $assets;
public function __construct() {
$this->assets = new ArrayCollection();
}
public function getAssets() {
return $this->assets;
}
public function addAssets(Asset $asset) {
$this->assets[] = $asset;
return $this;
}
...
}
class Asset extends MasterData {
...
/**
* #var Division
*
* #ORM\ManyToOne(targetEntity="Division", inversedBy="assets")
*/
private $division;
...
}
class MasterData {
/**
* #ORM\Column(name="deleted", type="boolean", options={"default":0})
*/
protected $deleted;
public function __construct() {
$this->deleted = 0;
}
...
}
These are only snippets of the entities, not the entire thing.
When I am in the controller for a Division, I'd like to pull a list of all the Assets that are related to that division and are not marked as deleted. I can see a couple ways of doing this.
An easy solution would be to create a custom repository to handle the pull of data. This however would provide a limitation when I would like to further filter data (using findBy() for example).
A second solution would be to alter the getAssets() function in the Division entity to only return assets that are not deleted. This however means that I'm pulling all of the data from the database, then filtering it out post which is very inefficient.
Ideally, I'm looking for a way to alter the definition in the entity itself to add a where clause for the asset itself so that way the filtering is happening in the entity removing the needs for custom repositories and a more efficient option. Similar as to how I can define #ORM\OrderBy() in the annotations, is there a way to similar to this that lets me filter out deleted assets pre-execution and without a custom repository?
Thanks in advance :)
Doctrine does not support conditional associations in mapping. To achive this behavior you can use Criteria API in the entity methods. And yes, in this case all data will be fetched from DB before applying condition.
But Doctrine (>=2.2) supports Filters. This feature allows to add some SQL to the conditional clauses of all queries. Soft-deletes can be implemented through this feature.
The DoctrineExtensions library already has this functionality (SoftDeletable, based on Filters API).
Also, many don't recommend to use soft-deletes (1, 2).

Persisting entity with Doctrine association

I'm having trouble trying to persist an entity with an association using Doctrine.
Here's the mapping on my owning side: (User.php)
/** #Role_id #Column(type="integer") nullable=false */
private $role_id;
/**
* #ManyToOne(targetEntity="Roles\Entities\Role")
* #JoinColumn(name="role_id", referencedColumnName="id")
*/
private $role;
There's no mapping on the inverse side, I tried with (OneToMany) and it didn't seem to make a difference.
Basically, I'm passing a default value of 2 (integer) to a method setRole_id but it shows up as blank when I actually go to persist the entity which causes a MySQL error as that column doesn't allow nulls.
Edit 1:
Literally just persisting this for role_id
$this->user->setRole_id( 2 );
Cheers,
Ewan
Your mapping seems incorrect. Try to rewrite it as follows:
/**
* #ManyToOne(targetEntity="Roles\Entities\Role")
* #JoinColumn(name="role_id", referencedColumnName="id", nullable=false)
*/
private $role;
In other words, you only need to describe the role_id as the join column of your relationship. You don't need to map it as a "normal" column. Then just write and use a regular setter declared like the one below:
public function setRole(Roles\Entities\Role $role) {
$this->role = $role;
}
Use the above instead of $this->user->setRole_id(2) and persist your user entity. Doctrine should automatically take care of storing the correct entity ID in the foreign key field for you.

doctrine2 checking if one entity is related to other over ManyToMany relation

For example I have 2 entities: event and user both has ManyToMany relation.
class Event {
/**
* #Id #Column(type="integer") #GeneratedValue
*/
protected $id;
/**
* #ManyToMany(targetEntity="User", inversedBy="participated_events")
*/
protected $participations;
}
and
class User {
/**
* #Id #Column(type="integer") #GeneratedValue
* */
protected $id;
/**
* #ManyToMany(targetEntity="Event", inversedBy="participations")
*/
protected $participated_events;
}
how can i check if $em->getRepository('event')->find(RANDOM_ID) and $em->getRepository('user')->find(RANDOM_ID) is related to each other over participation? In other words how to check if user is participated in this event? What is the best way? I know that i could get all participated events and check over foreach() but i think that it must be an easier way.
Thanks.
You have to create a custom repository DQL and do select fields you want to do.;
I, personally, avoid Doctrines many to many relations as it is easier for me to do things you want to do, aslo if i need extra columns in join_table. So, I almost always create a join Entity with corresponding manyTo one and onetoMany references. So in your instance there would be a ParticipatedEvent entity which has manyToOne users and manyToOne Events (and on their target Entites oneToMany) You stil have your relationship but you have extra join Model (or Entity)
So, you end up like this:
$particpated_event = $em->getRepository('ParticipatedEvents')->findOneBy('event_id'=>RANDOM_ID, 'user_id'=>RANDOM_USER_ID);
if($particpated_event) {
$event = $particpated_event->getEvent(); //or get user.
}
and you can stil do things like this
$particpated_events = $em->getRepository('event')->find(RANDOM_ID)->getParticipatedEvents();
foreach( $particpated_events as $particpated_event){
$user = $particpated_event->getUser();
}
Again see what best suits fro you. You can, as said, create Repository class with DQL query

Doctrine2, Mapping "inherited" tables

as pointed out here: Doctrine 2.1 - Map entity to multiple tables Doctrine2 does not allow mapping of one object to multiple tables.
I currently have a Mysql db setup similar to this:
base_entity: id, some_basic_data_columns
state: id, state, entity_id (FK to base_entity.id), start_time, end_time, ...
entity_one: id (FK to base_entity.id), some_specific_data
entity_two: id (FK to base_entity.id), some_specific_data
and so on...
In a way, entity_x is "extending" base_entity, and all these entities can have multiple states. To have proper foreign keys I would have to either have separate state tables (which I don't want to do because they will structurally be the same ), or do it like this.
The base entity by itself is useless, id could even be boiled down to just the id field to allow to join with each child entity to multiple states.
I do not need a BaseEntity class, but I do need for each child Entity to have a getStates() method. Of course I may actually have an abstract entity class, but concrete entities will extend it, not have it as a property like they would if I would map them as one would map other one-to-one relationships
Since Doctrine will not allow me to map EntityOne to both entity_one and base_entity table I have to ask:
Is this bad design? Am I overlooking some other way to solve this elegantly? I know other DMBSs have inheritance, but for instance PostgreSql would still not allow me to join the base_entity to state if no physical base_entity exists for a child.
I could do something like this on the code side:
class EntityOne {
// baseEntity as a property
private $_baseEntity;
// private getter for the base table
private getBaseEntity();
// and getters like this for properties in the base table
public getStates(){
return $this->getBaseEntity()->getStates();
}
}
This way the entity would behave like a single entity (not combined from base and child) to the outside world, but it would still require that I write a separate BaseEntity class and all the config info to connect it to other entity classes
Basically, what I'm asking is: is this a Db design issue, and I got it completely wrong from the start (and if I did, which is the "best" approach), or is this a code issue, and I should work around it with code (if so, is my approach in 2. ok, or are there better ways to deal with this), and are there ORMs which allow for multiple table mapping?
Many thanks in advance.
You could use Class Table Inheritance (see Doctrine documentation about that), defining a BaseEntity entity class, and and create EntityOne and EntityTwo extending that.
You could define the relationship between the BaseEntity class and the State entity class as one-to-many association - if I understood right what you wanted, providing the needed getState() method in the BaseEntity class.
Something like this:
/**
* #Entity
* #Table(name="base_entity")
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="entity_type", type="string")
* #DiscriminatorMap({"entity_one"="EntityOne", "entity_two"="EntityTwo"})
*/
class BaseEntity {
/**
* #Id
* #Column(type="integer")
*/
protected $id;
/**
* #OneToMany(targetEntity="State", mappedBy="entity)
**/
protected $states;
public function getStates() {
return $this->states;
}
...
}
/**
* #Entity
* #Table(name="entity_one")
*/
class EntityOne extends BaseEntity {
...
}
/**
* #Entity
* #Table(name="entity_two")
*/
class EntityTwo extends BaseEntity {
...
}
/**
* #Entity
* #Table(name="state")
*/
class State {
/**
* #ManyToOne(targetEntity="BaseEntity", inversedBy="states")
* #JoinColum(name="entity_id", referencedColumnName="id")
*/
protected $entity;
public function getEntity() {
return $this->entity;
}
...
}

Categories