Symfony Doctrine : Use real tablename in request - sql

I'm using doctrine in a symfony project and I have a little problem.
I have a "Character" entity, and a "Equipment" entity.
The character can only wear 5 equipment on him.
But he could buy some other equipment, to put in his inventory. This way, he can switch one of his equipment for another one in his inventory.
So, in my "Character" entity I have :
/**
* #ORM\ManyToMany(targetEntity="rs\WelcomeBundle\Entity\Equipment", cascade={"persist"})
* #ORM\JoinTable(name="InventoryCharacter")
*/
private $inventory;
/**
* #ORM\ManyToMany(targetEntity="rs\WelcomeBundle\Entity\Equipment", cascade={"persist"})
* #ORM\JoinTable(name="EquipmentWearCharacter")
*/
private $equipementsWear;
The problem is : I want to get the list of equipment that the character don't already buy.
In fact I want to get the list of equipment that are in the complete list (findAll in equipment) but NOT IN the list of character inventory.
I try to do a request but doctrine doesn't know the table "InventoryCharacter" because there is no corresponding entity class...
So I can't do "Select p from InventoryCharacter..."
How I can do ? I want to specify to search in the real database, not in the list of entity class...

The 'ManyToMany' relationship with Doctrine is very transparent. You can not do anything more than getting entity list for the one or the other.
If you ever find yourself needing to access the 'Relation' table, either to add extra data about the relation (e.g. time created) or to apply a filter (e.g. recently created), you need to create that relationship entity yourself. i.e. CharacterInventory. and set up OneToMany and ManyToOne relationships between the 3 entities.
Hope this help.
+++++++++++Edited+++++++++++
If you simply want to retrieve all equipments user can purchase (i.e. not purchased yet), you actually dont need to create the middle entity CharacterInventory:
/**
* Add this to the Equipment Entity
*
* #ORM\ManyToMany(targetEntity="rs\WelcomeBundle\Entity\Character", cascade={"persist"})
* #ORM\JoinTable(name="InventoryCharacter")
*/
private $characters;
Then you can use this to query what you want:
$dql = "SELECT s FROM rs\WelcomeBundle\Entity\Equipment s LEFT JOIN s.characters ct WHERE ct != :character";
$found = $em->createQuery($dql)
->setParameter('character', $characterEntityObject)
->getResult();

Related

Symfony 3 | Override entity table name

I'm using FOSMessage in my project (https://github.com/FriendsOfSymfony/FOSMessage) and I would like to override the table names of the entities.
For example in FOSMessage (\FOS\Message\Driver\Doctrine\ORM\Entity\Conversation) I have :
/**
* #ORM\Table(name="fos_message_conversations")
* #ORM\Entity
*/
class Conversation extends BaseConversation
...
// properties
...
And in my custom entity, I do :
/**
* #ORM\Table(name="user__message__fos_message_conversations")
* #ORM\Entity
*/
class Conversation extends \FOS\Message\Driver\Doctrine\ORM\Entity\Conversation
{
}
It works but only for "none-relation" properties. There is properties with "One-To-Many" relationships and there are ignored.
When I update my database, I only have text properties and id. I don't have "messages" relation for example.
How can I do ? I only want to change table name.
oneToMany needs association mapping on many side, therefore you would need also custom Message entity with mapping manyToOne pointing your custom Conversation.
That's because in fact in RMDBS, the foreign key is in many side table, which in this case is message table. There's a column conversation_id in message and not vice versa.

DDD - Association mapping between bounded contexts using Doctrine 2

