1
votes

What is "the right way" to implement business logic Models with ZF2 and Doctrine, while creating a clean OOP and MVC structure, and providing the Models with access to the EntityManager?

Althought I am tempted to use the Doctrine Repositories for all the business logic, I know they should primarily be used to perform complex queries. I also know that a model in ZF2 can be provided with dependencies (EntityManager) in several ways: ZF2: Dependency injection done the proper way http://zend-framework-community.634137.n4.nabble.com/ZF2-Injecting-objects-to-a-controller-or-getting-objects-from-the-service-locator-td4656872.html

To illustrate the problem in real life, how do we create a Model layer for a simple Web shop functionality:

Firstly, we would have the Entities

/**
 * @Entity(repositoryClass="App\Repository\ProductRepository")
 * @Table(name="products")
 */
class Product { ... }

/**
 * @Entity(repositoryClass="App\Repository\ProductgroupRepository")
 * @Table(name="productgroups")
 */
class Productgroup { ... }

And the Repositories which give us more detailed query capabilities

class ProductRepository extends EntityRepository {
    public function isAvailable() { ... }
    public function getQuantity() { ... }
    public function getProductImages() { ... }
    public function getRelatedProducts() { ... }
    ...
}

class ProductgroupRepository extends EntityRepository {
    public function getAvailableProducts() { ... }
    public function getAllProducts() { ... }
    ...
}

But where do we place the business logic such as this?

Products functionalities:
- createNewProduct( $data ), eg. update prices, retrieve new quantities, generate a     new image gallery, notify the administrator, ...
- deleteProduct(),           eg. delete corresponding files and existing associations, put product into archive
- ...

Productgroups functionalities:
- removeProductFromProductgroup( $product), eg. update ordering in group, remove associations, ...
- addProductToProductgroup( $product ),     eg. update ordering in group, create associations, ...
- deleteProductgroup(),                     eg. delete productgroup, set all it's products as uncategorized
- ...

Should the Productgroup business model be created, for example, as a class which is injected with the EntityManager at the service level? --

class Productgroup implements ServiceLocatorAwareInterface
{
    public function removeProductFromProductgroup( $productgroup, $product) { }
    public function addProductToProductgroup( $productgroup, $product) { }
}

Or should it maybe also extend the original entity so as to have access to its internal structure? --

class Productgroup extends \Application\Entity\Productgroup 
    implements ServiceLocatorAwareInterface
{
    public function removeProductFromProductgroup( $productgroup, $product) { }
    public function addProductToProductgroup( $productgroup, $product) { }
}

And if so, should it also have some kind of a set state method ? public function set( $product ) { $this->populate( $product ); }

2

2 Answers

1
votes

About where to have Business Logic, it is obviously Models. I would not recommend extending your Entities because any logic which has nothing to do with setting/getting your entity object should be outside your entity.

So the answer is yes, I would be injecting the Entity Manager into the models. But whether you use the Service Locator or some other method for DI is upto you.

The questions you are pondering over is really about OOP, DI, DiC, etc. Please go through some of the best blog posts on DI from the gurus. I have listed them below:

Its really upto you to take your dependency injection to different levels. If you check Martin Fowler's blog, you can find out about constructor injection, setter injection and a DiC doing the injection for you. You have to take the call for your system. What is working for me is, for within module dependency, I do constructor injection (so that the dependency is clearly visible). Anything that is a service, will be called from outside my module will be through a DiC or ServiceLocator.

The only argument against the Service Locator pattern is it hides the dependency injection into its configuration and it is not clearly visible.

0
votes

I use the Array Collection for tasks like these. This Object has various methods that are pretty usefull like clean(), contains() etc.

I tend to store my oneToOne, oneToMany etc. relations in arrayCollections.

Within your Entity you'll have to use a constructor to initiate the collection.

use Doctrine\Common\Collections\ArrayCollection;

/**
 * @ORM\OneToMany(targetEntity="yourModule\Entity\yourEntity", mappedBy="yourRelation", cascade={"persist"})
 */
protected $yourCollection;

public function __construct()
{
   $this->yourCollection = new ArrayCollection();
}
//once I have the collection I usually create methods like add, remove, etc methods like so:
public function addToCollection($toAdd) {
   $this->yourCollection->add($toAdd);
}

public function removeFromCollection($toRemove) {
   $this->yourCollection->removeElement($toRemove);
}

//etc.....