Doctrine Compund Keys - php

I have these 3 table
"Business" with these fields: Id, Name, etc..
"City" with these fields: Id, Name, etc..
And then I have a table called BusinessCity (given that a bussines can be related to many cities). This table has the fields "BusinessId" and "CityId".
Im trying to relate the CityId to the City entity, and BusinessId to the business entity, on the class BusinessCity. I've been googling this for the past 3 days and couldnt find an answer, if this has been asked before im sorry i didnt see it. Could anyone help me or give me some pointers on how to get this done. Thanks in advance

What you are trying to achieve is a bi-directional many-to-many relation with a joinTable.
Many businesses can reside in multiple cities and in one city there can be multiple businesses.
In a many-to-many relationship either side can be the owning side. JoinTable definition can be left out and has sensible defaults but if you want to specify it concretely i included it in the example.
Business (in this example: owning side = inversedBy = JoinTable definition)
/**
* #ORM\ManyToMany(targetEntity="Your/Bundle/City", inversedBy="businesses",cascade="{persist,merge}" fetch="EAGER")
* #ORM\JoinTable(name="BusinessCity",
* joinColumns={#JoinColumn(name="business_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="city_id", referencedColumnName="id")}
* )
*/
protected $cities;
public function __construct()
{
$this->cities = new ArrayCollection();
}
public function getCities()
{
return $this->cities;
}
public function setCities(Collection $cities)
{
// using map with closure to have dublicate/type-checking provided by addCity
$this->cities->map(function($city) {
$this->addCity($city);
});
return $this;
}
public function addCity(CityInterface $city)
{
// ... you don't want dublicates in your collection
if (!$this->cities->contains($city)) {
$this->cities->add($city);
}
return $this;
}
public function removeCity(CityInterface $city)
{
$this->cities->removeElement($city);
return $this;
}
// other properties and methods ..
City (inverse side = mappedBy)
/**
* #ORM\ManyToMany(targetEntity="Your/Bundle/Business", mappedBy="cities")
*/
protected $businesses;
// getters & setters ...
// other properties and methods ...

This is actually pretty simple, all you have to do is define your JoinTable. It's not easy to find in the docs, but there is an example in the section Composite Primary Keys.
In short, all you have to do is use oneToMany/manyToOne-associations with the class representing your JoinTable instead of directly associating both Business and City with ManyToMany-associations.

Related

Laravel polymorphic-relationship many to many

I'm having some trouble figuring out the polymorphic relationships.
I've read the documentation but for me it is quite confusing.
Hope anyone has the time to help me a bit to understanding it.
What I'm trying to do is to have a very simple tag system for some wallpapers.
I started a new test project just to get this working.
I have 3 models: Wallpaper, Tag and WallpaperTag
class Wallpaper extends Model
{
protected $primaryKey = 'wallpaper_id';
protected $table = 'wallpapers';
protected $guarded = ['wallpaper_id'];
/**
* Get all the tags assigned to this wallpaper
*/
public function tags()
{
//
}
}
class Tag extends Model
{
protected $primaryKey = 'tag_id';
protected $table = 'tags';
protected $guarded = ['tag_id'];
/**
* Get all wallpapers that have this given tag
*/
public function wallpapers()
{
//
}
}
class WallpaperTag extends Model
{
protected $primaryKey = 'wallpaper_tag_id';
protected $table = 'wallpaper_tags';
protected $guarded = ['wallpaper_tag_id'];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
* Wallpaper relation
*/
public function wallpaper()
{
return $this->belongsTo('App\Wallpaper','wallpaper_id');
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
* Tag relation
*/
public function tag()
{
return $this->belongsTo('App\Tag','tag_id');
}
}
The wallpapers table in this test project contains only wallpaper_id
The tags table contanis a tag_id and a tag
The wallpaper_tags table contains a foreign key for both tags.tag_id and wallpapers.wallpaper_id
I've set it up like this so wallpapers can share tags without duplicating them. The problem is that I really dont understand the polymorphic relations and the example in the documentation.
Can anyone here 'spoonfeed' how this would work? :') Thanks in advance for all help.
So you are trying to create a relationship with ManyToMany between 2 tables, which in the DB needs a 3rd table to allow you to create such relationship.
This is due to the fact that one Wallpaper can have many Tag and vice versa! For such you need a 3rd table that holds that information accordingly.
The 3rd table is only holding ids in relationship to your 2 main tables. This allows the flexibility you are looking for, while your Object tables can actually hold information specific to them, without you having to duplicate it.
If you were to store the relationship ids on both tables you would be forced to duplicate your data and that is just something you do not wish on databases! Imagine having to update 1000 rows because it is basically the same wallpaper but with so many different tags.
Anyway, below is the code that should be get you going:
You do need to create a class to represent your relationship table (Kudos on the WallpaperTag class! That is the one!);
You do not touch that class anymore, do not add belongs or any other function!
You create the relationships on the main classes Wallpaper and Tag;
class Wallpaper extends Model
{
...
public function tags()
{
return $this->belongsToMany('App\Tag', 'wallpaper_tag', 'tag_id', 'wallpaper_id');
}
}
class Tag extends Model
{
...
public function wallpapers()
{
return $this->belongsToMany('App\Wallpaper', 'wallpaper_tag', 'wallpaper_id', 'tag_id');
}
}
class WallpaperTag extends Model
{
}
Laravel should create a relationship between your classes and map it accordingly to the correct 3rd table to sort the search for you.
If you follow the semantics all you needed was the class name. If ids are to change, then you will need to start telling Laravel what id column names it should be looking for as you deviate from the normal behaviour. It still finds it, just needs some guidance on the names! Hence why we start adding more parameters to the relationships belongsTo or hasMany etc :)
Pivot Table Migration
You do not need an id for your pivot table since your primary key is a combination of the two foreign keys from the other tables.
$table->bigInteger('wallpaper_id')->unsigned()->nullable();
$table->foreign('wallpaper_id')->references('wallpaper_id')
->on('wallpaper')->onDelete('cascade');
$table->bigInteger('tag_id')->unsigned()->nullable();
$table->foreign('tag_id')->references('tag_id')
->on('tags')->onDelete('cascade');
Let me know if it helped! :3