I am struggling to understand the right way to implement association mapping between two entities from different bounded contexts using Doctrine 2. Suppose that there are two "User" and "Post" entities that belong to "User" and "Content" bounded contexts, respectively. There is also a "User" concept in "Content" context that determines the author of a "Post" through a Many-To-One association. Therefore, "User" in "Content" context is simply a value object containing the user id.
My question is that how should I implement this association using Doctrine 2? I have two solutions that both have their own issues:
Solution 1:
/**
* #ORM\Entity
* #ORM\Table(name="posts")
* #ORM\HasLifecycleCallbacks()
*/
class Post
{
...
/**
* #ORM\ManyToOne(targetEntity="UserBC\User", inversedBy="posts")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
...
}
Solution 2:
/**
* #ORM\Entity
* #ORM\Table(name="posts")
* #ORM\HasLifecycleCallbacks()
*/
class Post
{
...
/**
* #ORM\Column(type="integer")
*/
private $user_id;
...
}
In the first solution, "User" entity from "User" context has been used inside "Content" context that violates DDD rules on BCs being independent of each other. The second solution respects DDD rules but affects database schema (removes database-level relationship between "users" and "posts" tables through a Foreign key constraint).
So, what is the right way to implement such associations?
The second solution is correct.
As you correctly observe, associations between different BCs should be avoided. The right way to reference an entity in another BC is by ID.
This has the consequence that the BCs don't have constraints between them in the DB. After all, you try to make them independent. If you feel that this is wrong, then the only way around this is to reconsider your BC design, i.e. merge the two BCs. This is however a decision that should not be driven by code smells, but by your context map.
Note: Referencing entities from other BCs by ID is only allowed if they are aggregate roots. If the referenced entity is not an AR, you have another design smell right there. Not a serious one, but still one that needs consideration.
Also for me the second solution is the correct one. And I try to answer to the #EresDev question:
"In solution 2, how do you know that $ user_id is correct and already exists?"
To do this you should use events. For example you could dispatch a PostPublicationEvent which contains the user id as well as post data. This event is listened by the User BC. Here you can verify that the user exists and dispatch a new UserValidatedEvent which is listened to by the Post BC. Now you can publish your post knowing that the user is valid.

How do I obtain all documents referenced by a single document?

If I have a document Shop that has many Activities defined as ReferenceMany, is there a way I can directly query for the list of Activities for a Shop without hydrating a Shop instance?
For example:
{
"_id": fd390j09afj09dfj,
"activities": [
...
]
}
All I want is to be able to say "get me the array activities where _id is fd390j09afj09dfj, and hydrate them as Activity instances.
Here's the first solution I came up with:
/**
* Gets all activities configured for a shop.
*
* #param string $shopId
* #return \BikeShed\Domain\Activity[]|\Doctrine\Common\Collections\ArrayCollection
*/
public function findByShopId($shopId) {
/** #var \BikeShed\Domain\Repository\Shop $shopRepository */
$shopRepository = $this->dm->getRepository('BikeShed\Domain\Shop');
$shop = $shopRepository->findOneById($shopId);
return $shop->getActivities();
}
It's simply fetching the Shop and then getting all the Activities via the defined relation.
Here's a working example of how you would implement jmikola's last suggestion:
/**
* #param string $shopId
* #return ActivityModel[]
*/
public function findByShopId($shopId) {
$partialShopData = $this->dm->getRepository('BikeShed\Domain\Shop')->createQueryBuilder()
->hydrate(false)
->field('activities')
->getQuery()
->getSingleResult()
;
$activityIds = [];
if(!empty($partialShopData['activities']))
foreach($partialShopData['activities'] as $activity)
if(!empty($activity['$id']))
$activityIds[] = $activity['$id'];
return $this->createQueryBuilder()
->field('id')
->in($activityIds)
->getQuery()
->toArray()
;
}
You cannot directly query the Shop collection or (or ODM repository) and receive Activity instances; however, you can use the Query Builder API to specify a projection with select('activities'). The executed query will still return Shop instances, but the activities field should be the only thing hydrated (as a PersistentCollection of Activity instances). In this case, you shouldn't modify any of the non-hydrated Shop fields, as ODM will detect any non-null value as a change.
It should be trivial to add a convenience method on ShopRepository that issues the above query with its select() and returns the collection (or an array) of Activity documents instead of the Shop. Keeping the Shop inaccessible should also protect you from inadvertently modifying other non-hydrated fields within it.
The down-side with this method is that the Activities will be proxy objects and lazily loaded. You can mitigate this with reference priming. With priming, you'll end up doing two queries (one for the Shop and one for all referenced Activity documents).
Regarding your follow-up question about putting this method on the Activity repository, you do have another option. Firstly, I agree that ActivityRepository::findByShopId() is preferable to calling a method on ShopRepository that returns Activity objects.
Each repository has a reference to the document manager, which you can use to access other repositories via the getRepository() method. An ActivityRepository::findByShopId() could do the following:
Access the Shop repository through the document manager
Query for the Shop by its ID, projecting only the activities field and disabling hydration completely
Collect the identifiers from the activities array. Depending on whether the Activity references are simple or not, the elements in that array may be the raw _id values or DBRef objects.
Execute a query for all Activity objects (easy, since we're already in that repository) where the ID is $in the array of identifiers

How to map many to many relationship with composite key in symfony2 Doctrine ORM

I have this scenario
I have four classes
User
profiles
Activity
Workbook
user can have many profiles based on per year. Every year diff profile
User profile will have many to many with Activities
so there will be profile_activities table with profile_id and activity_id
Now User will do 1 workbook per activity per profile
so i am confused how to map in database
I mean for profile table , i can have
class profile
#many to many
protected $activities
many to one
protected $user
But in class workbook how to define foreign key which belongs to activity and profile relationship table
For every activity child has to complete workbook. How should i define that
#[one to one [PK of activity_profile table]
protected $workbook
Symfony provides you different methods for do this operation.
One of those methods is annotation. With annotation you can write directly into php classes this relationships and more (like db column's type, specify if an attribute is mandatory and so on ...)
So, let's took this example (because i dind't understand the relationships of your entities)
Consider two entities: User and Groups.
One user can belong to n Groups and a Groups can have m Users. This mean that we have to "break up" m-n cardinality into an m-1-n relationship.
In symfony (with doctrine; i don't know if with mongodb and similar is the same) you haven't to create this "intermediate table" as a php class. All you have to do is, with annotation into php classes involves, specify what tables are related and in what way.
Let's see how!
/**
* Acme\UserBundle\Entity\User
*
* #ORM\Table(name="users")
* #ORM\Entity()
*/
class User implements AdvancedUserInterface
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
[...]
/**
* #ORM\ManyToMany(targetEntity="Groups", inversedBy="users")
*
*/
private $groups;
[...]
}
As you can see, this is a part of a php class mapped into db table with doctrine (ORM).
This class han an attribute $groups that tells (take a look to annotations) that is a many-to-many relationship between this class and another class (identified by targetEntity) and inversedBy tells what attribute (db column; attribute if you talk about class) is involved into the relationship (external key).
/**
* #ORM\Table(name="groups")
* #ORM\Entity()
*/
class Groups implements RoleInterface
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
[...]
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="groups")
*/
private $users;
[...]
}
This entity is group entity and have the "other side" of relationship: $user
As you can see, there is #ORM\ManyToMany(targetEntity="User", mappedBy="groups") that indicates that realtionship is with class User and field into User class is groups.
Now you can run doctrine command for entity generation onto db php app/console doctrine:generate:entities Acme where Acme is bundle's name and the trick is done.
Some words on mappedBy and inversedBy:
There are "two" sides: the inverted side and the owning side. Remember that docrine will "observe" changes only into the owning side, so take care to place it into the right class
The inversed side is identified by mappedBy keyword and it's value is the name of owning side class
The owning side is identified with inversedBy keyword and it's value is the name of the inversed side class
manyToOne association has always the owning side
oneToMany association has always the inversed side
The owning side of oneToOne relationship is always the entity with external key
Into manyToMany relationship is the same

