I'm trying to figure out how to reuse Domain Models in different parts of the application and I have a feeling that the Data Mapper pattern is the way forward. The example below has methods that directly access the methods of the Mapper.
class Groups
{
protected $_groups = array();
public function addGroup($name)
{
$this->_groups[] = $name;
}
public function doSomethingGroupy($cakes)
{
// get all the groups that have cake
return $cakeyGroups;
}
}
... And a mapper to match the methods on the Groups class.
class GroupMapper
{
public function find($id, Groups $group)
{
// Mappy type things, maybe some sql
}
public function fetchByNeediness($cuddles, Groups $group)
{
// More mappy type things
}
public function save(Groups $groups)
{
// Saves
}
}
However if sometime later I wanted to use the same Groups Models but populate the groups using different queries I would use a different mapper.
class AngryGroupMapper
{
public function find($id, Groups $group)
{
// Something similar but with other tables and joins
}
public function fetchByRage($anger, Groups $group)
{
// Something new but only needed here
}
public function isEditable(Groups $groups)
{
// Do some querying
return $bool;
{
}
Now I Know the aim is Skinny Controller - Fat Model, so would I have another model to Map the Mapper (so to speak) to the Model?
class FatModelRepository
{
public function getHappyGroups()
{
$mapper = new GroupMapper();
return $mapper->fetchByNeediness('Puffy Shoes', new Groups());
}
public function getSadGroups()
{
$mapper = new AngryGroupMapper();
return $mapper->fetchByRage('Aghh!', new Groups());
{
public function save(Groups $groups)
{
$mapper = new GroupMapper();
return $mapper->save($groups);
{
}
The Data Model should have no knowledge of the Data Mapper. Your Groups class/model shouldn't have find methods and it should not have access to the mapper.
Once you remove the mapper dependency from your model your problems will go away.
NOTE: check out Doctrine 2
As rojoca says you shouldnt have the fetch/find methods directly on the model. Technically hes also right about the model not storing a reference to the mapper, but in less complex situations i think this is ok so long as the model only excpets the most abstract form of mapper you plan on having (ie. some kind of base mapper class or an interface).
Given thos to things, you should only need to add methods to the mapper, and for this i would just use inheritance, ie. extend your groups mapper for the new functionality. Of course this requires that the mapper is injectable into the model. But if youre going to have the model hold a reference to its mapper then it does need to be injectable anyhow.
Related
For historical reasons, my pattern of running databases using Symfony is mixed. That is, the query uses DBAL and the insert uses ORM. Now you need to write a lot of data to the database. The flush in ORM can help me achieve business at the lowest cost.
All flush operations have been removed from the project. Put it in the __destruct of the controller.
However, doing so will cause DBAL to not find the latest changed data. Of course, these data ORMs can be obtained normally.
This is a very difficult problem. I hope to get guidance.
class BaseController extends Controller
{
public function __destruct()
{
$this->getDoctrine()->getManager()->flush();
}
public function indexAction()
{
$model = new CompanyModel();
$model->install(['company_name' => '1234']);
$model->update(['company_name' => 'abcd'], $model->lastInsertId);
}
}
class CompanyModel extends BaseController
{
public function validate($data, $id = false)
{
$this->entityManager = $this->getDoctrine()->getManager();
if(empty($id)){
$this->company_class = new Company();
}else{
if(!$this->is_exist($id)){
return false;
}
$this->company_class = $this->entityManager->getRepository(Company::class)->find($id);
}
if(array_key_exists('company_name', $data)){
$this->company_class->setCompanyName($data['company_name']);
}
if(self::$error->validate($this->company_class)){
return false;
}
return true;
}
public function insert($data)
{
if(!$this->validate($data)){
return false;
}
$this->company_class->setCreateAt(new \DateTime());
$this->entityManager->persist($this->company_class);
//$this->entityManager->flush();
$this->lastInsertId = $this->company_class->getId();
return true;
}
public function update($data, $id)
{
if(empty($id)){
self::$error->setError('param id is not null');
return false;
}
if(!$this->validate($data, $id)){
return false;
}
$this->company_class->setUpdateAt(new \DateTime());
//$this->entityManager->flush();
return true;
}
public function is_exist($id)
{
return $this->get('database_connection')->fetchColumn('...');
}
}
The final result of executing indexAction company_name is 1234; $ model-> update() was not executed successfully. The reason is that the $this-> is_exist() method that took the DBAL query did not find the ORM insert but did not flush the message.
Unchanging conditions,run
$this->entityManager->getRepository(Company::class)->find($id);
Is successful。
The problem is not the entity manager or dbal, as far as I can tell, but the usage of an anti-pattern, which I would call ... entanglement. What you should strive for is separation of concerns. Essentially: Your "CompanyModel" is an insufficient and bad wrapper for the EntityManager and/or EntityRepository.
No object should know about the entity manager. It should only be concerned with holding the data.
The entity manager should be concerned with persistence and ensuring integrity.
The controller is meant to orchestrate one "action", that can be adding one company, editing one company, batch-importing/updatig many companies.
Services can be implemented, when actions become to business-logic-heavy or when functionality is repeated.
(Note: the following code samples could be made way more elegant with using all the features that symfony provide, like ParamConverters, the Form component, the Validation component, I usually wouldn't write code this way, but I assume everything else would go way over your head - no offence.)
handling actions in the controller
controller actions (or service actions, really) are when you look at your problem from the task perspective. Like "I want to update that object with this data"). That's when you fetch/create that object, then give it the data.
use Doctrine\ORM\EntityManagerInterface;
class BaseController extends Controller {
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
public function addAction() {
$company = new Company(['name' => '1234']); // initial setting in constructor
$this->em->persist($company);
// since you have the object, you can do any changes to it.
// just change the object
$company->update(['name' => 'abcd']); // <-- don't need id
// updates will be flushed as well!
$this->em->flush();
}
public function editAction($id, $newData) {
$company = $this->em->find(Company::class, $id);
if(!$company) {
throw $this->createNotFoundException();
}
$company->update($newData);
$this->em->flush();
}
// $companiesData should be an array of arrays, each containing
// a company with an id for update, or without an id for creation
public function batchAction(array $companiesData) {
foreach($companies as $companyData) {
if($companyData['id']) {
// has id -> update existing company
$company = $this->em->find(Company::class, $companyData['id']);
//// optional:
// if(!$company) { // id was given, but company does not exist
// continue; // skip
// // OR
// $company = new Company($companyData); // create
// // OR
// throw new \Exception('company not found: '.$companyData['id']);
// }
$company->update($companyData);
} else {
// no id -> create new company
$company = new Company($companyData);
$this->em->persist($company);
}
}
$this->em->flush(); // one flush.
}
}
the base controller should handle creating objects, and persisting it, so very basic business logic. some would argue, that some of those operations should be done in an adapted Repository for that class, or should be encapsulated in a Service. And they would be right, generally.
the entity handles it's internal state
Now, the Company class handles its own properties and tries to stay consistent. You just have to make some assumptions here. First of all: the object itself shouldn't care if it exists in the database or not. it's not its purpose! it should handle itself. Separation of concerns! The functions inside the Company entity should concern simple business logic, that concerns its INNER state. It doesn't need the database, and it should not have any reference to the database, it only cares about it's fields.
class Company {
/**
* all the database fields as public $fieldname;
*/
// ...
/**
* constructor for the inital state. You should never want
* an inconsistent state!
*/
public function __construct(array $data=[]) {
$this->validate($data); // set values
if(empty($this->createAt)) {
$this->createAt = new \DateTime();
}
}
/**
* update the data
*/
public function update(array $data) {
$this->validate($data); // set new values
$this->updateAt = new \DateTime();
}
public function validate(array $data) {
// this is simplified, but you can also validate
// here and throw exceptions and stuff
foreach($array as $key => $value) {
$this->$key = $value;
}
}
}
some notes
Now, there should be NO use case, where you get an object to persist and at the same time an update - with an id - that refers to the new object ... unless that object was given the id beforehand! HOWEVER. If you persist an object, that has an ID and you call $this->em->find(Company::class, $id) you would get that object back.
if you have many relations, there are always good ways to solve this problem without destroying separation of concerns! you should never inject an entity manager into an entity. the entity should not manage its own persistence! nor should it manage the persistence of linked objects. handling persistence is the purpose of the entity manager or entity repository. you should never need a wrapper around an object just to handle that object. be careful not to mix responsibilities of services, entities (objects) and controllers. In my example code, I have merged services and controllers, because in simple cases, it's good enough.
I have following classes
Interface
interface IRole {
public function All();
}
In below class, I will also write some logic before sending the data to Database layer and some code after data is retrieved from Database class
class RoleBL implements IRole {
public function All() {
return (new RoleDb())->All();
}
}
Database class
class RoleDb {
public function All() {
$Roles = \App\Models\Role\RoleModel
::all();
return $Roles;
}
}
Below is my Controller Action Method
class MembershipController extends \App\Http\Controllers\BaseController
{
private $role;
public function __construct(IRole $_role) {
$this->role = $_role;
parent::__construct();
}
public function create() {
$RoleTypes = $this->role->All();
return view('Membership.Create', array('Roles' => $RoleTypes));
}
}
Can somebody help me how can I stop direct access to RoleDb class? It's access modifier is public right now.
You should not even try to do that. At PHP it will look more like hacks. Just keep one code style and if you work in team then write some guidelines.
Keep all your objects in separate layers based on their purpose. Database related objects in data access layer. Domain objects in domain layer.
Models at domain layer represent all business objects that business talk about (role, customer, comment, payment, etc.) and all related actions on them (user->register, user->assignRole, blog->postArticle, etc.). These models hold business requirements, rules.
Models at data access layer represents all objects that persists state of those business objects and finds them in specific ways (blog->getUnreadPosts, user->findAdministrators, etc.).
If by RoleBL you mean role business logic then it should be part of domain (your business requirements). These objects are persisted/retrieved by DAL.
Your business objects should not know anything about data access layer. (new RoleDb())->All(); means reading from database. To fix this you can create separate read model at DAL for querying your business objects. That means there will be separate model (e.g. RoleDataModel) at DAL for designing query side based on business/application needs.
namespace Domain
{
class Role
{
private $id;
private $title;
/* etc. */
public function getId()
{
return $this->id;
}
}
class Person
{
private $roles = [];
/* etc. */
public function assignRole(Role $role)
{
// person already promoted?
// can person be promoted ?
// if you promote person then it might not be required to add instance af this $role
// to $this->roles array, just add identifier from this role
// If you would like to respond on what did just happen
// at this point you can return events that describe just that
}
public function getRoles()
{
return $this->roles;
}
}
}
namespace DAL
{
class Person
{
function storePerson(Person $person)
{
// here you can use eloqueent for database actions
}
function getAllAdministrators()
{
}
}
}
There will be separate Person class for eloquent. Use that just for data manipulation. Map data from eloquent objets to Data Transfet Objects or your Business Layer Objects. DTOs can be more specific to your other layers like UI where you might not need to show everything that BLOs contains. DTOs for your UI will model everything your UI needs.
Get familiar with some of the DDD and overall programming concepts. I should be able to look up something that will fit your needs.
I preferred double layer models (mapper and model) over doctrine in my zend framework 2 project and trying to make them work little bit like doctrine so I can access relational data from the models (entities). Following example demonstrates what I am trying to achieve.
class User
{
protected $userTable;
public $id;
public $name;
public function __construct($userTable)
{
$this->userTable = $userTable
}
public function getUserArticles()
{
return $this->userTable->getUserArticles($this->id);
}
}
Problem is I cannot inject my user table in my user model, because table gateway uses model class as array object prototype which gets later injected to create user table gateway (mapper).
I don't want to inject service manager in my models as it is considered as a bad practice. How can I inject my user table in my user model? is it possible? what is the best way to achieve what I am trying to do
What you are trying to do is mix two design patterns: Active Record and Data Mapper.
If you take a look at the Data Mapper pattern, you have the Mapper that accesses both the Model and the database. The Model is passive - usually does not call external resources (it's a POPO - Plain Old PHP Object).
A solution for your issue is to inject the related information into the Model, thus keeping the Model only as a data structure.
Here is a working scenario for an MVC application:
Controller - used for input validation & retrieving data from services
<?php
...
public function viewAction()
{
$id = (int) $this->params()->fromQuery('id');
$service = $this->getServiceLocator()->get('your-user-service-name');
$user = $service->getUser($id);
...
}
Service - used for executing the business logic; calls multiple Data Mappers
<?php
...
public function getUser($id)
{
// get user
$mapper = $this->getServiceLocator()->get('your-user-mapper');
$user = $mapper->getUserById($id);
// get articles
$article_mapper = $this->getServiceLocator()->get('your-article-mapper');
$user->articles = $article_mapper->getArticlesByUser($id);
return $user;
}
Data Mapper - used to manipulate one type of Domain entity - it should be composed with a tableGateway if you are accessing the database
<?php
...
public function getUserById($id)
{
$select = $this->tableGateway->getSql()->select();
$select = $select->where(array('id' => $value));
$row = $this->tableGateway->selectWith($select)->current();
return $row;
}
Domain Model - used for data representation
<?php
...
class User
{
public $name; // user name
...
public $articles; // holds the user articles
}
Advantages
Passive Models are easy to read - understand the data structure and it's relations.
Passive Models are easy to test - you don't need external dependencies.
You separate the persistence layer from the Domain layer.
Hope this helps!
You should not inject your mapper into your model, that's exactly the other way around. Important for you to understand is the way the relations work and models shouldn't have any knowledge how their data is mapped to a persistency framework.
You refer to Doctrine, so I'd suggest you also look at how Doctrine solves this problem. The way they do it is via a Proxy. A proxy is a generated class (you need to write your own generator or write all proxies yourself) which extends the model and have the mapper injected:
class Foo
{
protected $id;
protected $name;
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
class ProxyFoo extends Foo
{
protected $mapper;
public function __construct(FooMapper $mapper)
{
$this->mapper = $mapper;
}
public function getName()
{
if (null === $this->name) {
$this->load();
}
return parent::getName();
}
protected function load()
{
$data = $this->mapper->findById($this->id);
// Populate this model with $data
}
}
My suggestion: look either at the default mapper pattern Zend Framework 2 applies and forget lazy loading, or just use Doctrine. This is too much work to get this done properly.
This question already has answers here:
Using ORM classes directly from the controller in MVC, bad practice?
(4 answers)
Closed 9 years ago.
I'm quite new to ORM's and just a bit experienced with MVC, so I was wondering the following:
I have ORM models User, Organization.. When I want to search all users in organization 1 I do the following:
$users = Model_User::query()->where('organisation_id', 1);
Should i just put that straight in the controller, or somewhere else?
Ideally you should have another layer between ORM (or data layer or persistence or repository) and controller. You can call this services/AppServices/BLL.
This layer should handle the extra logic, whereas your ORM should get data directly from database/other sources as mapped, and controller should just call next layer depending on user request.
ORM:
Users GetUsers() //for all users
Service:
Users GetUsersForOrganization(int orgId) //call orm method and filter for organization id
When i work with MVC i always have a Value Object Data1 and an mapper Data1Mapper. The ValueObject itself is for holding the data and the mapper contains methods like: find, save, delete, fetchAll etc...
In the controller i instantiate the mapper and access the needed method.
Example:
class DataMapper {
public function __construct() {
//fetch TableDateGateway
}
public function find() {
//find by identifier
}
public function save($data) {
//insert or update
}
}
class Data {
protected $_property;
public function getProperty() {
return $this->_property;
}
public function setProperty($value) {
$this->_property = $value;
}
}
class Controller {
public function indexAction() {
$id = 1;
$mapper = new DataMapper();
$data = $mapper->find($id); //--> returns if found a Data-Object
}
}
I have been working on my own library/framework for the learning experience for a while. MVC is one of those things that took me a while to really understand but I do finally "Get it".
Below is some sample code for a basic MVC setup in PHP. I think I am in the right direction so far, where I need a little help is down in the "Example controller" near the bottom, you will see where I can create a view, I just need to figure out how to best get my data from a model file into that controller class. Please help with example code if you can, hopefully I am making sense.
Also I am welcome to any comments/suggestions on any of the code
Abstract Controller class...
/**
* MVC Example Project
*/
/**
* Extend this class with your Controllers
* Reference to the model wrapper / loader functions via $this->model
* Reference to the view functions via $this->view
*/
abstract class Core_Controller {
protected $view;
protected $model;
function __construct($dependencyContainer){
$this->view = new Core_View();
//$this->view = $dependencyContainer->get(view);
}
}
Abstract Model class...
/**
* Extend this class with your models and reference to the database object via $this->$db
*/
abstract class Core_Model {
protected $db;
protected $session;
function __construct($dependencyContainer) {
$this->db = $dependencyContainer->get(database);
$this->session = $dependencyContainer->get(session);
}
}
View class, might make it abstract as well...
class Core_View {
protected $data;
# Load a view file (views/$view.php);
# $param data this gets extracted and be thus be used inside the view
# When loading another view from inside the view file the data is 'cached' so you
# don't have to pass them again
public function load($view,$data = null) {
if($data) {
$this->data = $data;
extract($data);
} elseif($this->data != null) {
extract($this->data);
}
require(APP_PATH . "Views/$view.php");
}
public function set($data = null) {
if($data) {
$this->data = $data;
extract($data);
} elseif($this->data != null) {
extract($this->data);
}
}
}
Example putting it together...
/**
* Example Controller
*/
class User_Controller extends Core_Controller {
public function profile()
{
$profileData = array();
$profileData = //GET from Model
$this->view->load('userProfile', $profileData);
}
}
?>
My suggestion is not to tie view and model to the controller at all. Let them be instantiable from controller code, just like any other classes. You can then get the model data (and pass it to the view) in standard object oriented way.
Will you use a Data access layer (DAL) / Object-relational mapping (ORM)? Take a look at Zend_Db, Doctrine or Propel
I'd say that you're missing the part of the application that manipulate your models. It could be your controller, but isn't a good practice. So we need a model mapper.
The best way to get model data from your controller is simply calling it. But generally we use a kind of "pointer" which knows how to populate your object model. This pointer is called "Mappers" (Data Mapper Pattern):
$MyModelMapper = new MyModelMapper();
$Profile = $MyModelMapper->getProfileById($id); // return Core_Model.
This function will perform a database query and will populate one specific model with the data. You could also get an array of objects for a "list" action for example.
Then you'll pass this model to your view.
I think you should take a look at the Zend Framewok quick start. It will give you some ideas.
See this question too: What's the difference between DAO and Data Mapper