#ORM\OneToMany Is retrieving the virtual deleted entries

I have a relation in Doctrine2 #ORM\OneToMany, suposing that i have table school and student, in the entity school i have the #ORM\OneToMany column students,
and i also have a virtual deletion column deleted_at, so every student that has the deleted_at different of null is a deleted student that is supposed not to appear in the column #ORM\OneToMany $students. How can i make this filter?
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="App\Oceano\Entities\Student",
* mappedBy="cartCore",
* cascade={"all"}
* )
*/
private $students;
So, when i call for school students, it is retrieving also the deleted ones.
$schoolObj->getStudents();
Any Solution using annotation or some clean change?
You practically described Laravel's soft deleting feature. So, if you use it, you do not need to do anything and soft deleted students will not appear. You just need to add Illuminate\Database\Eloquent\SoftDeletes trait to the Student model.
If you're using some own functionality, create a local scope in the Student model:
public function scopeNotDeleted($query)
{
return $query->whereNull('deleted_at');
}
And use it:
Student::notDeleted()->get();
Or:
$school->students()->notDeleted()->get();
You can use Criteria filter class in your entity to students collection which are not deleted
protected getStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->eq('deleted_at', null));
return $this->students->matching($criteria);
}
To get deleted students you could write it like
protected getDeletedStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->neq('deleted_at', null));
return $this->students->matching($criteria);
}
How filter data inside entity object in Symfony 2 and Doctrine

Symfony Array Collection as Entity Property