Mapping two tables to one entity in Doctrine2

I'm looking at using doctrine for an application I'm working on - but after reading the documentation I'm having trouble conceptualizing how to represent the database structure we have in terms of entities.
I have many tables which have partner tables which hold translation data like the following....
Where I would like to have one Entity (Navigation Element) which had access to the 'label' field depending on what Language I set in my application. The following from the Doctrine documentation seems to suggest that you need to define one (single) table which is used to persist an entity
http://www.doctrine-project.org/docs/orm/2.0/en/reference/basic-mapping.html
By default, the entity will be
persisted to a table with the same
name as the class name. In order to
change that, you can use the #Table
annotation as follows:
Or do I need to define two entities and link them (or allow the translation table to inherit from the element table).
And what strategy would I use to always insert a language_id clause to the Join (to ensure I'm pulling the right label for the currently set language). Is this something I would define in the entity itself, or elsewhere?
This seems to suit a One-To-Many Bidirectional association. This is the scenario from that page translated to your situation:
/** #Entity */
class NavigationElement
{
// ...
/**
* #OneToMany(targetEntity="NavigationElementTranslation", mappedBy="navigationElement")
*/
private $translations;
// ...
public function __construct() {
$this->translations = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class NavigationElementTranslation
{
// ...
/**
* #ManyToOne(targetEntity="NavigationElement", inversedBy="translations")
* #JoinColumn(name="navigation_element_id", referencedColumnName="id")
*/
private $navigationElement;
// ...
}
You could add a getLabel($languageId) method to the NavigationElement entity that searches through the translations to get the correct label:
public function getLabel($languageId) {
foreach($this->translations as $trans) {
if($trans->languageId == $languageId)
return $trans->label;
}
throw new InvalidArgumentException();
}
And you could use the following DQL to ensure you only load the translation you want into the $translations property:
$query = $em->createQuery(
"SELECT ne, net
FROM Entity\NavigationElement ne
JOIN ne.translations net WITH net.languageId = :langId"
);
$query->setParameter('langId', $languageId);
$navigationElements = $query->execute();
This situation sounds like one where you would want to cache aggressively. Make sure you look into Doctrine 2's caching mechanisms too.
Also, internationalization can be handled reasonably well in PHP with gettext if you find join tables for translations start to become unmanageable.
I would also direct anyone who has to tackle this same problem to take a look at the following doctrine extension.
http://www.gediminasm.org/article/translatable-behavior-extension-for-doctrine-2