I learnt that I should be using service classes to persist entities into the database instead of putting such logic in models/controllers. I currently made my service class something like
class Application_DAO_User {
protected $user;
public function __construct(User $user) {
$this->user = $user
}
public function edit($name, ...) {
$this->user->name = $name;
...
$this->em->flush();
}
}
I wonder if this should be the structure of a service class? where a service object represents a entity/model? Or maybe I should pass a User object everytime I want to do a edit like
public static function edit($user, $name) {
$user->name = $name;
$this->em->flush();
}
I am using Doctrine 2 & Zend Framework, but it shouldn't matter
I think you should first consider what you'd like to do with the user objects. For example if you only want to create, update and delete user records (CRUD) I can imagine this type of API:
<?php
public function create (array $data = null)
{
$user = new User($data);
$this->_persist($user)
->_flush();
return $user;
}
public function update (User $user, array $data)
{
foreach ($data as $name => $value) {
$user->$name = $value;
}
$this->_flush();
return $user;
}
public function delete (User $user)
{
$this->_remove($user)
->_flush();
}
I seperated the methods to the entity manager, you can create something like below or skip the seperated methods at all. With these methods you could do additional checkups (for example if you want to persist there can be a check to look if the object is already persisted).
protected function _persist ($obj)
{
$this->_em->persist($obj);
return $this;
}
protected function _detach ($obj)
{
$this->_em->detach($obj);
return $this;
}
protected function _remove ($obj)
{
$this->_em->remove($obj);
return $this;
}
protected function _flush ()
{
$this->_em->flush();
return $this;
}
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I'm trying to grasp the concept of a datamapper (I hope this is the right terminology) in conjunction with protected properties.
I am building an authentication system. There I have a User class
class User {
protected $id;
public $first_name;
public $mail;
protected $password;
As you can see, I chose to make $id and $password protected. Actually I'm not quite sure if that's right, but I did read, that one should try to keep the scope of properties as closed as possible.
I also build a datamapper to save my user object to my database. The mapper is injected to the user class via constructor dependency injection. I call the mappers save-method from inside my user class this way
public function save () {
return $this->dep['mapper']->saveUser($this);
}
Inside my mappers saveUser()-method I am building an array of values to pass along to my database class.
public function saveUser($obj) {
$insert_array;
foreach ( $obj as $key => $value ) {
$insert_array[$key] = $obj->get($key);
}
This does not work the way it's intended, because my mapper is not able to iterate over the protected properties. Therefore these properties are not passed on to the database. If the said properties are public it works just fine.
So my question is: How do I have to setup my classes and methods so that my mapper is able to get all the values it needs, without exposing all my properties?
Extra: I already made use of __get() to circumvent the problem, but is that good coding practice?
There is no single right answer for this, but IMO you don't want to have different visibility for fields in a data object. Here are some ideas.
If you're set on having different visibility for fields on you User class, you can change things up like this to allow your Mapper to save the data using an array you build in the save method of your user class.
<?php
class User
{
protected $id;
public $first_name;
public $mail;
protected $password;
private $dep = [];
public function __construct()
{
$this->dep['mapper'] = new Mapper();
}
public function save()
{
$data = [
'id' => $this->id,
'first_name' => $this->first_name,
'mail' => $this->mail,
'password' => $this->password
];
return $this->dep['mapper']->saveUser($data);
}
}
class Mapper
{
public function saveUser($data)
{
foreach($data as $field=>$value)
{
echo $field.': '.$value.PHP_EOL;
}
}
}
$myUser = new User();
$myUser->first_name = 'Lando';
$myUser->mail = 'lando#cloudcity.gov';
$myUser->save();
A more formal option is to use a Data Transfer Object (DTO), which is a dead-simple class that just encapsulates the data. Then you can control access to the fields in your business object.
<?php
class User
{
private $dto;
private $dep = [];
public function __construct(UserDto $dto)
{
$this->dto = $dto;
$this->dep['mapper'] = new Mapper();
}
public function __get($propName)
{
if($propName=='password')
{
throw new Exception('No password for you');
}
elseif(property_exists($this->dto, $propName))
{
return $this->dto->$propName;
}
throw new InvalidArgumentException('No property '.$propName.' found in object');
}
public function __set($propName, $value)
{
if($propName=='id')
{
throw new Exception('ID may not be changed');
}
elseif($propName=='password')
{
throw new Exception('Password may not be changed');
}
elseif(property_exists($this->dto, $propName))
{
$this->dto->$propName = $value;
}
else
{
$this->$propName = $value;
}
}
public function __isset($propName)
{
return (property_exists($this->dto, $propName));
}
public function save()
{
return $this->dep['mapper']->saveUser($this->dto);
}
}
class UserDto
{
public $id;
public $first_name;
public $mail;
public $password;
}
class Mapper
{
public function saveUser(UserDto $dto)
{
foreach ($dto as $key => $value)
{
$insert_array[$key] = $dto->$key;
echo $key.': '.$value.PHP_EOL;
}
}
}
try
{
$dto = new UserDto();
$myUser = new User($dto);
$myUser->first_name = 'Lando';
$myUser->mail = 'lando#cloudcity.gov';
echo $myUser->password;
$myUser->password = 'foobar';
$myUser->save();
}
catch(Exception $e)
{
echo $e->getMessage().PHP_EOL;
}
A better option to control access to properties is by using get/set/has methods. This is verbose, but has the benefit of adding logic or transforms to the data as you get and set it. One of the major benefits of this approach is that full-featured code editors will code-complete all of these getters and setters, you don't get that with magic methods. You can of course use this in combination with DTOs.
<?php
class User
{
private $data = [
'id'=>null,
'first_name'=>null,
'mail'=>null,
'password'=>null
];
private $dep = [];
public function __construct($data)
{
$validData = array_intersect_key($data, $this->data);
foreach($validData as $currKey=>$currValue)
{
$this->data[$currKey] = $currValue;
}
$this->dep['mapper'] = new Mapper();
}
public function getId()
{
return $this->data['id'];
}
//Notice there is no setter for ID!
public function hasId()
{
return (!empty($this->data['id']));
}
public function getFirstName()
{
return $this->data['first_name'];
}
public function setFirstName($val)
{
$this->data['first_name'] = $val;
}
public function hasFirstName()
{
return (!empty($this->data['first_name']));
}
public function getMail()
{
return $this->data['mail'];
}
public function setMail($val)
{
$this->data['mail'] = $val;
}
public function hasMail()
{
return (!empty($this->data['mail']));
}
//Notice there is no getter for ID!
public function setPassword($val)
{
$hashed = md5($val); //Just an example, don't do this
$this->data['password'] = $hashed;
}
public function hasPassword()
{
return (!empty($this->data['password']));
}
public function save()
{
return $this->dep['mapper']->saveUser($this->data);
}
}
class Mapper
{
public function saveUser($data)
{
foreach($data as $field=>$value)
{
echo $field.': '.$value.PHP_EOL;
}
}
}
try
{
$dataFromDb = [
'id'=>123,
'first_name'=>'Lando',
'mail'=>'lando#cloudcity.gov',
];
$myUser = new User($dataFromDb);
$myUser->setFirstName('Chewie');
$myUser->setMail('wookie#kashyyyk.net');
if(!$myUser->hasPassword())
{
$myUser->setPassword('AAAAAARRRRRRGHHHH');
}
$myUser->save();
}
catch(Exception $e)
{
echo $e->getMessage().PHP_EOL;
}
I prefer to do something like this, where all of the verbose boilerplate is relegated to data access objects that encapsulate the data and handle loading and saving individual records, and the app logic for individual records is contained in the main business object. They can be superclasses or traits, whatever floats your boat. Personally, I have code that writes all of my DAO and business object classes for me based on database schemas, so all I have to worry about is app logic.
<?php
trait UserDao
{
private $data = [
'id'=>null,
'first_name'=>null,
'mail'=>null,
'password'=>null
];
private $deps;
public function getId()
{
return $this->data['id'];
}
//Notice there is no setter for ID!
public function hasId()
{
return (!empty($this->data['id']));
}
public function getFirstName()
{
return $this->data['first_name'];
}
public function setFirstName($val)
{
$this->data['first_name'] = $val;
}
public function hasFirstName()
{
return (!empty($this->data['first_name']));
}
public function getMail()
{
return $this->data['mail'];
}
public function setMail($val)
{
$this->data['mail'] = $val;
}
public function hasMail()
{
return (!empty($this->data['mail']));
}
private function _getPassword()
{
return $this->data['password'];
}
private function _setPassword($val)
{
$this->data['password'] = $val;
}
public function hasPassword()
{
return (!empty($this->data['password']));
}
public function load($data)
{
$validData = array_intersect_key($data, $this->data);
foreach($validData as $currKey=>$currValue)
{
$this->data[$currKey] = $currValue;
}
}
private function _save()
{
return $this->dep['mapper']->saveUser($this->data);
}
}
class User
{
use UserDao;
public function __construct()
{
$this->dep['mapper'] = new Mapper();
}
public function setPassword($val)
{
$hashed = str_rot13($val); //Just an example, don't do this
$this->_setPassword($hashed);
}
public function getPassword()
{
return str_rot13($this->_getPassword()); //Just an example, don't do this
}
public function save()
{
echo 'Do some complex validation here...'.PHP_EOL;
$this->_save();
}
}
class Mapper
{
public function saveUser($data)
{
foreach($data as $field=>$value)
{
echo $field.': '.$value.PHP_EOL;
}
}
}
try
{
$dataFromDb = [
'id'=>123,
'first_name'=>'Lando',
'mail'=>'lando#cloudcity.gov',
];
$myUser = new User();
$myUser->load($dataFromDb);
$myUser->setFirstName('Chewie');
$myUser->setMail('wookie#kashyyyk.net');
if(!$myUser->hasPassword())
{
$myUser->setPassword('AAAAAARRRRRRGHHHH');
}
$myUser->save();
echo 'Unfutzed Password: '.$myUser->getPassword().PHP_EOL;
}
catch(Exception $e)
{
echo $e->getMessage().PHP_EOL;
}
I recommend doing some research on this subject, there are a lot of patterns, and everyone has different opinions.
I have a controller that acquires data to pass to a view. Into this is injected (via a pimple container) a service which uses a number of domain models + business logic to create the data.
The service itself has a 'repository' class injected into it which has methods for creating data mappers and returning a domain model instance.
I'm aware that I might not have got my head around the repository concept as Martin Fowler puts it to "build another layer of abstraction over the mapping layer" & "A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection." So I may be using this term erroneously.
service:
class InputService
{
private $repos;
public function __construct($repo) {
$this->repos = $repo;
}
public function getInitialData()
{
$product = $this->repo->getProduct();
$country = $this->repo->getCountry();
$spinalPoint = $this->repo->getPoint();
/*business logic with model instances to produce data array*/
return //array of data
}
}
repository:
class InputRepository
{
private $db;
public function __construct($db) {
$this->db = $db;
}
public function getCountry()
{
$mapper = new CountryMapper($this->db);
$country = $mapper->fetch();
return $country; //returns country object
}
// lots of other methods for returning different model objects
}
mapper:
class CountryMapper
{
private $db;
public function __construct($db) {
$this->db = $db;
}
public function fetch()
{
$data = //code to grab data from db;
$obj = new Country($data);
return $obj;
}
}
As you can see, the mappers are tightly coupled to the repository class, however I can't see a way around it.
I was wondering if there is a way to implement this repository that provides looser coupling to the data mapper classes?
In the grand scheme of things this application is fairly small and so having to update code across both wouldn't be disastrous, but you never now when thing will grow!
The db operations should be performed through adapters (MySqliAdapter, PdoAdapter, etc). So, the db connections are injected into adapters, not into the mappers. And certainly not in the repositories, because then the abstraction purpose of the repositories would be pointless.
A mapper receives adapter(s) as dependencies and can receive other mappers too.
The mappers are passed as dependencies to the repositories.
A repository name is semantically related to the domain layer names, not really to the ones of the service layer. E.g: "InputService": ok. "InputRepository": wrong. "CountryRepository": correct.
A service can receive more repositories. Or mappers, if you don't want to apply the extra layer of repositories.
In the code, the only tightly coupled structure is the Country object (entity or domain object) - dynamically created for each fetched table row. Even this could be avoided through the use of a domain objects factory, but I, personally, don't see it really necessary.
P.S: Sorry for not providing a more documented code.
Service
class InputService {
private $countryRepository;
private $productRepository;
public function __construct(CountryRepositoryInterface $countryRepository, ProductRepositoryInterface $productRepository) {
$this->countryRepository = $countryRepository;
$this->productRepository = $productRepository;
}
public function getInitialData() {
$products = $this->productRepository->findAll();
$country = $this->countryRepository->findByName('England');
//...
return // resulted data
}
}
Repository
class CountryRepository implements CountryRepositoryInterface {
private $countryMapper;
public function __construct(CountryMapperInterface $countryMapper) {
$this->countryMapper = $countryMapper;
}
public function findByPrefix($prefix) {
return $this->countryMapper->find(['prefix' => $prefix]);
}
public function findByName($name) {
return $this->countryMapper->find(['name' => $name]);
}
public function findAll() {
return $this->countryMapper->find();
}
public function store(CountryInterface $country) {
return $this->countryMapper->save($country);
}
public function remove(CountryInterface $country) {
return $this->countryMapper->delete($country);
}
}
Data mapper
class CountryMapper implements CountryMapperInterface {
private $adapter;
private $countryCollection;
public function __construct(AdapterInterface $adapter, CountryCollectionInterface $countryCollection) {
$this->adapter = $adapter;
$this->countryCollection = $countryCollection;
}
public function find(array $filter = [], $one = FALSE) {
// If $one is TRUE then add limit to sql statement, or so...
$rows = $this->adapter->find($sql, $bindings);
// If $one is TRUE return a domain object, else a domain objects list.
if ($one) {
return $this->createCountry($row[0]);
}
return $this->createCountryCollection($rows);
}
public function save(CountryInterface $country) {
if (NULL === $country->id) {
// Build the INSERT statement and the bindings array...
$this->adapter->insert($sql, $bindings);
$lastInsertId = $this->adapter->getLastInsertId();
return $this->find(['id' => $lastInsertId], true);
}
// Build the UPDATE statement and the bindings array...
$this->adapter->update($sql, $bindings);
return $this->find(['id' => $country->id], true);
}
public function delete(CountryInterface $country) {
$sql = 'DELETE FROM countries WHERE id=:id';
$bindings = [':id' => $country->id];
$rowCount = $this->adapter->delete($sql, $bindings);
return $rowCount > 0;
}
// Create a Country (domain object) from row.
public function createCountry(array $row = []) {
$country = new Country();
/*
* Iterate through the row items.
* Assign a property to Country object for each item's name/value.
*/
return $country;
}
// Create a Country[] list from rows list.
public function createCountryCollection(array $rows) {
/*
* Iterate through rows.
* Create a Country object for each row, with column names/values as properties.
* Push Country object object to collection.
* Return collection's content.
*/
return $this->countryCollection->all();
}
}
Db adapter
class PdoAdapter implements AdapterInterface {
private $connection;
public function __construct(PDO $connection) {
$this->connection = $connection;
}
public function find(string $sql, array $bindings = [], int $fetchMode = PDO::FETCH_ASSOC, $fetchArgument = NULL, array $fetchConstructorArguments = []) {
$statement = $this->connection->prepare($sql);
$statement->execute($bindings);
return $statement->fetchAll($fetchMode, $fetchArgument, $fetchConstructorArguments);
}
//...
}
Domain objects collection
class CountryCollection implements CountryCollectionInterface {
private $countries = [];
public function push(CountryInterface $country) {
$this->countries[] = $country;
return $this;
}
public function all() {
return $this->countries;
}
public function getIterator() {
return new ArrayIterator($this->countries);
}
//...
}
Domain object
class Country implements CountryInterface {
// Business logic: properties and methods...
}
You could inject the class names OR instances in the constructor:
class InputRepository
{
private $db;
protected $mappers = array();
public function __construct($db, array $mappers) {
$this->db = $db;
$this->mappers = $mappers;
}
public function getMapper($key) {
if (!isset($this->mappers[$key]) {
throw new Exception('Invalid mapper "'. $key .'"');
}
if (!$this->mappers[$key] instanceof MapperInterface) {
$this->mappers[$key] = new $this->mappers[$key]($this->db);
}
return $this->mappers[$key];
}
public function getCountry()
{
$mapper = $this->getMapper('country');
$country = $mapper->fetch();
return $country; //returns country object
}
// lots of other methods for returning different model objects
}
You would probably want to make the interface checking a bit more robust, obviously.
Ok so currently have this function in controller, which is called multiple times.
public function formatFloat($value)
{
return (float)sprintf('%0.6f', $value);
}
So I am trying to use getters and setters so I can just use
$model->$whatever;
and the formatting will be done.
In my model I have
public function getChargePeak()
{
return $this->charge_peak;
}
public function setChargePeak($value)
{
return $this->charge_peak = (float)sprintf('%0.6f', $value);
}
but when doing
$peak = $model->chargepeak;
var_dump($peak);die;
it is still returning as a string
If the charge_peak property is stored as string and you need a float in you app you should use
public function getChargePeak()
{
return floatval($this->charge_peak);
}
Anyway you should store the values in a coherent way as you use the values in your app ..
http://php.net/manual/en/function.floatval.php
So I suggest u another pattern: decorator and helpers. You should use a controller only to get data from request, prepare it for model and send it to view.
Formatting values is a helper logic. So create a new class
\common\helpers\Number.php
namespace common\helpers;
class Number
{
public static function formatFloat($value)
{
return (float)sprintf('%0.6f', $value);
}
}
Then create decorator for your model:
namespace common\models\decorators;
class YourModelDecorator
{
/**
* YourModel
*/
private $model;
public function __construct(YourModel $model)
{
$this->model = $model;
}
public function __get($name)
{
$methodName = 'get' . $name;
if (method_exists(self::class, $methodName)) {
return $this->$methodName();
} else {
return $this->model->{$name};
}
}
public function __call($name, $arguments)
{
return $this->model->$name($arguments);
}
public function getChargePeak()
{
return \common\helpers\Number::formatFloat($this->model->charge_peak);
}
}
and send it to view for example:
public function actionView($id)
{
$model = $this->loadModel($id);
$this->render('view', [
'model' => new \common\models\decorators\YourModelDecorator($model)
]);
}
I have a php singleton session class as follows.
class Session {
static private $_instance = NULL;
private function __construct()
{
session_start();
}
/**
* Prevents the class from being cloned
* #return NULL
*/
private function __clone() { }
/**
* Returns the singleton instance of this class
* #return Session
*/
public static function getInstance()
{
if (!self::$_instance) {
self::$_instance = new Session();
}
return self::$_instance;
}
public function __get($key) {
if (isset($_SESSION[$key])) {
return $_SESSION[$key];
}
return NULL;
}
public function __set($key, $value)
{
$_SESSION[$key] = $value;
}
public function __isset($key) {
return isset($_SESSION[$key]);
}
public function __unset($key) {
unset($_SESSION[$key]);
}
}
I can create an object as follows
$session = Session::getInstance();
$session->name = 'some name';
I can also get the value like
echo $session->name;
The problem is, i want to pass an array to this object and it is not working. for example, i wan to set something like
$_SESSION['user']['data'] = array('name'=>'some name',"empId"=>'123');
I am trying like this.
$session->['user']['data'] = array('name'=>'some name',"empId"=>'123');
but it is not working. Could you please suggest what is wrong.
The workaround in this case would be to use:
public function &__get($key) {
if (isset($_SESSION[$key])) {
return & $_SESSION[$key];
}
return NULL;
}
You need to modify the __get() method, because an assignment like
$session->user['data'] = ...
will actually retrieve the [user] key, and then try to assign a new subarray [data] to that temporary array result.
Also note that $session->['user']['data'] is invalid syntax. You either need $session->user['data'] or $session->{'user'}['data'].
Anyway, I think it is probably not a good idea to use a wrapper if you often want to do assignments like that. (I do actually have something very similar.)
$session->user = array('data' => array('name'=>'some name',"empId"=>'123'));
Make sure you don't overwrite anything else in user you want to keep
i've a little problem with the proper design for some simple database models. Lets say i have an User Object with getter/setters and an read method. Read querys the database and sets the properties.
class User extends MyDbBaseClass
{
protected $_id;
protected $_name;
public function setId($id)
{
$this->_id = $id;
}
public function setName($name)
{
$this->_name = $name;
}
public function getId()
{
return (int) $this->_id;
}
public function getName()
{
return (string) $this->_name;
}
public function read($id)
{
// fetch ONE record from Database
$this->_id = $this->setId($sqlResult['id');
$this->_name = $this->setName($sqlResult['name']);
}
public function save()
{
// do some sql stuff to save user to database
}
}
My Problem is, how to return multiple users?
public function getCollection()
{
// fetch all user records from database
forearch ($sqlResult as $result) {
// ... no idea..
}
}
Goal:
// works
$u = new User();
$u->read(1);
echo $u->getName();
// dont know the best way
$u = new User();
$uC = $u->getCollection();
foreach ($uC as $u)
{
echo $u->getName();
}
Any best practices for this?
You could just return an array with users
public function getCollection()
{
// fetch all user records from database
$users = array();
forearch ($sqlResult as $result) {
// ... no idea..
$user = new User();
$user->_name = $result->name; // just an example
$user[] = $users;
}
return $users;
}