yifei3212
yifei3212

Reputation: 851

Symfony / Doctrine: Restful API design and design pattern about repository injection

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?

Upvotes: 0

Views: 753

Answers (2)

Don Omondi
Don Omondi

Reputation: 946

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.

Upvotes: 0

fgamess
fgamess

Reputation: 1324

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?

Upvotes: 1

Related Questions