I have a next system of objects (simple example):
class Grid
{
public State $state;
// Other fields
public function __construct(State $state)
{
$this->state = $state;
}
// ...
}
class State
{
public bool $isCompleted = false;
public ?User $judge;
}
class User
{
}
Disclaimer: Grid class is a legacy ActiveRecord model which can't be covered by isolated unit tests because it writes to a database and changes some other data in the system. So I'm only interested in State class.
I need a mutator class for State. It must be easy to test. It's look like this:
class StateMutator
{
public function mutate(State $state, array $changes):?State
{
// ...
$state->isCompleted = true;
// ...
if(!$someCondition){
return null;
}
// ...
return $state;
}
}
And it's used in this way:
/** #var Grid $grid */
/** #var array $changes */
$newState = (new StateMutator())->mutate($grid->state, $changes);
if($newState !== null){
$grid->state = $newState;
}
// Some other changes in $grid
$grid->saveChanges();
It looks good. But something confuses me. If the mutator does some changes in an obtained object and returns null after this, so calling code will thinks that State hasn't changed - makes some other changes in it and save it to the database. But because PHP pass objects by reference, changes which the mutator made in the state object will be save to the database too. And it's a problem.
What should I do to avoid this problem?
I have two ways to solve this problem, but both of them have big problems.
If the mutator can't change the state object in any place inside it, it should revert changes which it has already done. But it's difficult to do or even impossible in some cases.
The mutator should clone the state, mutate it's copy and return it. But in this case the method will need much more memory (the state can have more then 1000 objects in it's property).
May be someone have any idea?
I would generally avoid that objects can get into an invalid state. Your State object should have methods that either mutate its state or return a clone with the new state. These methods validate the input and mutate/return a clone only if the resulting state is valid. No need to keep track of changes and rolling back.
Related
So, in my framework X, let it be Phalcon, I often create models objects.
Let's assume that all fields already validated. Questions related only about creation logic.
A simple example of creating Users object and save it to DB:
<?php
$user = new Users();
$user->setName($name);
$user->setLastName($lastname);
$user->setAge($age);
$user->create();
For simplicity, I show here only 3 fields to setup, in the real world they always more.
I have 3 questions:
1) What the best way to encapsulate this logic in Factory class? If I create Factory class that will create objects like Users object, every time I will need pass long amount of parameters.
Example:
<?php
$factory = new UsersFactory();
$factory->make($name, $lastname, $address, $phone, $status, $active);
2) Even if I implement Factory in a way showed above - should Factory insert data in DB? In my example call method create()? Or just perform all setters operations?
3) And even more, what if i will need to create Users objects with relations, with other related objects?
Thank you for any suggestions.
Your question starts out simple and then builds with complexity. Reading your post it sounds like your concerned about the number of arguments you would have to pass to the method to build the object. This is a reasonable fear as you should try to avoid functions which take more than 2 or 3 args, and because sometimes you will need to pass the 1st 3rd and 5th arg but not the 2nd and 4th which just gets uncomfortable.
I would instead encourage you to look at the builder pattern.
In the end it will not be that much different than just using your User object directly however it will help you prevent having a User object in an invalid state ( required fields not set )
1) What the best way to encapsulate this logic in Factory class? If I create Factory class that will create objects like Users object, every time I will need pass long amount of parameters.
This is why I recommended the builder pattern. To avoid passing a large number of params to a single function. It also would allow you to validate state in the build method and handle or throw exceptions.
class UserBuilder {
protected $data = [];
public static function named($fname, $lname) {
$b = new static;
return $b
->withFirstName($fname)
->withLastName($lname);
}
public function withFirstName($fname) {
$this->data['first_name'] = $fname;
return $this;
}
public function withFirstName($lname) {
$this->data['last_name'] = $lname;
return $this;
}
public function withAge($age) {
$this->data['age'] = $age;
return $this;
}
public function build() {
$this->validate();
$d = $this->data;
$u = new User;
$u->setFirstName($d['first_name']);
$u->setLastName($d['last_name']);
$u->setAge($d['age']);
return $u;
}
protected function validate() {
$d = $this->data;
if (empty($d['age'])) {
throw new Exception('age is required');
}
}
}
then you just do..
$user = UserBuilder::named('John','Doe')->withAge(32);
now instead of the number of function arguments growing with each param, the number of methods grows.
2) Even if I implement Factory in a way showed above - should Factory insert data in DB? In my example call method create()? Or just perform all setters operations?
no it should not insert. it should just help you build the object, not assume what your going to do with it. You may release that once you build it you will want to do something else with it before insert.
3) And even more, what if i will need to create Users objects with relations, with other related objects?
In Phalcon those relationships are part of the entity. You can see in their docs this example:
// Create an artist
$artist = new Artists();
$artist->name = 'Shinichi Osawa';
$artist->country = 'Japan';
// Create an album
$album = new Albums();
$album->name = 'The One';
$album->artist = $artist; // Assign the artist
$album->year = 2008;
// Save both records
$album->save();
So to relate this back to your user example, suppose you wanted to store address information on the user but the addresses are stored in a different table. The builder could expose methods to define the address and the build method would create both entities together and return the built User object which has a reference to the Address object inside it because of how Phalcon models work.
I don't think it's entirely necessary to use a builder or "pattern" to dynamically populate your model properties. Though it is subjective to what you're after.
You can populate models through the constructor like this
$user = new Users([
'name' => $name,
'lastName' => $lastname,
'age' => $age,
]);
$user->create();
This way you can dynamically populate your model by building the array instead of numerous method calls.
It's also worth noting that if you want to use "setters" and "getter" methods you should define the properties as protected. The reason for this is because Phalcon will automatically call the set/get methods if they exist when you assign a value to the protected property.
For example:
class User extends \Phalcon\Mvc\Model
{
protected $name;
public function setName(string $name): void
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
$user= new MyModel();
$user->name = 'Cameron'; // This will invoke User::setName
echo $user->name; // This will invoke User::getName
It is also worth noting that the properties will behave as you'd expect a protected property to behave the same as a traditional protected property if the respective method is missing. For example, you cannot assign a value to a protected model property without a setter method.
I'm relatively new to PHP (and to programming in general). Keep that in mind.
Here's the basic scenario. I am building a web-based mini-university.
There are two key classes: Lesson and LessonSeries.
Lesson has a Series property, which stores a singular LessonSeries if that lesson belongs to a series. It may not.
LessonSeries has a Lessons property, which will hold an array of Lesson objects..
Both classes have an 'Id' property, which is an integer and will be unique to their class. My database ensures there will not be two Lessons or two LessonSeries with the same Id.
In numerous pages throughout my website, Lesson objects are iterated and (if they have a series), they will display the series they belong to, and sometimes use some of the other LessonSeries methods and properties.
So here's the issue: When a Lesson is pulled from the Db, it constructs a LessonSeries to correspond to it. But I don't want there to exist 20 instances of the same LessonSeries. Certain methods trigger a db query that only needs to be executed once per LessonSeries. This could lead to exponentially more db activity than necessary if there were 20 instances of essentially the same series.
I'm new to programming patterns, but here's what I think I need:
I want a registry of unique LessonSeries.
I want each unique LessonSeries to be shared by all lessons that
belong to it.
Ideally, I want this functionality to be within the LessonSeries class, without having to have a second manager class, if it's at all possible.
Basically, what I want is that, whenever a LessonSeries is constructed, the registry will be checked first for the existence of an the id. If so, what is returned is a reference to the existing LessonSeries. If it DOESN'T exist in the registry, then it is created as usual.
With this said, I have no idea how to make this happen in PHP.
Edit:
It was pointed out in the comments that I need to demonstrate I've attempted to solve the problem for this to be a good question, of sorts, on SO. But that's exactly my problem.
I considered doing something like this:
class LessonSeries {
private static $registry;
public $Id;
public $SeriesName;
public $ImagePath;
public $Description;
private $index;
private $lessons;
private $lessonCount;
public function __construct($seriesName, $imagePath=null, $Id=null, $description = null) {
if(isset(self::$registry[$id])){
return self::$registry[$Id];
}else{
$this->SeriesName = $seriesName;
$this->ImagePath = $imagePath;
$this->Id = $Id;
$this->Description = $description;
self::$registry[$Id] = $this;
}
}
However, I don't think this is how php constructors work. I think I know how to do what I want in Python (using subclasses and such), but PHP doesn't have subclasses. And that's where I'm struggling.
Here's what you can do.
Create a factory object. Move all creation/caching logic there.
Pass that factory to newly created lessons and series so that they could delegate creation to it.
In your page code, strat by creating a factory.
Try to avoid statics if you can. For instance, your code will be easier to unit test if it's real objects.
Here's a pseudo-code demonstrating it:
class LessonsFactory {
private $lessons = [];
private $series = [];
public getLesson($id) {
if (isset($this->lessons[$id])) {
return $this->lessons[$id];
}
$lesson = $this->loadLesson($id);
$this->lessons[$id] = $lesson;
$this->getSeries($lesson[$id]);
}
private loadLesson($id) {
$data = ... // load lesson from db
return new Lesson($data, $this);
}
public getSeries($id) {
if (isset($this->series[$id])) {
return $this->series[$id];
}
$series = $this->loadSeries($id);
$this->series[$id] = $series;
}
private loadSeries($id) {
$data = ... // load series from db
return new LessonSeries($data, $this);
}
}
class Lesson {
private $factory;
public function __construct($data, LessonFactory $factory) {
$this->factory = $factory;
// + code to intialize object with $data
}
// this is how you get series from lessons
public function getSeries() {
return $this->factory->getSeries($this->seriesId);
}
}
// somewhere in your page controller code
$factory = new LessonsFactory();
$lesson = $factory->getLesson($_GET['lessond_id']);
I have two database tables Players and Units. Respectively I also have two classes in PHP that look pretty identically in the base.
class Player {
private $id;
private $name;
// a bunch of other properties
public function __construct($id){
// fetch info from database with $id and populate properties
}
}
class Unit {
private $id;
private $name;
// a bunch of other properties
public function __construct($id){
// fetch info from database with $id and populate properties
}
}
But one player can have multiple units, so I implemented the method loadUnits() in the Player class
public function loadUnits(){
$units = array();
foreach($db->prepare('SELECT id FROM units WHERE owner = ?')->execute([$this->id])->fetchAll() as $unit){
$units[] = new Unit($unit['id']);
}
return $units;
}
The problem is that constructing Unit X number of times will make X number of calls to the database and this is really something I don't like. I would like to ask what are the good practices and how is this done in reality? Thanks!
Instead of just selecting the id in loadUnits and then issuing another query for the properties per id, I recommend getting all unit properties with a single query and passing those properties to the constructor of Unit
//don't just get the id's, get the actual unit properties
public function loadUnits(){
$units = array();
foreach($db->prepare('SELECT <all properties> FROM units WHERE owner = ?')->execute([$this->id])->fetchAll() as $properties){
$units[] = new Unit($properties['id'],$properties);
}
return $units;
}
class Unit {
private $id;
private $name;
// a bunch of other properties
//make the unit properties an optional parameter and use it
//instead of querying the db if available
public function __construct($id,$properties=null){
if(is_null($properties)) {
// fetch info from database with $id and populate properties
}
else {
// populate via $properties
}
}
}
I have a similar problem in one of my projects. Calling Room->computeDoors() will run a query to find what Doors are attached to the Rooms, then for each of them it has to run a query to find out what the Door's properties are... and then it has to query each room to find out what the door is connected to!
Problem solved: Memcache. Store as much as you can in cache, that way the data's already there to be used no matter how many times you need it, even across pageloads/AJAX calls, or even across users! Just make sure to invalidate or update the cache when you update the object's state.
You can solve this bei either using an ORM like Doctrine or Eloquent.
In Doctrine you define the relations between your entities, it will automatically generate the SQL and tables, it will generate proxies containing findBy() methods, give notes about wrong relations or missing values.
Doctrine has implemented different fetching methods and caching for example persistence of entities and lazy loading. You define your model and Doctrine takes care of everything else the most stable and fastest way.
If you want to implement your own, you should cache the results locally in the instance.
private $units;
protected function loadUnits(){
$units = array();
foreach($db->prepare('SELECT id FROM units WHERE owner = ?')->execute([$this->id])->fetchAll() as $unit){
$units[] = new Unit($unit['id']);
}
$this->setUnits($units);
return $this;
}
public function setUnits($units) {
assert(is_array($units));
$this->units = $units;
return $this;
}
public function getUnits() {
// this if needs improvement to fit your needs
if (!is_array($this->units)) {
$this->loadUnits();
}
return $this->units;
}
I've an ORM model (PHP Active Record), say, for a blogging system. I've something that's a post model that stores the number of likes. The post could either be a picture or quote (say), and they are different tables (and hence models).
The schema is that a post holds data like number of shares, likes, description, etc. along with either a picture or a quote.
So when writing getters for the post model I'm having to write
public function getX() {
if ($this->isPicture()) {
return $this->picture->getX();
}
else if ($this->isQuote()) {
return $this->quote->getX()
}
else {
return self::DEFAULT_X
}
}
I'm currently having to write this structure for many getter. Is there something I can do to avoid that?
PS: Tagged as PHP because that's my code in.
EDIT
Changed comments to code.
This is a model (and a corresponding table in the DB) that has more data than just a picture and quote. Example, description that's part of the post and doesn't reside on either the picture or the quote.
There's tables for pictures and quotes.
Using PHP Active Record and each of the three classes extends the generic model class provided by PHP Active Record.
The picture model has it's own data. Same for quote.
To expand on the idea of the Strategy pattern mentioned in the comments:
class Post {
// get the correct 'strategy'
public function getModel() {
if ($this->isPicture()) {
return $this->picture;
}
if ($this->isQuote()) {
return $this->quote;
}
return null;
}
// using the strategy
public function getX() {
$model = $this->getModel();
if (null === $model) {
return self::DEFAULT_X;
}
return $model->getX();
}
}
Each strategy would presumably implement the same interface as the Post class for exposing those getters. Even better would be to provide a default strategy (rather than returning null) and have that return the default values. That way, the null check in each getter becomes redundant.
An alternative approach to this is a very basic form of metaprogramming. The idea is that you go a level higher than calling your methods by hand, and let the code do it for you.
(Assume that the method definitions are all part of Post)
public function getX($model = null) {
if ($model) return $model->getX();
else return self::DEFAULT_X;
}
// usage
$postModel->getX($pictureModel);
What's happening here is that, in this single instance of getX in your Post model, you're passing in the name of another class, and executing the `getX' method on that instance (if it exists and is callable).
You can extend this in other ways. For example, maybe you don't want to pass an instance in, when the method can do it anyway:
public function getX($model_name = null) {
if ($model_name && $class_exists($model_name) && is_callable(array($model_name, 'getX')) {
$model = new $model_name;
return $model->getX();
} else {
return self::DEFAULT_X;
}
}
// usage
$postModel->getX('Picture');
In this instance, you pass the model in as a string, and the method will do the rest. While this makes it quicker to get what you want, you might find that you don't want to work with fresh instances all the time (or you can't), so there's a bit of a trade-off with this 'convenient' way.
That still doesn't fully solve your problem, though, since you still have to repeat that for each getter, over and over again. Instead, you can try something like this:
public function __call($method, $args) {
$class = $args[0];
if (class_exists($class) && is_callable(array($class, $method))) {
$model = new $class;
return $model->$method();
}
}
// usage
$postModel->getX('Picture');
$postModel->getY('Quote');
$postModel->getZ('Picture');
If you call a function that doesn't exist on the Post model, that magic method will be called, and it'll fire up a new instance of the model name you supply as an argument, and call the getWhatever method on it, if it exists.
It's important to note that you must not define these getters in Post, unless you want to override the methods in the other classes.
There is still the problem of this creating new instances all the time, though, and to remedy this you can use a bit of dependency injection. This means that you let the Post class contains a list of other instances of classes that it wants to use in future, so you can add and remove them at will.
This is what I would consider the actual solution, with the other examples hopefully showing how I've got here (will edit to clarify things, of course).
public $models = array();
public function addModel($instance) {
$this->models[get_class($instance)] = $instance;
}
public function __call($method, $args) {
$class = $args[0];
if (array_key_exists($class, $this->models)) {
$model = $this->models[$class];
if (is_callable(array($model, $method)) {
return $model->$method();
}
}
}
// usage
$this->addModel($pictureModel);
$this->addModel($quoteModel);
$this->getX('Picture');
$this->getY('Quote');
Here, you're passing in your existing instances of models into the Post class, which then stores them in an array, keyed by the name of the class. Then, when you use the class as described in the last example, instead of creating a new instance, it will use the instance it has already stored. The benefit of this is that you might do things to your instances that you'd want reflected in the Post model.
This means that you can add as many new models as you like that need to plug into Post, and the only thing you need to do is inject them with addModel, and implement the getters on those models.
They all require you to tell the class what models to call at some point or another. Since you have an array of dependent models, why not add a way to get everything?
public function __call($method, $args) {
$class = $args[0];
if (array_key_exists($class, $this->models)) {
$model = $this->models[$class];
if (is_callable(array($model, $method)) {
return $model->$method();
}
} elseif ($class === 'all') {
// return an array containing the results of each method call on each model
return array_map(function($model) use ($method) {
if (is_callable(array($model, $method) return $model->$method();
}, $this->models);
}
}
// usage
$postModel->getX('all');
Using this, you'll get an array containing the return values of each getX method on each model you added with addModel. You can create pretty powerful functions and classes that do all this stuff without you having to repeat tedious logic.
I have to mention that these examples are untested, but at the very least I hope the concept of what you can do has been made clear.
Note:
The same thing can be applied to __GET and __SET methods, too, which are used for accessing properties. It's also worth saying that there may be the slight risk of a library already using these magic methods, in which case you'll need to make the code a little more intelligent.
I have recently started reading about dependency injection and it has made me rethink some of my designs.
The problem i have is something like this:
Let's say i have two classes: Car and Passenger;
For those two classes i have some data mappers to work with the database: CarDataMapper and PassengerDataMapper
I want to be able to do something like this in code:
$car = CarDataMapper->getCarById(23); // returns the car object
foreach($car->getPassengers() as $passenger){ // returns all passengers of that car
$passenger->doSomething();
}
Before I knew anything about DI, I would build my classes like this:
class Car {
private $_id;
private $_passengers = null;
public function getPassengers(){
if($this->_passengers === null){
$passengerDataMapper = new PassengerDataMapper;
$passengers = $passengerDataMapper->getPassengersByCarId($this->getId());
$this->setPassengers($passengers);
}
return $this->_passengers;
}
}
I would also have similar code in the Passenger->getCar() method to fetch the car the passenger is in.
I now understand that this creates dependencies (well, I understood it before too, but I wasn't aware that this is "wrong") between the Car and the Passenger objects and the data mapper objects.
While trying to think of the solution for this two options came to mind, but I don't really like any of them:
1: Doing something like this:
$car = $carDataMapper->getCarById(23);
$passengers = $passengerDataMapper->getPassengersByCarId($car->getId());
$car->setPassengers($passengers);
foreach($car->getPassengers() as $passenger){
$passenger->doSomething();
}
But what if passengers have objects that they need injected, and what if the nesting goes to ten or twenty levels... I would wind up instantiating nearly every object in the start of my application, which would in turn query the entire database during the process.
If i have to send the passenger to another object which has to do something with the objects that the passenger holds, I do not want to immediately instantiate these objects too.
2: Injecting the data mappers into the car and passenger objects and having something like this:
class Car {
private $_id;
private $_passengers = null;
private $_dataMapper = null;
public function __construct($dataMapper){
$this->setDataMapper($dataMapper);
}
public function getPassengers(){
if($this->_passengers === null && $this->_dataMapper instanceof PassengerDataMapper){
$passengers = $this->_dataMapper->getPassengersByCarId($this->getId());
$this->setPassengers($passengers);
}
return $this->_passengers;
}
}
I dont like this any better, because it's not like the Car is really unaware of the data mapper, and without the data mapper, the Car could behave unpredictably (not returning passengers, when it actually has them)
So my first question is:
Am I taking a completely wrong approach here, because, the more I look at it, the more it looks like I'm building an ORM, instead of a business layer?
The second question is:
is there a way of actually decoupling the objects and the data mappers in a way that would allow me to use the objects as described in the very first code block?
Third question:
I've seen some answers for other languages (some version of C, I think) resolving this issue with something like this described here:
What is the proper way to inject a data access dependency for lazy loading?
As I haven't had time to play with other languages, this makes no sense to me, so I'd be grateful if someone would explain the examples in the link in PHP-ish.
I have also looked at some DI frameworks, and read about DI Containers and Inversion of Control, but from what I understood they are used to define and inject dependencies for 'non dynamic' classes, where for instance, the Car would depend on the Engine, but it would not need the engine to be loaded dynamically from the db, it would simply be instantiated and injected into the Car.
Sorry for the lengthy post and thanks in advance.
Maybe off-topic, but I think that it will help you a bit:
I think that you try to achieve the perfect solution. But no matter what you come up with, in a couple of years, you will be more experienced and you'll definitely be able to improve your design.
Over the past years with my colleagues we had developed many ORMs / Business Models, but for almost every new project we were starting from scratch, since everyone was more experienced, everyone had learned from the previous mistakes and everyone had come across with new patterns and ideas. All that added an extra month or so in development, which increased the cost of the final product.
No matter how good the tools are, the key problem is that the final product must be as good as possible, at the minimum cost. The client won't care and won't pay for things that can't see or understand.
Unless, of course, you code for research or for fun.
TL;DR: Your future self will always outsmart your current self, so do not overthink about it. Just pick carefully a working solution, master it and stick with it until it won't solve your problems :D
To answer your questions:
Your code is perfectly fine, but the more you will try to make it "clever" or "abstract" or "dependency-free", the more you will lean towards an ORM.
What you want in the first code block is pretty feasible. Take a look at how the Doctrine ORM works, or this very simple ORM approach I did a few months ago for a weekend project:
https://github.com/aletzo/dweet/blob/master/app/models
I was going to say "I know this is an old question but..." then I realized you posted it 9 hours ago, which is cool, because I just came to a satisfactory 'resolution' for myself. I thought of the implementation and then I realized it is what people were calling 'dependency injection'.
Here is an example:
class Ticket {
private $__replies;
private $__replyFetcher;
private $__replyCallback;
private $__replyArgs;
public function setReplyFetcher(&$instance, $callback, array $args) {
if (!is_object($instance))
throw new Exception ('blah');
if (!is_string($callback))
throw new Exception ('blah');
if (!is_array($args) || empty($args))
throw new Exception ('blah');
$this->__replyFetcher = $instance;
$this->__replyCallback = $callback;
$this->__replyArgs = $args;
return $this;
}
public function getReplies () {
if (!is_object($this->__replyFetcher)) throw new Exception ('Fetcher not set');
return call_user_func_array(array($this->__replyFetcher,$this->__replyCallback),$this->__replyArgs);
}
}
Then, in your service layer (where you 'coordinate' actions between multiple mappers and models) you can call the 'setReplyFetcher' method on all of the ticket objects before you return them to whatever is invoking the service layer -- OR -- you could do something very similar with each mapper, by giving the mapper a private 'fetcherInstance' and 'callback' property for each mapper the object is going to need, and then set THAT up in the service layer, then the mapper will take care of preparing the objects. I am still weighing the differences between the two approaches.
Example of coordinating in the service layer:
class Some_Service_Class {
private $__mapper;
private $__otherMapper;
public function __construct() {
$this->__mapper = new Some_Mapper();
$this->__otherMapper = new Some_Other_Mapper();
}
public function getObjects() {
$objects = $this->__mapper->fetchObjects();
foreach ($objects as &$object) {
$object->setDependentObjectFetcher($this->__otherMapper,'fetchDependents',array($object->getId()));
}
return $objects;
}
}
Either way you go, the object classes are independent of mapper classes, and mapper classes are independent of each other.
EDIT: Here is an example of the other way to do it:
class Some_Service {
private $__mapper;
private $__otherMapper;
public function __construct(){
$this->__mapper = new Some_Mapper();
$this->__otherMapper = new Some_Other_Mapper();
$this->__mapper->setDependentFetcher($this->__otherMapper,'someCallback');
}
public function fetchObjects () {
return $this->__mapper->fetchObjects();
}
}
class Some_Mapper {
private $__dependentMapper;
private $__dependentCallback;
public function __construct ( $mapper, $callback ) {
if (!is_object($mapper) || !is_string($callback)) throw new Exception ('message');
$this->__dependentMapper = $mapper;
$this->__dependentCallback = $callback;
return $this;
}
public function fetchObjects() {
//Some database logic here, returns $results
$args[0] = &$this->__dependentMapper;
$args[1] = &$this->__dependentCallback;
foreach ($results as $result) {
// Do your mapping logic here, assigning values to properties of $object
$args[2] = $object->getId();
$objects[] = call_user_func_array(array($object,'setDependentFetcher'),$args)
}
}
}
As you can see, the mapper requires the other resources to be available to even be instantiated. As you can also see, with this method you are kind of limited to calling mapper functions with object ids as parameters. I'm sure with some sitting down and thinking there is an elegant solution to incorporate other parameters, say fetching 'open' tickets versus 'closed' tickets belonging to a department object.
Here is another approach I thought of. You can create a 'DAOInjection' object that acts as a container for the specific DAO, callback, and args needed to return the desired objects. The classes then only need to know about this DAOInjection class, so they are still decoupled from all of your DAOs/mappers/services/etc.
class DAOInjection {
private $_DAO;
private $_callback;
private $_args;
public function __construct($DAO, $callback, array $args){
if (!is_object($DAO)) throw new Exception;
if (!is_string($callback)) throw new Exception;
$this->_DAO = $DAO;
$this->_callback = $callback;
$this->_args = $args;
}
public function execute( $objectInstance ) {
if (!is_object($objectInstance)) throw new Exception;
$args = $this->_prepareArgs($objectInstance);
return call_user_func_array(array($this->_DAO,$this->_callback),$args);
}
private function _prepareArgs($instance) {
$args = $this->_args;
for($i=0; $i < count($args); $i++){
if ($args[$i] instanceof InjectionHelper) {
$helper = $args[$i];
$args[$i] = $helper->prepareArg($instance);
}
}
return $args;
}
}
You can also pass an 'InjectionHelper' as an argument. The InjectionHelper acts as another callback container -- this way, if you need to pass any information about the lazy-loading object to its injected DAO, you won't have to hard-code it into the object. Plus, if you need to 'pipe' methods together -- say you need to pass $this->getDepartment()->getManager()->getId() to the injected DAO for whatever reason -- you can. Simply pass it like getDepartment|getManager|getId to the InjectionHelper's constructor.
class InjectionHelper {
private $_callback;
public function __construct( $callback ) {
if (!is_string($callback)) throw new Exception;
$this->_callback = $callback;
}
public function prepareArg( $instance ) {
if (!is_object($instance)) throw new Exception;
$callback = explode("|",$this->_callback);
$firstCallback = $callback[0];
$result = $instance->$firstCallback();
array_shift($callback);
if (!empty($callback) && is_object($result)) {
for ($i=0; $i<count($callback); $i++) {
$result = $result->$callback[$i];
if (!is_object($result)) break;
}
}
return $result;
}
}
To implement this functionality in the object, you would require the injections at construction to ensure that the object has or can get all of the information it needs. Each method that uses an injection simply calls the execute() method of the respective DAOInjection.
class Some_Object {
private $_childInjection;
private $_parentInjection;
public function __construct(DAOInjection $childInj, DAOInjection $parInj) {
$this->_childInjection = $childInj;
$this->_parentInjection = $parInj;
}
public function getChildObjects() {
if ($this->_children == null)
$this->_children = $this->_childInjection->execute($this);
return $this->_children;
}
public function getParentObjects() {
if ($this->_parent == null)
$this->_parent = $this->_parentInjection->execute($this);
return $this->_parent;
}
}
I would then, in the constructor of my service class, instantiate the mappers relevant to that service using the relevant DAOInjection classes as arguments for the mappers' constructors. The mappers would then take care of making sure each object has its injections, because the mapper's job is to return complete objects and handle the saving/deleting of objects, while the service's job is to coordinate the relationships between various mappers, objects, and so on.
Ultimately you can use it to inject callbacks to services OR mappers, so say you want your 'Ticket' object to retrieve a parent user, which happens to be outside the realm of the 'Ticket Service' -- the ticket service can just inject a callback to the 'User Service', and it won't have to know a thing about how the DAL works for other objects.
Hope this helps!