0

I'm new to Symfony and these questions were brought about in the recent course of learning.

Take a store as an example, I'll create two entities, Product and Category, which have a bidirectional Many-to-One relationship.

class Product
{
    private $id;
    private $name;
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="products")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=false)
     */
    private $category;
}

class Category
{
    private $id;
    private $name;
    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="category")
     */
    private $products;
}

So my 1st question is:

If I want to get all the products in a particular category, should the URL be

/categories?categoryId=1&limit=20&orderBy=name (I know it's a bit silly, but should a Category record contains all the Product information?)

or

/products?categoryId=1&limit=20&orderBy=name

For the latter one, here comes the 2nd question:

I injected the ProductRepository to the ProductController

class ProductController extends Controller
{
    private $productRepository;

    public function __construct(ProductRepository $productRepository)
    {
        $this->productRepository = $productRepository;
    }

    ...
}

In order to get all the product in a category, I wrote a method like this:

public function findByCategory(Category $category): array
{
    return $this->createQueryBuilder('p')
        ->andWhere('p.category = :category')
        ->setParameter('category', $category)
        ->orderBy('p.name', 'ASC')
        ->setMaxResults(20)
        ->getQuery()
        ->getResult()
    ;
}

So, in the ProductController, how should I get the Category object from the query string 'categoryId' in the URL? Should I inject CategoryRepository as well or should I simply inject an entity manager object?

yifei3212
  • 821
  • 1
  • 9
  • 28

2 Answers2

1

Marco Pivetta aka Ocramius (one of the main developers of Doctrine) said:

Avoid bi-directional associations

Bi-directional associations are overhead

Code only what you need for your domain logic to work

Hack complex DQL queries instead of making them simpler with bidirectionality

So maybe you don't need bi-directional association here.

For your first question, the second solution is better in my opinion:

/products?categoryId=1&limit=20&orderBy=name

For your second question yes, you should inject the CategoryRepository if you want to access Category object, avoid accessing the whole entityManager in your controller even if it is possible.

You should inject services in your controllers. Your services should expose public methods to perform custom CRUD access to the DB through data mappers. Note that a repository is not a data mapper but it

mediates between the domain and data mapping layers, acting like an in-memory domain object collection.

P of EAA Catalog - Martin Fowler

Repositories are services in fact so that's fine to inject them into the controller.

Some people defend the position that repositories should not contain CREATE, UPDATE or DELETE but only READ. They say that these operations make collections (which are accessible through repositories) inconsistent.

This post can help too: How should a model be structured in MVC?

Community
  • 1
  • 1
fgamess
  • 1,276
  • 2
  • 13
  • 25
  • 1
    why did you say : 'avoid injecting repositories in controllers' ? Is it mainly to 'respect' MVC ? Nice links btw, good material. – Elbarto Aug 06 '18 at 09:53
  • @Elbarto I edited my post in fact. There is no reason to avoid injecting repositories in the controller. Thanks for pointing this to me. – fgamess Aug 06 '18 at 10:22
  • Thank you! It's very inspiring. The only question is can I know why that injecting an entity manager is a bad practice? – yifei3212 Aug 06 '18 at 21:41
  • 1
    @yifei3212 it is bad practice because you will use the entity manager to retrieve other objects (repositories) on which method are called and this is a violation of the Law of Demeter (https://en.wikipedia.org/wiki/Law_of_Demeter). You can take a look at this interesting article: https://matthiasnoback.nl/2014/05/inject-a-repository-instead-of-an-entity-manager/ – fgamess Aug 06 '18 at 21:50
0

To me, the problem here is a very clear confusion between what the ORM does in Symfony and Database Design, Modelling and Querying.

In the database (using PhpMyAdmin), you'll notice that on the product table there is a column called category (or category_id). Keep it simple, to get products belonging to a category, all you need is the category_id. Take that knowledge to Symfony, you DO NOT need the Category Object, please just use the category ID that you got from the request. Also, just use the EntityManager in the controller, don't complicate stuff, especially since it seems you're just starting out.

use Symfony\Component\HttpFoundation\Request;

class ProductController extends Controller
{
    public function get_product_from_categoryAction(Request $request)
    {
        $category_id = (int) $request->get('category');
        $limit = (int) $request->get('limit');
        $orderBy = strip_tags($request->get('orderBy'));

        $em = $this->getDoctrine()->getManager();
        $products = $em
            ->getRepository('AppBundle:Products')
            ->queryProductsByCategoryId($category_id, $limit, $orderBy);

    }

    ...
}

And the repo

public function queryProductsByCategoryId(int $category_id, int $limit = 10, string $orderBy = 'name')
{
    return $this->createQueryBuilder('p')
        ->andWhere('p.category = :category')
        ->setParameter('category', $category_id)
        ->orderBy('p.name', 'ASC')
        ->setMaxResults($limit)
        ->getQuery()
        ->getResult()
    ;
}

Keep things simple, then when you got to be more advanced, try the more fancy stuff if you so please.

Don Omondi
  • 946
  • 9
  • 15
  • Thanks for your answer. I totally agree with you to start from something simple, but actually I've been using Laravel for sometime, so I've been familiar with most of the basic stuff :) – yifei3212 Aug 06 '18 at 21:45
  • Great, so you shouldn't have the hardest of times adjusting. Just focus on making Symfony work first, then customize it thereafter. What I've posted for you above will do that. All the best. – Don Omondi Aug 06 '18 at 21:59