I'm a big begginer in Symfony, coming from Ruby on Rails world and trying to get same behaviours.
I have a simple application with 2 entities : Product and Category, with the relation Product belongs to Category and Category has many Products.
class Category {
[ ... ]
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category", cascade={"all"})
*/
protected $products;
}
class Product {
[ ... ]
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products", cascade={"all"})
*/
protected $category;
}
What I'm trying to do is to delete every nested products when I'm deleting a Category.
My current action looks like
public function deleteAction($id, Request $request)
{
$repository = $this->getDoctrine()->getRepository('AppBundle:Category');
$category = $repository->find($id);
$em = $this->getDoctrine()->getManager();
$em->remove($category);
$em->flush();
return $this->redirect('/categories/');
}
A simple method could be remove all the products in the controller, but it's not very maintainable and not very object oriented. I'm looking about a practice to remove all the products of the deleted category directly in the model. A method, in RoR, is the callbacks (named after_destroy), automatically called when the object is destroyed. Is there any looking-like method in Symfony ?
Related
I have two entities Item and Category in a ManyToMany relationship.
When I run $category->getItems() on a Category I would like to filter the results by the property visible (boolean).
I remember that there was a #ORM\Somewhat Annotation, but I can't find it.
Category.php
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Item", mappedBy="categories")
* #ORM\OrderBy({"salable" = "DESC", "id" = "DESC"})
*/
private $items;
Item.php
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Category", inversedBy="items")
*/
private $categories;
I want to get an ArrayCollection from the $category->getItems() function, only including those that are visible.
There's no annotation for collection filtering.
To return only visible Item entities from a Category entity do the following:
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Collection;
public function getVisibleItems(): Collection
{
$criteria = Criteria::create()
->andWhere(Criteria::expr()->eq('isVisible', true))
->orderBy(['createdAt' => 'DESC'])
;
return $this->items->matching($criteria);
}
I using symfony 3.4 and doctrineORM 2.5, I have an entity called Project which contains a OneToMany relationship to other entity called Opportunity
So, in the form edit UI of Project entity I have a multiselect choices of opportunities, the user can select or deselect any opportunity in this list.
When I submit the form to ProjectController--->editAction doctrine update automatically opportunities's ArrayCollection with the new selected values, that's good :), but after persist Project object doctrine does not remove that user is deselected from the multi selection list.
class Project{
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Module\CRMBundle\Entity\Opportunity", mappedBy="project")
*/
private $opportunities;
}
class Opportunity{
/**
* #var Project
* #ORM\ManyToOne(targetEntity="Module\CRMBundle\Entity\Project", inversedBy="opportunities")
* #ORM\JoinColumn(name="project_code", referencedColumnName="code")
*/
private $project;
}
My solution is to reset the relationship everytime somebody edits Project like this :
public function editAction(Request $request, Project $project){
$form = $this->createForm('Module\CRMBundle\Form\ProjectType', $project);
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
//Get all old opportunities of this project and set their project to null
$oldOpportunities = $em->getRepository('CRMBundle:Opportunity')->findBy(array(
'project' => $project
));
foreach ($oldOpportunities as $opportunity){
$opportunity->setProject(null);
$em->persist($opportunity);
}
//If request is comming we set this project to whole opportunities
if ($request->isMethod('POST')) {
foreach ($project->getOpportunities() as $opportunity){
$opportunity->setProject($project);
}
}
$em->persist($project);
$em->flush();
}
This solution work very well, but there is a best way to update this relationship because sometimes I have a very lot Opportunities from the owning side (Project), I think that very expensive query that doctrine will excute
I have 2 entities related with ManyToMany : Routage and Contact.
Entity Routage :
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Bdd\Contact", inversedBy="routages", cascade={"persist"})
* #ORM\JoinTable(name="routages_contacts")
*/
private $contacts;
Entity Contact :
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Routage\Routage", mappedBy="contacts")
*/
private $routages;
But when I add some Contact to one Routage in his Collection, it creates X queries.
But I want limit the number of queries.
Here my controller part :
foreach($Abonnes as $Contact){
$Routage->addContact($Contact);
$this->getDoctrine()->getManager()->persist($Routage);
}
$this->getDoctrine()->getManager()->flush();
the line
$this->getDoctrine()->getManager()->persist($Routage);
should be out of the foreach loop so your code should look like this :
foreach($Abonnes as $Contact){
$Routage->addContact($Contact);
}
$this->getDoctrine()->getManager()->persist($Routage);
$this->getDoctrine()->getManager()->flush();
The context
I'm working on an online shop. Every Product on the website can have several ProductVariations (e. g. size, color, ...) which values are a list of ProductVariationValue (e. g. XL, L, Blue, Red, ...).
Each ProductVariationValue is bound to a ProductVariation (e. g. you can't choose Red for Size).
I'm trying to create a stock management system which tracks the amount of in-stock (Product, ProductVariations, ProductVariationValues) combination.
Product <--> ProductVariation <--> ProductVariationValue
I've managed to do it by creating a Stock entity holding a Product and a collection of ProductVariationValues:
/** #ORM\Entity */
public class Stock
{
// ...
/** #ORM\ManyToOne(targetEntity="AcmeDemoBundle:Product") */
protected $product;
/** #ORM\ManyToMany(targetEntity="AcmeDemoBundle:ProductVariationValues") */
protected $productVariationValues;
/** #ORM\Column(type="integer") */
protected $number = 0;
// ...
}
When the user adds a Product to his cart, I create an instance of OrderItem which holds the ProductVariationValues they selected:
/** #ORM\Entity */
public class OrderItem
{
// ...
/** #ORM\ManyToOne(targetEntity="AcmeDemoBundle:Product") */
protected $product;
/** #ORM\ManyToMany(targetEntity="AcmeDemoBundle:ProductVariationValues") */
protected $productVariationValues;
// ...
}
The problem
I understand the Stock entity is similar to the OrderItem entity. However, when the user adds a Product to his cart, and I'm building the OrderItem entity, I cannot find how to retrieve the Stock instance to check whether the Product with its ProductVariationValues are in stock, because I can't find which relation to add in the Stock entity.
Indeed, the Stock entity cannot be identified by the $product or $productVariationValues relations by themselves: they need to be put together.
What I've tried to do
In the controller, given the $product and an array of $productVariationValues, the user selected, I've tried to set up a query to retrieve the Stock instance.
class StockRepository
{
public function retrieveStock($product, $productVariationValues)
{
$this->getQueryBuilder('s')
->leftJoin('s.product', 'p')
->leftJoin('s.productVariationValues', 'pvv')
->where('s.product = ?')
->andWhere('s.productVariationValues = ?')
->setParameter(1, $product)
->setParameter(2, $productVariationValues);
// ...
}
}
This code does not work as the setParameter() method can not accept array or ArrayCollection parameters.
Either way, this solution would not be optimal as this code must be run from the controller and I wouldn't be able to run it from the OrderItem entity.
Is my entity model wrong? Is there a simple way to add a Stock relation in my OrderItem class? Or at least to retrieve a Stock item, given its Product and ProductVariationValues?
You need to use an IN clause, and iterate over all the values; something like:
class StockRepository
{
public function retrieveStock($product, $productVariationValues)
{
$qb=$this->getQueryBuilder('s');
$qb->leftJoin('s.product', 'p')
->leftJoin('s.productVariationValues', 'pvv')
->where('s.product = ?')
->setParameter(1, $product)
;
$stockVariationValues=$qb->getQuery()-
>getSingleResult()->getProductVariationValues();
$qb=$this->getQueryBuilder('s');
$qb->leftJoin('s.product', 'p')
->leftJoin('s.productVariationValues', 'pvv')
->where('s.product = ?')
->setParameter(1, $product)
;
$stockVariationValues->map(function ($element) use ($qb){
$qb->andWhere($qb->expr()
->in('s.productVariationValues'
, "'".implode(","
,$productVariationValues->toArray())
."'")
);
});
// ...
}
}
I have an Item entity and a Category entity. An Item has one Category. My mapping code looks like this:
// Item.php
/**
* #ORM\ManyToOne(targetEntity = "Category")
* #ORM\JoinColumn(name = "category_id", referencedColumnName = "id")
*/
protected $category;
To create the association, I use this method:
// Item.php
public function setCategory(Category $category) {
$this->category = $category;
}
This works fine as long as I first fetch the Category entity from the DB. But I'm wondering if it's possible to pass an id instead of the Category entity. I'd like to manually set the JoinColumn category_id with a scalar value. But sine category_id isn't an actual member of Item, I'm not sure how I can do this.
Use getReference:
$item->setCategory($em->getReference('Category', $id));