Inheritance Mapping and Many-To-One Relation - orm

I made an abstract Upload class with 4 different children.
The classes UploadCompanyPic, UploadCompanyLogo and UploadUserPic are one-to-one relations, but the UploadPostFile has to be Many-to-one (one post has many uploaded files), I want to use alle the functions that are in the abstract class, but I can't get the Many-to-one relation to work.
This is what I have since now:
<?php
namespace AppBundle\Entity\Upload;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Util\Registry;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Abstract Class Upload
*
* #package AppBundle\Entity\Upload
*
* #ORM\MappedSuperclass()
* #ORM\Entity(repositoryClass="AppBundle\Repository\Upload\UploadRepository")
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="upload", indexes={
* #ORM\Index(name="idx_cat_id", columns={"category","foreign_id"})
* }
* )
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="category", type="string")
* #ORM\DiscriminatorMap({"company-pic" = "UploadCompanyPic", "user-pic" = "UploadUserPic", "company-logo" = "UploadCompanyLogo", "post-file" = "UploadPostFile"})
*/
abstract class Upload
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(name="id", type="integer", length=255, nullable=false)
*/
protected $id;
protected $foreignId;
/**
some more code
* #return mixed
*/
public function getForeignId()
{
return $this->foreignId;
}
/**
* #param mixed $foreignId
*/
public function setForeignId($foreignId)
{
$this->foreignId = $foreignId;
}
UploadCompanyPic:
<?php
namespace AppBundle\Entity\Upload;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Upload\Upload as Upload;
/**
*
* #ORM\Entity
*
*/
class UploadCompanyPic extends Upload
{
protected $category="company-pic";
/**
* this is the foreign id of the mapped entity
* #ORM\Column(name="foreign_id", type="integer", length=255, nullable=false)
*/
protected $foreignId;
}
(Upload CompanyLogo and UploadUserPic look the same)
<?php
namespace AppBundle\Entity\Upload;
use AppBundle\Entity\Blog\Post;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Upload\Upload as Upload;
/**
*
* #ORM\Entity
*
*/
class UploadPostFile extends Upload
{
protected $category="post-file";
/**
* ManyFiles have one Post
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Blog\Post", inversedBy="files")
* #ORM\JoinColumn(name="foreign_id", referencedColumnName="id")
*/
protected $foreignId;
/**
* #return ArrayCollection
*/
public function getForeignId()
{
return $this->foreignId;
}
/**
* #param Post $foreignId
*/
public function setForeignId($foreignId)
{
$this->foreignId = $foreignId;
}
}
Any suggestions for me? Thanks!

One my colleagues found an answer:
/**
* Abstract Class Upload
*
* #package AppBundle\Entity\Upload
*
* #ORM\MappedSuperclass()
* #ORM\Entity(repositoryClass="AppBundle\Repository\Upload\UploadRepository")
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="upload", indexes={
* #ORM\Index(name="idx_cat_id", columns={"category","foreign_id"})
* }
* )
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="category", type="string")
* #ORM\DiscriminatorMap({"company-pic" = "UploadCompanyPic", "user-pic" = "UploadUserPic", "company-logo" = "UploadCompanyLogo", "post-file" = "UploadPostFile", "partner-logo" = "UploadPartnerLogo"})
*/
abstract class Upload

Related

Symfony 4 + JMS Serializer - Internal Server Error

