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
Related
I have a problem, it's for a school project and I need to allow duplications of the same relation between two entities on my app using Symfony 5 & Doctrine & postgresql .
I have a basicly a ManyToMany relation between Order and Products, I don't want to add fields for quantity, so I'm looking to count the number of occurences of the a same relation id_order & id_product on my order_product table, but I can't persist more than one same relation between order & product.
I searched and mainly saw people tryng to avoid duplications of the same relation, i'm looking for the exact contrary.
Thx
When using relation with Many on at least one side of the relation, you get Collection on the opposite side. On the collection you can call count() method.
So if you need to calculate quantity of Products in your Order, your Order entity can look like this:
/** #Entity */
class Order
{
...
/**
* #ManyToMany(targetEntity="Product", inversedBy="orders")
* #JoinTable(name="order_product")
*/
private $products;
public function __construct()
{
$this->products = new ArrayCollection();
}
public function countProducts(): int
{
return $this->products->count();
}
public function countProductsById(int $productId): int
{
return $this->products->filter(static function(Product $product) use ($productId) {
return $product->getId() === $productId;
})->count();
}
...
}
PS: Also be aware that word Order is a reserved word in PostgreSQL. You need to either name your Order entity differently or escape the naming correctly.
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
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
I'm building an app where clients can create a document from a pre-defined template, edit some fields with their own text and save it. I've sketched out the relations as I think they would be, and it's mostly fine to convert into Laravel:
The only question I have is how I'd handle the FieldValue relationship. The idea is that the Template defines all the fields, then rather than re-create these on each Document, it should just look to its Template for them. That would mean the FieldValue needs to look up to its Document, to the Template of that and find the corresponding Field from there.
Is there a clean way to implement this, or is there a better way of designing the relationship to make it more practical to implement?
Going by your diagram, looks like a pivot table with pivot data...
Which would generally be modeled like this in Laravel:
class Document extends Model
{
public function template()
{
return $this->belongsTo('App\Template');
}
public function fields()
{
return $this->belongsToMany('App\Field')->withPivot('value');
}
}
class Template extends Model
{
public function organisation()
{
return $this->belongsTo('App\Organisation');
}
public function documents()
{
return $this->hasMany('App\Document');
}
public function fields()
{
return $this->hasManyThrough('App\Field', 'App\Section');
}
public function sections()
{
return $this->hasMany('App\Section');
}
}
class Section extends Model
{
public function fields()
{
return $this->hasMany('App\Document')->withPivot('value');
}
public function template()
{
return $this->belongsTo('App\Template');
}
}
class Field extends Model
{
public function documents()
{
return $this->belongsToMany('App\Document')->withPivot('value');
}
public function section()
{
return $this->belongsTo('App\Section');
}
}
class Organisation extends Model
{
public function documents()
{
return $this->hasManyThrough('App\Document', 'App\Template');
}
public function templates()
{
return $this->hasMany('App\Template');
}
}
With related tables (if sticking with laravel defaults):
fields
id - integer
section_id - integer
documents
id - integer
template_id - integer
templates
id - integer
organisation_id - integer
sections
id - integer
template_id - integer
organisations
id - integer
document_field
id - integer
document_id - integer
field_id - integer
value - string
Then you can access things many different ways. Here is one example:
$user = App\User::find(3);
$organisation = $user->organisation;
foreach ($organisation->documents as $document)
{
foreach ($document->fields as $field)
{
echo $field->pivot->value;
}
}
And inserting:
$field = App\Field::find(2);
$document = App\Document::find(4);
$value = 'My field value';
$document->fields()->save($field, ['value' => $value]);
Relevant docs:
Many-to-many relationships
Querying relationships
Inserting related models
Working with pivot tables
Hope this is what you meant:
$doc = Document::findOrFail(Input::get('docId'));
$sections = $doc->template->sections;
$fieldValues = $doc->field values;
Now you simply run on the field values and get the field and the section and start placing stuff.
For better performance I would eager load the fieldValue parameters with:
->with('field');
To give a answer to you're first question:
I suggest that you call it content. You can handle the content as you're doing now. The relationship as is is good enough. You've to do that separated from the other tables.
Second question: a better way
I've created a ERD myself based on you're ERD:
I've changed a few thinks.
Not so important but a document is from a user.
Templates can have sub templates. The consequence of this is that you can reuse sub templates. E.g. if you've a logo you can just place that in you're document every time.
Due to the new template table you don't need sections anymore. With the new approach you can define sub sections/templates.
Properties are all put into one table. With this approach you can define infinite properties for fields. This can be allot more flexible. These are referenced to a field and the content. The content_id however isn't needed. I've just placed it there so you can easily check which field it applies to.
The main point of you're question was the FieldValue/Content. The content is referenced to the document. From the document you can fill in the fields of the template.
I hope this is clear for you. The advantages I see are:
More flexible in properties
Content lookup is easy and referenced to a field
I haven't changed anything on the content table. Just leave it as is. It's good that way! Hope this helps you. If you use Schema designer you can retrieve you're models very easily!
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.