Reputation: 4730
I'm trying to build a doctrine database for a Symfony application, and I'm not sure of the correct way of assembling the schema.
From here on in I'll use the Symfony3 tutorial Product
and Category
entities to illustrate my confusion:
// src/AppBundle/Entity/Category.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="category")
*/
class Category
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=100)
*/
private $type;
}
How do I define type
so that it is one of a select few values (say: ["car", "bike", "bus"]
)?
My first initial thought was to have validation inside Symfony itself, such that before pushing to the database a check occurs.
However, this feels slightly hacky to me, and I'm wondering whether the correct procedure would be to enforce this constraint inside Doctrine itself.
Can someone shed some light on the best way to do this?
My problem further complicates itself when I have a one-to-many relationship between Category
and Product
, and Product
itself has a type
variable. My issue is that the valid values for Product:type
differ depending on Category:type
, for example:
Category:type = "car" -> Product:type = ["BMW" OR "Volvo"]
Category:type = "bike" -> Product:type = ["BMX" OR "Mountain Bike"]
Should this all be done using form/controller validation, or am I correct in assuming that it should be handled at a "lower" level?
Upvotes: 1
Views: 538
Reputation: 21661
I think you should create a separate product type table that stores the category relationship and is itself stored in the product instead of putting the category in the product.
So you would have
Category
Product Type
Product
Product type is many to one categories, a category may contain many product types, a Product is many to one product types, a product type may contain many products.
Category Type -> Product Type -> Product
Make sense, there is no need to store the category in the product because it is inferred by the product type and its relationship.
Just to clarify,
Category
--------
id //such as 1
title //such as Car
description
Product Type
--------
id //such as 1
category_id // id from Category
title //such as BMW
description
Product
--------
id //such as 6
product_type_id // id from Product Type
title //2016 New BMW
description
Then with a join you could do
Select
c.title As category, pt.title As type, p.title
From products As p
Join product_type As pt
ON p.product_type_id = pt.id
Join category As c
ON p.category_id = c.id
Where
p.id = 6
Then it should say
['category'=>'Car', 'type' => 'BMW', 'title'=> '2016 New BMW']
Now if you want product types to be in more then one category you could /should do it this way
Category
--------
id //such as 1
title //such as Car
description
Category Type ///Bridge table, (category_id & type_id are unique togather )
--------
id // you could leave this id out, but it simplifies some things (see below)
category_id // id from Category
type_id // id from Type
Type
--------
id //such as 1
title //such as BMW
description
Product
--------
id //such as 6
category_type_id // id from Category Type
title //2016 New BMW
description
It makes things more complicated but that's what happens when you have complex relation ships like Many to Many. For the query you would add that
Select
c.title As category, t.title As type, p.title
From
products As p
Join
type As t
ON p.category_type_id = ct.id
Join
category_type As pt
ON t.id = ct.type_id
Join
category As c
ON pt.category_id = c.id
Where
p.id = 6
Of course you probably dont want to use title in them all, i was just not feeling particularly creative. In this last one if you can use the bridge table ID you can skip one of the tables if you dont need to reference it. So if you only wanted the category and product info,
Select
c.title As category, p.title
From
products As p
Join
category_type As ct
ON p.category_type_id = ct.id
Join
category As c
ON ct.category_id = c.id
Where
c.id = 6
This pattern is essentially like a Y
with the type and category at the top and the product on the bottom, and the nexus ( where they join ) the category_type table. Where if you don't use an id there ( in category type ) its liner, and you wouldn't be able to skip over one of the tables ( from c to p ). That would be ok because the two foreign keys ( category_id and type_id ) should be unique when combined ( compound key ). So its like this
Many to Many ( 1 ) Y shape ( not sure the technical term lol )
Category->
CategoryType -> Product
Type ->
Vs Many to Many ( 2 ) Liner
Category-> CategoryType -> Type -> Product
So in the first one ( 1 ) you can see you can leave one side off like this
CategoryType -> Product
Type ->
OR ( as shown in the above query )
Category->
CategoryType -> Product
Vs in the second ( 2 )
Category-> CategoryType -> Product
You would have a hole, that you couldn't jump. From CategoryType to Product, because there is not a reference to the Category or the Category Type in the Product record and vise versa. The drawback though is you can only have one category_type per product. Where if you use the liner one you have one type per product. Visual the second one is like a funnel.
In any case then you have a many to many between Category and Type, so you could have Many categories with many (product) types. For example you may have product types of
Clothes -> Adidas
Footwear -> Adidas
Clothes -> Levis
So the record ( Type ) of Adidas belongs to both Clothes and Footwear ( Categories ) but Clothes also has other types such as Levis. So it is Many to Many, this is a bit harder to deal with but it's probably closer to what you need, which is why I wanted to explain it as best I could.
I guess in summery there are many ways to do this, you just need to consider beforehand what the relationship is and how flexible you need it. The last one I will do is what I would call tags.
Tag
--------
id //such as 1
title //such as Car
description
Product Tags
--------
tag_id // id from Tag
product_id // id from Product
Product
--------
id //such as 6
product_type_id // id from Product Type
title //2016 New BMW
description
Here we have a many to many between the product and the tag, so you can put as many tags on as many products as you want. But you lose the structure of Adidas always being Footware, or Clothes for example.
The "ultimate" would be to have a bridge table between all three parts.
Category -> ( bridge ) -> Type -> ( bridge ) -> product.
That gets very complicated, but you could have 2 many to many relationships so products could belong to many types, which could belong to many categories. Personally I would go with the Tags, its almost as flexible and easier to deal with. It just takes more selection when creating the products.
Upvotes: 1