I have a problem with serialization data.
I have two entities, which are connection relation.
These are my entities:
Task.php
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* #ORM\Entity
*/
class Task
{
/**
*
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*
*/
private $id;
/**
* #JMS\MaxDepth(1)
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="task")
*
*/
private $user;
User.php
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="app_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Task", mappedBy="user")
*/
private $task;
public function __construct()
{
I would like to create API, and I serialization my data with JMS. I get this error:
Internal Server Error
The server encountered an internal error or misconfiguration and was unable to complete your request.
This is my controller:
public function getAllAction(): JsonResponse
{
$tasks = $this->taskService->getAll();
$serializer = SerializerBuilder::create()->build();
$data = $serializer->serialize($tasks, 'json',
SerializationContext::create()->enableMaxDepthChecks());
return new JsonResponse($data, 200, [], true);
}
Function getAll in my controller return data with table Task.
Please, help me with my problem. :)
Thanks, Friends.
I did not use JMS Serializer but i can suggest different solution like symfony serializer component.
public function getAllAction(SerializerInterface $serializer): JsonResponse
{
$data = $serializer->serialize($this->taskService->getAll(), 'json');
return new JsonResponse($data, 200, [], true);
}
Optional you can set serialization group to avoid circular reference error.
For each entity atribute that you want to serialize, set:
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Column(name="title", type="string", nullable=false, length=150)
* #Assert\Type("string")
* #Assert\NotBlank()
* #Groups({"group_name"})
*/
private $title;
/**
* #ORM\Column(name="description", type="string", nullable=true, length=255)
* #Assert\Type("string")
*/
private $description;
And then you serialize only this field with specific group:
$serializer->serialize($this->taskService->getAll(), 'json', ['groups' => ['group_name']])
Doc: https://symfony.com/doc/4.1/serializer.html#using-serialization-groups-annotations
PS. Action suffix isnt required now
https://symfony.com/doc/current/best_practices/controllers.html#controller-action-naming

Sylius - Extend Product fixtures from CoreBundle

I've extended the Product model from CoreBundle (v1.0.0-alpha) and I want to change the behavior of the fixtures based on this new model. Keeping in mind that CoreBundle\Fixture\Factory\ProductExampleFactory is final, I am looking for a way to extend the fixtures so I don't have to rewrite the whole class and the whole "default" suite.
AppBundle\Entity\Product
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\Product as BaseProduct;
/**
* Product
*
* #author leogout
*
* #ORM\Table(name="sylius_product")
* #ORM\Entity
*/
class Product extends BaseProduct
{
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Package", mappedBy="component")
*/
protected $packages;
/**
* #var boolean
*
* #ORM\Column(type="boolean")
*/
protected $exposed;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Customization", mappedBy="product")
* #ORM\JoinColumn(name="customization_id", referencedColumnName="id")
*/
protected $customizations;
/**
* Product constructor.
*/
public function __construct()
{
parent::__construct();
$this->packages = new ArrayCollection();
$this->customizations = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set exposed
*
* #param boolean $exposed
* #return Product
*/
public function setExposed($exposed)
{
$this->exposed = $exposed;
return $this;
}
/**
* Get exposed
*
* #return boolean
*/
public function getExposed()
{
return $this->exposed;
}
/**
* Add packages
*
* #param Package $packages
* #return Product
*/
public function addPackage(Package $packages)
{
$this->packages[] = $packages;
return $this;
}
/**
* Remove packages
*
* #param Package $packages
*/
public function removePackage(Package $packages)
{
$this->packages->removeElement($packages);
}
/**
* Get packages
*
* #return ArrayCollection
*/
public function getPackages()
{
return $this->packages;
}
/**
* Add customizations
*
* #param Customization $customizations
* #return Product
*/
public function addCustomization(Customization $customizations)
{
$this->customizations[] = $customizations;
return $this;
}
/**
* Remove customizations
*
* #param Customization $customizations
*/
public function removeCustomization(Customization $customizations)
{
$this->customizations->removeElement($customizations);
}
/**
* Get customizations
*
* #return ArrayCollection
*/
public function getCustomizations()
{
return $this->customizations;
}
}
Thanks in advance !
EDIT :
After some digging, I saw that I needed to code a whole new fixture with a new ProductFixture, a new ProductExampleFactory and a new MyCustomProductFixture on top of that with their own config files which is not ideal... Do you have a better solution ?

A choice list based on database values in sonata

is it possible to add a choice list in configureformfields with choices values mapped from the database instead of configuring it manually like this :
->add('testfield', 'choice', array('choices' => array(
'1' => 'choice 1',
'2' => 'choice 2',)))
if the entity is correctly mapped then you can just use:
->add('testfield')
and Sonata admin will do the job.
Let's say you have a Product class linked to a Category class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table(name="product")
*
*/
class Product
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
*/
protected $category;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set category
*
* #param Category $category
*
* #return Product
*/
public function setCategory(Category $category = null)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return Category
*/
public function getCategory()
{
return $this->category;
}
}
Simply using:
->add('category')
will provide a select form field with all the categories.
You can also use SONATA_TYPE_MODEL if you want something more advanced:
<?php
// src/AppBundle/Admin/ProductAdmin.php
class ProductAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$imageFieldOptions = array(); // see available options below
$formMapper
->add('category', 'sonata_type_model', $imageFieldOptions)
;
}
}
The documentation is on this page: Form Types
Hope this helps!