Is there a way to associate an ArrayCollection to a database column created by an Entity?
For example, I have two entities: Household and Pet Type.
Household current has a property for Pet Type, but it expects the Pet Type object, so only one can chosen at the moment.
I'd like Household to be able to have multiple Pet Types. So instead of having to choose between a Dog or a Cat, they can choose Dog AND Cat.
I have tried doing this, but I get the following error:
Catchable Fatal Error: Argument 1 passed to
Acme\CoreBundle\Entity\Household::setPetType() must be an instance of
Acme\CoreBundle\Entity\PetType, instance of
Doctrine\Common\Collections\ArrayCollection given
I'm assuming I'd need to change property for petType in the Household entity in order to associate to more than one pet type?
From your description it seems that Household and PetType has a cardinality of m-to-one; that means that an Household record could have only a PetType while a PetType could be associated to more than one Household record.
From DB point of view that means foreign key into Household table. If you want to make possible a "multiple" connection between Household and PetType, you have to modify your relationship between entities.
Just an example (disclaimer: your entities could be named differently and I didn't test this code. I'm explaining here a concept, not working on runnable code as your example didn't came with snippet examples)
class Household
{
//some properties
/**
* #ORM\ManyToMany(targetEntity="PetType", inversedBy="households")
* #ORM\JoinTable(name="household_pettype")
*/
$pet_types;
//some methods
public function addPetType(PetType $petType)
{
$this->pet_types[] = $petType;
return $this;
}
public function setPetTypes(ArrayCollection $petTypes)
{
$this->pet_types = $petTypes;
return $this;
}
public function removePetType(PetType $petType)
{
$this->pet_types->removeElement($petType);
}
public function getPetTypes()
{
return $this->pet_types;
}
}
class PetType
{
//some properties
/**
* #ORM\ManyToMany(targetEntity="Household", mappedBy="pet_types")
*/
$households;
//some methods
public function addHousehold(Household $household)
{
$this->households[] = $household;
return $this;
}
public function setHouseholds(ArrayCollection $households)
{
$this->households = $households;
return $this;
}
public function removeHousehold(Household $household)
{
$this->households->removeElement($household);
}
public function getHousehold()
{
return $this->households;
}
}
After that you need to run again
php app/consolle doctrine:schema:update --force
This will update your DB schema and, because new cardinality is m-to-n, a relationship table named household_pettype will be created (that will hold only foreign keys from other two tables)
After that you could alternatively use two methods (from household point of view)
->addPetType($petType); that will append a PetType object to
Household collection
->setPetTypes($petTypeArrayCollection); that will set in a shot all
PetTypes

Doctrine's Many-To-Many Self-Referencing and reciprocity

By default, self-referencing ManyToMany relationships under Doctrine involve an owning side and an inverse side, as explained in the documentation.
Is there a way to implement a reciprocal association whithout difference between both sides?
Following the example in the docs:
<?php
/** #Entity **/
class User
{
// ...
/**
* #ManyToMany(targetEntity="User")
**/
private $friends;
public function __construct() {
$this->friends = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
So, adding entity1 to entity2s friends implies that entity2 will be in entity1s friends.
There are a number of ways to solve this problem, all depending on what the requirements for the "friends" relation are.
Unidirectional
A simple approach would be to use a unidirectional ManyToMany association, and treat it as if it where a bidirectional one (keeping both sides in sync):
/**
* #Entity
*/
class User
{
/**
* #Id
* #Column(type="integer")
*/
private $id;
/**
* #ManyToMany(targetEntity="User")
* #JoinTable(name="friends",
* joinColumns={#JoinColumn(name="user_a_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="user_b_id", referencedColumnName="id")}
* )
* #var \Doctrine\Common\Collections\ArrayCollection
*/
private $friends;
/**
* Constructor.
*/
public function __construct()
{
$this->friends = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #return array
*/
public function getFriends()
{
return $this->friends->toArray();
}
/**
* #param User $user
* #return void
*/
public function addFriend(User $user)
{
if (!$this->friends->contains($user)) {
$this->friends->add($user);
$user->addFriend($this);
}
}
/**
* #param User $user
* #return void
*/
public function removeFriend(User $user)
{
if ($this->friends->contains($user)) {
$this->friends->removeElement($user);
$user->removeFriend($this);
}
}
// ...
}
When you call $userA->addFriend($userB), $userB will be added to the friends-collection in $userA, and $userA will be added to the friends-collection in $userB.
It will also result in 2 records added to the "friends" table (1,2 and 2,1). While this can be seen as duplicate data, it will simplify your code a lot. For example when you need to find all friends of $userA, you can simply do:
SELECT u FROM User u JOIN u.friends f WHERE f.id = :userId
No need to check 2 different properties as you would with a bidirectional association.
Bidirectional
When using a bidirectional association the User entity will have 2 properties, $myFriends and $friendsWithMe for example. You can keep them in sync the same way as described above.
The main difference is that on a database level you'll only have one record representing the relationship (either 1,2 or 2,1). This makes "find all friends" queries a bit more complex because you'll have to check both properties.
You could of course still use 2 records in the database by making sure addFriend() will update both $myFriends and $friendsWithMe (and keep the other side in sync). This will add some complexity in your entities, but queries become a little less complex.
OneToMany / ManyToOne
If you need a system where a user can add a friend, but that friend has to confirm that they are indeed friends, you'll need to store that confirmation in the join-table. You then no longer have a ManyToMany association, but something like User <- OneToMany -> Friendship <- ManyToOne -> User.
You can read my blog-posts on this subject:
Doctrine 2: How to handle join tables with extra columns
More on one-to-many/many-to-one associations in Doctrine 2

Doctrine2, using 2 tables in 1 entity

I'm looking for a way to use multiple tables (One-to-one) in one Doctrine 2 Entity class. Can this be achieved using plain annotations? Adding more classes is not something what I want to do.
I have the following table structure:
Attribute:
id
type_id
value
AttributeType:
id
name
unit
What I would like to do is create an entity which can basically call getters and setters for the 2 tables from the same class, without having to create separate entity classes, e.g.:
<?php
class Attribute {
public function getName(){ return $this->name; } // From AttributeType
public function getValue(){ return $this->value; } // From Attribute
}
?>
Any help is greatly appriciated.
I think this is what you are looking for
/**
* #OneToOne(targetEntity="AttributeType")
* #JoinColumn(name="type_id", referencedColumnName="id")
*/
Refer Docmentation for more details

Categories