Unknown Entity namespace alias in symfony2

Hey I have two bundles in my symfony2 project. one is Bundle and the other one is PatentBundle.
My app/config/route.yml file is
MunichInnovationGroupPatentBundle:
resource: "#MunichInnovationGroupPatentBundle/Controller/"
type: annotation
prefix: /
defaults: { _controller: "MunichInnovationGroupPatentBundle:Default:index" }
MunichInnovationGroupBundle:
resource: "#MunichInnovationGroupBundle/Controller/"
type: annotation
prefix: /v1
defaults: { _controller: "MunichInnovationGroupBundle:Patent:index" }
login_check:
pattern: /login_check
logout:
pattern: /logout
inside my controller i have
<?php
namespace MunichInnovationGroup\PatentBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use JMS\SecurityExtraPatentBundle\Annotation\Secure;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\Security\Core\SecurityContext;
use MunichInnovationGroup\PatentBundle\Entity\Log;
use MunichInnovationGroup\PatentBundle\Entity\UserPatent;
use MunichInnovationGroup\PatentBundle\Entity\PmPortfolios;
use MunichInnovationGroup\PatentBundle\Entity\UmUsers;
use MunichInnovationGroup\PatentBundle\Entity\PmPatentgroups;
use MunichInnovationGroup\PatentBundle\Form\PortfolioType;
use MunichInnovationGroup\PatentBundle\Util\SecurityHelper;
use Exception;
/**
* Portfolio controller.
* #Route("/portfolio")
*/
class PortfolioController extends Controller {
/**
* Index action.
*
* #Route("/", name="v2_pm_portfolio")
* #Template("MunichInnovationGroupPatentBundle:Portfolio:index.html.twig")
*/
public function indexAction(Request $request) {
$portfolios = $this->getDoctrine()
->getRepository('MunichInnovationGroupPatentBundle:PmPortfolios')
->findBy(array('user' => '$user_id'));
// rest of the method
}
Edit:
My Entity Class
<?php
namespace MunichInnovationGroup\PatentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* MunichInnovationGroup\PatentBundle\Entity\PmPortfolios
*
* #ORM\Table(name="pm_portfolios")
* #ORM\Entity
*/
class PmPortfolios
{
/**
* #var string $id
*
* #ORM\Column(name="id", type="string", length=36, nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #var string $portfolioName
*
* #ORM\Column(name="portfolio_name", type="string", length=255, nullable=false)
*/
private $portfolioName;
/**
* #var text $description
*
* #ORM\Column(name="description", type="text", nullable=true)
*/
private $description;
/**
* #var string $permalink
*
* #ORM\Column(name="permalink", type="string", length=255, nullable=false)
*/
private $permalink;
/**
* #var string $sharingCode
*
* #ORM\Column(name="sharing_code", type="string", length=255, nullable=false)
*/
private $sharingCode;
/**
* #var boolean $shared
*
* #ORM\Column(name="shared", type="boolean", nullable=false)
*/
private $shared;
/**
* #var integer $sharedPortfolioCalls
*
* #ORM\Column(name="shared_portfolio_calls", type="integer", nullable=true)
*/
private $sharedPortfolioCalls;
/**
* #var boolean $isDefault
*
* #ORM\Column(name="is_default", type="boolean", nullable=false)
*/
private $isDefault;
/**
* #var UmUsers
*
* #ORM\ManyToOne(targetEntity="UmUsers")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $user;
/**
* Get id
*
* #return string
*/
public function getId()
{
return $this->id;
}
/**
* Set portfolioName
*
* #param string $portfolioName
*/
public function setPortfolioName($portfolioName)
{
$this->portfolioName = $portfolioName;
}
/**
* Get portfolioName
*
* #return string
*/
public function getPortfolioName()
{
return $this->portfolioName;
}
/**
* Set description
*
* #param text $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* Get description
*
* #return text
*/
public function getDescription()
{
return $this->description;
}
/**
* Set permalink
*
* #param string $permalink
*/
public function setPermalink($permalink)
{
$this->permalink = $permalink;
}
/**
* Get permalink
*
* #return string
*/
public function getPermalink()
{
return $this->permalink;
}
/**
* Set sharingCode
*
* #param string $sharingCode
*/
public function setSharingCode($sharingCode)
{
$this->sharingCode = $sharingCode;
}
/**
* Get sharingCode
*
* #return string
*/
public function getSharingCode()
{
return $this->sharingCode;
}
/**
* Set shared
*
* #param boolean $shared
*/
public function setShared($shared)
{
$this->shared = $shared;
}
/**
* Get shared
*
* #return boolean
*/
public function getShared()
{
return $this->shared;
}
/**
* Set sharedPortfolioCalls
*
* #param integer $sharedPortfolioCalls
*/
public function setSharedPortfolioCalls($sharedPortfolioCalls)
{
$this->sharedPortfolioCalls = $sharedPortfolioCalls;
}
/**
* Get sharedPortfolioCalls
*
* #return integer
*/
public function getSharedPortfolioCalls()
{
return $this->sharedPortfolioCalls;
}
/**
* Set isDefault
*
* #param boolean $isDefault
*/
public function setIsDefault($isDefault)
{
$this->isDefault = $isDefault;
}
/**
* Get isDefault
*
* #return boolean
*/
public function getIsDefault()
{
return $this->isDefault;
}
/**
* Set user
*
* #param MunichInnovationGroup\PatentBundle\Entity\UmUsers $user
*/
public function setUser(\MunichInnovationGroup\PatentBundle\Entity\UmUsers $user)
{
$this->user = $user;
}
/**
* Get user
*
* #return MunichInnovationGroup\PatentBundle\Entity\UmUsers
*/
public function getUser()
{
return $this->user;
}
}
My bundle main class: MunichInnovationGroupPatentBundle.php
<?php
namespace MunichInnovationGroup\PatentBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MunichInnovationGroupPatentBundle extends Bundle
{
}
when i try to load localhost/web/app_dev.php/portfolio
It says
Unknown Entity namespace alias 'MunichInnovationGroupPatentBundle'.
I am unable to figure out this error
please help me if anyone has any idea I googled it a lot :(
Thanks in advance
500 Internal Server Error - ORMException
Please, check your config.yml.
Reviewed in section mappings of entity_managers.
You should have something like MunichInnovationGroupPatentBundle: ~
That is:
doctrine:
orm:
entity_managers:
defaults:
mappings:
MunichInnovationGroupPatentBundle: ~
In my case I was missing namespace name in the security.yml under providers
I had:
entity: { class: AdministratorBundle:AdminUser }
and needed to have:
entity: { class: NamespaceAdministratorBundle:AdminUser }
If you use 2 or more entity managers -- you need to specify manager also
getManager('YourManager')
$repository =
$this->getDoctrine()
->getManager('YourManager')
->getRepository('YourBundle:YourEntity');
Check you bundle logical name (MunichInnovationGroupPatentBundle). Bundle logical name is name of main class of your bundle, e.g. JobsBundle
and provide your entity sourcecode.
Documentation here states you can use the string 'MunichInnovationGroupPatentBundle:PmPortfolios' as shortcut to 'MunichInnovationGroupPatentBundle\Entity\PmPortfolios' as long as your entity lives under the Entity namespace of your bundle.
Your bundle is MunichInnovationGroupBundle so instead of
->getRepository('MunichInnovationGroupPatentBundle:PmPortfolios')
use
->getRepository('MunichInnovationGroupPatentBundle\Entity\PmPortfolios')
Try being more explicit in your config.yml file by adding some fields:
orm:
...
mappings:
MunichInnovationGroupPatentBundle:
type: annotation
dir: "MunichInnovationGroupPatentBundle/Controller"
is_bundle: true
prefix: MunichInnovationGroup\PatentBundle
alias: MunichInnovationGroupPatentBundle
[more mappings..]
Please, check your config.yml + AppKernel.php
config.yml must be
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
or replace auto_mapping with
mappings:
StoreShopBundle: ~
for more, check this: https://stackoverflow.com/a/37652091/5809937
in AppKernel.php
don't forget to check if bundle is activated:
new MunichInnovationGroup\PatentBundle\MunichInnovationGroupPatentBundle(),
I had this when tried to use bandle name without core folder name. It was in config/security.yml
Folder structure in my case is the next src/Dp/UserBundle/....
I changed this
`providers:
main:
entity: { class: UserBundle:User, property: username }`
to this
`providers:
main:
entity: { class: DpUserBundle:User, property: username }`
So copy name of unknown Entity name and search each entries in project, check - they have to be with folder prefix (Dp in my case)
As at Symfony version 2.3.7, I used NameofCompanySomethingBundle:EntityRequired e.g. AcmeBlogBundle:User and it works.
auto-mapping: true (default) was used under orm: in config.yml.
This error will occur if you use multiple entity managers and you do not specify the entity manager in your controller function.
$em = $this->get('doctrine.orm.//your_entity_manager_name_here//_entity_manager');
$dql = "SELECT ...";
$query = $em->createQuery($dql);
this worked for me.
open your app\config.yml, must be
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
replace to
orm:
auto_generate_proxy_classes: '%kernel.debug%'
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
mappings:
MunichInnovationGroupPatentBundle: ~

managment of many-to-many tags with Doctrine2

I am interested how tagging works. My idea so far:
I have three database tables Bookmarks id|title|uri|…, Tags id|title|… and bookmarks_tags (mxm, 3NF). My first test will be only a single-user system, so i have not to deal with Tags owned by specific users.
Storing a bookmark: uri (String) + tags (String, eg. Lorem Ipsum, Hello should result in two Tags: Lorem Ipsum and Hello).
Problem: Where and how should i create the missing Tags and loading the known ones?
Creating tags in the model is possible (see Bookmark::setTags() below). Loading and linking in the Model seems not possible, because the ORM is not available inside the class (or is there a static ressource for fetching the ORM? would this be recommended?).
I could load existing tags and create the tags inside Controller, but i assume tagging should be model's work.
I am using Symfony2 with Doctrine2.
Bookmark Class/Table
* #ORM\Table()
* #ORM\Entity(repositoryClass="X\BookmarksBundle\Entity\BookmarkRepository")
*/
class Bookmark
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string $uri
*
* #ORM\Column(name="uri", type="string", length=255)
*/
private $uri;
/**
* #var datetime $created_at
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $created_at;
/**
* #var datetime $deleted_at
*
* #ORM\Column(name="deleted_at", type="datetime")
*/
private $deleted_at;
/** #ORM\ManyToMany(targetEntity="Tag", cascade={"persist", "remove"}) */
private $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
public function getTags () {
if ($this->tags->isEmpty()) {
return "NO TAGS";
}
// TODO load tags from db
return "TODO: TAGS FOUND";
}
public function setTags ($tags) {
// TODO create and load/link existing tags
$tag = new Tag();
$tag->setTitle("test tag");
$this->tags->add($tag);
}
/* setters and getters for other private variables here */
Tag Class/Table
* #ORM\Table()
* #ORM\Entity(repositoryClass="X\BookmarksBundle\Entity\TagRepository")
*/
class Tag
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=64)
*/
private $title;
/**
* #var datetime $created_at
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $created_at;
/**
* #var datetime $deleted_at
*
* #ORM\Column(name="deleted_at", type="datetime", nullable=true)
*/
private $deleted_at;
public function __construct () {
$this->created_at = new \DateTime('now');
}
/* setters and getters for other private variables here */
When fetching entities from your database, Doctrine2 don't give you the POPO Entity but a "Proxy". This proxy has the ability to load missing elements from the database. Thus, you don't have to implement the logic of retrieving the missing data from the database.
Btw, you can also create this method:
public function addTag(Tag $tag)
{
$this->tags->add($tag);
}