I'm trying to adopt SOLID principles in my app.
Say I have these 2 models:
Client (fields=id, name, address etc) which hasMany:
Holdings (fields=id, client_id, ticker, holding_date, value)
In my ClientsController I might have a method like so:
public function show($id)
{
$client = Client::find($id);
$client->setValuations();
$valuations = $client->getValuations();
return View::make('clients.show')-with(compact('client', 'valuations'));
}
So in the controller I want to get the valuations over time for the client. The setValuations() on my client model performs a fairly complex query which sums the holdings for a client and sets the resulant collection as a property on the client.
So my client model might look a bit like:
class Client extends \Eloquent {
// All the usual model stuff
protected $valuations;
public function setValuations()
{
$this->valuations = DB::table('holdings')
->select('holdings.holding_date', DB::raw('SUM(holdings.value) AS sumofvalue') )
->where('holdings.client_id', $this->id)
->where('holdings.holding_date', DB::raw('LAST_DAY(holdings.holding_date)') )
->groupBy('holdings.holding_date')
->orderBy('holdings.holding_date', 'asc')
->get();
return $this;
}
public function getValuations()
{
return $this->valuations;
}
}
As we can see this is a fair amount of crap to put in a model (and I have condensed it a lot for the sake of brevity!). I should think that using a repository pattern might be the best approach but I'm unsure of how to structure this. Assuming I have several client-related attributes that need a fair amount of querying or processing to determine what they are (such as returns, transactions in client currency - this would require applying fx conversion to a collection of values, for example), how would be the best way to structure this and where should the logic be placed?
You are absolutely right. Keeping logic like this bloats the model and leads to violations of SOLID principles. I use the repository pattern to move this kind of logic out of my model (who should not have that responsibility i.e. Single Responsibility Principle) and into a dedicated class. I suggest you take a look at an open source project like the Laravel.io website that utilizes the repository pattern. After seeing their code you should be able to apply the pattern for yourself. Here is a link https://github.com/LaravelIO/laravel.io
I also highly recommend https://www.laracasts.com . SOLID principles and the repository pattern are thoroughly discussed and has drastically improved my understanding of Laravel and coding best practices.
Related
I'm new to Laravel and am working on a collection of API endpoints that fetch data from a variety of database tables, transforms and processes that data, and then returns it as a JSON response. What I'm struggling to decide, from reading the documentation, is where my processing/transformation code should live.
I've setup my routes in routes/api.php, and have them point towards a Controller subclass, but from here things get a little murky because I'm not currently looking to make use of Eloquent ORM. It seems like typically I'd generate Model subclasses for each database table, and have a Repository call into those and transform the returned data, like so:
Route => Controller => Repository => Model
But where should I be placing both the database query code, and the logic required to process/transform that data (the business logic), when not making use of Eloquent ORM or the Model paradigm? Making the controller fat with DB queries and logic seems like a messy solution, but at the same time a lot of the Laravel DB example code does place logic in Controller subclasses.
So I'll offer an approach that is I think accomplishes what you are asking. In the API I developed, I used the route/api.php file to create the API endpoints. Each of these point to a Controller where the authentication and request validation is performed. The Controller then calls a Service class that handles all the business logic of the application and these are the real heavy lifting parts. The Service class makes calls to a Repository, which actually performs Model changes and saving.
I saw this used in another project several years ago and mimicked it for my projects. The code flow is shown below. Not sure if it will work for you, but I find it to be a very neat and keeps the code grouped together in logical ways.
Route => Controller => Service => Repository => Model
Here is an example of how you can setup your project without Eloquent.
Models are just data containers (records). They don't know how or where to store themselves. They are instantiated with some data and provide access for it:
class OrderRecord
{
protected $id;
protected $createdAt;
public function __construct($id, $createdAt = null)
{
$this->id = $id;
$this->createdAt = $createdAt ?: date('d-m-Y H:i:s');
}
public function getID()
{
return $this->id;
}
...
}
You read and write models only via repositories. Repository is just a class with your typical methods to retrieve/write one or multiple records (find, findAll, update, delete etc). It reads data, instantiates record(s) with it or take record(s), gets data from them and writes it. For example:
class OrderRepository
{
public function find($id): OrderRecord
{
// Here goes your querying. StdClass will be returned in this case
// that will be used to create new record.
$row = DB::table('orders')->find($id);
if ($row) {
return (new OrderRecord($row->id, $row->created_at));
} else {
// If not found you can throw an exception or return null
// (return type should be ?OrderRecord then) depending on
// how you want to design your workflow with repositories.
...
}
}
...
}
Lastly your business logic can be in your controllers. Your controllers should use repositories to get your model objects, work with them and the use repositories to store result if needed.
Obviously, you will need to implement base classes/interfaces for all your entities. Use dependency injection and Laravel's service container to glue everything together.
You can also extend this design by separating querying and mapping in your repository - you will have Repository classes that know only how to retrieve/store raw data and you will have Mapper classes that know how to create/retrieve raw data to/from your records (and serve as factories that repository will use to generate record objects from query results). If business logic also need to be interchangeable then you can also take business logic out of your controllers and put it into separate classes that your controller can utilize.
Here is an excellent article about repository-based approach and how you can extend it.
After developing a few projects using Codeigniter since 2 years, I stared to learn Laravel.
I downloaded a few projects lo learn how they are coded. As I understood, many of them are using only models, views and controllers which is same as Codeigniter.
But one project has used repositories and interfaces. It is really hard to understand whats going on that project. So what is the usage of repositories and interfaces in Laravel? When should I use them?
I will try to explain as clearly as possible the two concepts.
Interfaces\Contracts
In general OOP interfaces are used to describe which methods/functionalities the class that implements that interface is offering without caring about the actual implementation.
Laravel uses Contracts mainly to separate a service from the actual implementation. To be more clear let's make an example
<?php
namespace App\Orders;
class OrdersCache
{
protected $cache;
public function __construct(\SomePackage\Cache\Memcached $cache)
{
$this->cache = $cache;
}
public function find($id)
{
if ($this->cache->has($id)) {
//
}
}
}
As you can see in this class the code is tightly coupled to a cache implementation (i.e. \SomePackage\Cache\Memcached) so if the API of that Cache class changes our code also must be changed accordingly. The same thing happens if we want to change the Cache implementation with another one (e.g. redis).
Instead of doing that, our code could depend on an interface that is agnostic from the implementation:
<?php
namespace App\Orders;
use Illuminate\Contracts\Cache\Repository as Cache;
class OrdersCache
{
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
public function find($id)
{
if ($this->cache->has($id)) {
//
}
}
}
Now our code is not coupled with any specific implementation because Cache is actually an interface. So basically in our class we are requiring an instance of a class that behaves like described in the Cache interface, but we are not really interested in how it works internally. Doing that if we want to change the cache implementation we could write a class that implements the interface Cache without changing any line of code in our OrdersCache class. Doing that our code is easier to understand and maintain and your packages are a lot more reusable. See the section Loose Coupling in the Laravel documentation for further examples.
Interfaces and Service Container
One of the main features of Laravel is its Service Container, it is used to manage dependencies and performing dependency injection. Please take a look at Service Container definition from Laravel documentation.
Dependency Injection is widely used by Laravel also to bind interfaces to implementation. Let's make an example:
$app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');
And let our class be
<?php
namespace App\Http\Controllers;
use App\Contracts\EventPusher;
class EventsController extends Controller
{
protected $pusher;
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
}
Without declaring anything else we are basically saying everytime that someone need an EventPusher instance, please Laravel, provide an instance of RedisEventPusher class. In this case everytime that your controller is instantiated, Laravel will pass an instance of RedisEventPusher to your controller without specifying anything else.
You can dig into that by looking at Binding Interfaces to Implementation section on the Laravel documentation.
Repositories
Repositories is a concept applicable to the MVC pattern independently from any specific framework. Typically you have your Model that is the data layer (e.g. interacts with the database directly), your Controller that handles the access logic to the data layer and your View that shows the data provided by the Controller.
Repositories instead could be defined as follows:
To put it simply, Repository pattern is a kind of container where data access logic is stored. It hides the details of data access logic from business logic. In other words, we allow business logic to access the data object without having knowledge of underlying data access architecture.
Soruce: https://bosnadev.com/2015/03/07/using-repository-pattern-in-laravel-5
To know how to use them within Laravel please take a look at this great article.
That's all, i hope it helps to clear up your mind.
Interfaces are what any implementing class should call.
interface CanFlyInterface
{
public function fly();
}
Think of it like programming without bothering with logic.
if ($object instanceof CanFlyInterface) {
$obj->fly();
}
Now we could have passed a Bird object, or an Aeroplane object! PHP DOESN'T CARE, so long as it implements the interface!
class Bird implements CanFlyInterface
{
public function fly()
{
return 'flap flap!';
}
}
class Aeroplane implements CanFlyInterface
{
public function fly()
{
return 'roar! whoosh!';
}
}
Your other question, what a Repository class is. It's just a class that keeps all your DB queries in the one place. Check this interface as an example:
interface RepositoryInterface
{
public function insert(array $data);
public function update(array $data);
public function findById($id);
public function deleteById($id);
}
Hopefully this should clear things up for you! Good luck with all your PHP coding :-D
Let's start with the easier one, the interface:
You normally use interfaces to implement classes with required methods:
http://php.net/manual/en/language.oop5.interfaces.php
Laravel's Contracts are a set of interfaces that define the core services provided by the framework. For example, a Illuminate\Contracts\Queue\Queue contract defines the methods needed for queueing jobs, while the Illuminate\Contracts\Mail\Mailer contract defines the methods needed for sending e-mail.
https://laravel.com/docs/5.4/contracts#introduction
When Laravel is running it can check if a class implements a special interface:
if ($cls instanceof IInterface) {
$cls->interfaceFunction();
}
Since Laravel is able to work with queues it will check if the event should be queued or not by checking for an exiting interface.
To inform Laravel that a given event should be broadcast, implement the Illuminate\Contracts\Broadcasting\ShouldBroadcast interface on the event class.
https://laravel.com/docs/5.4/broadcasting#defining-broadcast-events
Repository:
I didn't found that much about this:
Our repository should not have so much knowledge regarding who is providing them data or how they are providing it. https://laravel.com/docs/5.4/contracts#loose-coupling
But I found some other information on a webpage:
a Repository will connect Factories with Gateways
https://code.tutsplus.com/tutorials/the-repository-design-pattern--net-35804
The link will give you more information about the the details.
Hope I could help you :)
First of all, using Repository and Interface in larger application is not only beneficiary in Laravel but in all technology for coding standard as well as for separation of concern.
According to Microsoft (I found best explanation here)
Why to use Repository:
Use a repository to separate the logic that retrieves the data and
maps it to the entity model from the business logic that acts on the
model. The business logic should be agnostic to the type of data that
comprises the data source layer. The repository mediates between the
data source layer and the business layers of the application. It
queries the data source for the data, maps the data from the data
source to a business entity, and persists changes in the business
entity to the data source.
A repository separates the business logic
from the interactions with the underlying data source or Web service.
The separation between the data and business tiers has three benefits:
It centralizes the data logic or Web service access logic. It provides
a substitution point for the unit tests. It provides a flexible
architecture that can be adapted as the overall design of the
application evolves. There are two ways that the repository can query
business entities. It can submit a query object to the client's
business logic or it can use methods that specify the business
criteria. In the latter case, the repository forms the query on the
client's behalf. The repository returns a matching set of entities
that satisfy the query.
For Interface, you have a lot of answers above, hope you have understand.
First of all, repositories and interfaces are not specific to Laravel but common coding standards in most of the languages.
Below Laracasts videos will be useful to understand the basics if you don't mind spend few dollars.
https://laracasts.com/lessons/repositories-and-inheritance
https://laracasts.com/series/object-oriented-bootcamp-in-php
let's take two example.
Example 1 (Repository pattern)
Interface
interface FooInterface {
public function all();
}
Model(Using it in a loose term)
class FooModel implements FooInterface {
public function all()
{
return DB::('sometable')->get();
}
}
Service Provider
class FooServiceProvider extends ServiceProvider {
public function register()
{
$this->app->bind(
'Foo\FooInterface',
'Foo\FooModel'
);
}
config/app.php
'providers' => array(
// --
'Foo\FooServiceProvider'
),
And at last the controller:
use Foo\FooInterface as Model;
public function __construct(Model $model)
{
$this->model = $model;
}
Now i can access the methods as $this->model->all(). that's great! Let's look at the 2nd example.
Example 2:
controller:
public function __construct()
{
$this->model = new \Foo\FooModel();
}
now i can also access the same method as $this->model->all();
Question
As i read, the advantage of using repository pattern is, in future, easily configurable/changeable interface system. e.g.
if i change the db system, i just need to change, the bindings in the service provider.
But, i can also just easily change the model instaintiation in the controller construct to achieve the same. like, changing $this->model = new \Foo\FooModel() to $this->model = new \Bar\BarModel(); where BarModel will hold the methods of different system.
What exactly i am missing here in the aspect of advantages of a repository pattern.? or in this particular case, repository pattern doesn't give much advantage yet, in some other case it may? if that's a yes, what can be that situation?
p.s. the term model is used just for the convenince.
Being prepared for switching databases is one of the most annoying arguments one can give for the repository pattern. This one is usually followed by the ignorant "I'll never switch databases." If i had an upvote for each time I had this conversation...
Now imagine you want to add a back-end like caching or some search engine to optimize searches. This would fit really well in a repository pattern.
The general key benefit of the repository pattern is that all changes related to the back-end are more manageable than otherwise.
To demonstrate why you wouldn't want this stuff in a model; If you want to migrate a model's attributes the model needs to be fetched from the back-end differently. You might need two models in some cases. When you have this all in one model you need to apply a lot of hackery to make this even work. If the repository manages these changes the models stay clear and your code becomes manageable again.
It really comes down to how you have set-up your code. In your particular case, there wouldn't appear to be much benefit.
But what if your code required you to have multiple instantiations of your model across many different controllers? For example, maybe you have a model to your user repository. There may be many controllers which need to get information about the user.
Then it would be a hassle to go through all your controllers changing all the references (i.e. your example 2). Much better to just change the repository once (i.e. your example 1).
There is never one size fits all in coding. The best you can do is code for what you need now, with an awareness of any potential solutions which may aid flexibility in the future. My view is that the repository pattern is one of those solutions which aids flexibility. You may never need to change the model, or move to a different database, but the effort in coding with it now is minimal compared to the hassle you will have if you ever did want to change your db.
tl;dr
What strategies exist to overcome parameter type invariance for specializations, in a language (PHP) without support for generics?
Note: I wish I could say my understanding of type theory/safety/variance/etc., was more complete; I'm no CS major.
Situation
You've got an abstract class, Consumer, that you'd like to extend. Consumer declares an abstract method consume(Argument $argument) which needs a definition. Shouldn't be a problem.
Problem
Your specialized Consumer, called SpecializedConsumer has no logical business working with every type of Argument. Instead, it should accept a SpecializedArgument (and subclasses thereof). Our method signature changes to consume(SpecializedArgument $argument).
abstract class Argument { }
class SpecializedArgument extends Argument { }
abstract class Consumer {
abstract public function consume(Argument $argument);
}
class SpecializedConsumer extends Consumer {
public function consume(SpecializedArgument $argument) {
// i dun goofed.
}
}
We're breaking Liskov substitution principle, and causing type safety problems. Poop.
Question
Ok, so this isn't going to work. However, given this situation, what patterns or strategies exist to overcome the type safety problem, and the violation of LSP, yet still maintain the type relationship of SpecializedConsumer to Consumer?
I suppose it's perfectly acceptable that an answer can be distilled down to "ya dun goofed, back to the drawing board".
Considerations, Details, & Errata
Alright, an immediate solution presents itself as "don't define the consume() method in Consumer". Ok, that makes sense, because method declaration is only as good as the signature. Semantically though the absence of consume(), even with a unknown parameter list, hurts my brain a bit. Perhaps there is a better way.
From what I'm reading, few languages support parameter type covariance; PHP is one of them, and is the implementation language here. Further complicating things, I've seen creative "solutions" involving generics; another feature not supported in PHP.
From Wiki's Variance (computer science) - Need for covariant argument types?:
This creates problems in some situations, where argument types should be covariant to model real-life requirements. Suppose you have a class representing a person. A person can see the doctor, so this class might have a method virtual void Person::see(Doctor d). Now suppose you want to make a subclass of the Person class, Child. That is, a Child is a Person. One might then like to make a subclass of Doctor, Pediatrician. If children only visit pediatricians, we would like to enforce that in the type system. However, a naive implementation fails: because a Child is a Person, Child::see(d) must take any Doctor, not just a Pediatrician.
The article goes on to say:
In this case, the visitor pattern could be used to enforce this relationship. Another way to solve the problems, in C++, is using generic programming.
Again, generics can be used creatively to solve the problem. I'm exploring the visitor pattern, as I have a half-baked implementation of it anyway, however most implementations as described in articles leverage method overloading, yet another unsupported feature in PHP.
<too-much-information>
Implementation
Due to recent discussion, I'll expand on the specific implementation details I've neglected to include (as in, I'll probably include way too much).
For brevity, I've excluded method bodies for those which are (should be) abundantly clear in their purpose. I've tried to keep this brief, but I tend to get wordy. I didn't want to dump a wall of code, so explanations follow/precede code blocks. If you have edit privileges, and want to clean this up, please do. Also, code blocks aren't copy-pasta from a project. If something doesn't make sense, it might not; yell at me for clarification.
With respect to the original question, hereafter the Rule class is the Consumer and the Adapter class is the Argument.
The tree-related classes are comprised as follows:
abstract class Rule {
abstract public function evaluate(Adapter $adapter);
abstract public function getAdapter(Wrapper $wrapper);
}
abstract class Node {
protected $rules = [];
protected $command;
public function __construct(array $rules, $command) {
$this->addEachRule($rules);
}
public function addRule(Rule $rule) { }
public function addEachRule(array $rules) { }
public function setCommand(Command $command) { }
public function evaluateEachRule(Wrapper $wrapper) {
// see below
}
abstract public function evaluate(Wrapper $wrapper);
}
class InnerNode extends Node {
protected $nodes = [];
public function __construct(array $rules, $command, array $nodes) {
parent::__construct($rules, $command);
$this->addEachNode($nodes);
}
public function addNode(Node $node) { }
public function addEachNode(array $nodes) { }
public function evaluateEachNode(Wrapper $wrapper) {
// see below
}
public function evaluate(Wrapper $wrapper) {
// see below
}
}
class OuterNode extends Node {
public function evaluate(Wrapper $wrapper) {
// see below
}
}
So each InnerNode contains Rule and Node objects, and each OuterNode only Rule objects. Node::evaluate() evaluates each Rule (Node::evaluateEachRule()) to a boolean true. If each Rule passes, the Node has passed and it's Command is added to the Wrapper, and will descend to children for evaluation (OuterNode::evaluateEachNode()), or simply return true, for InnerNode and OuterNode objects respectively.
As for Wrapper; the Wrapper object proxies a Request object, and has a collection of Adapter objects.
The Request object is a representation of the HTTP request.
The Adapter object is a specialized interface (and maintains specific state) for specific use with specific Rule objects. (this is where the LSP problems come in)
The Command object is an action (a neatly packaged callback, really) which is added to the Wrapper object, once all is said and done, the array of Command objects will be fired in sequence, passing the Request (among other things) in.
class Request {
// all teh codez for HTTP stuffs
}
class Wrapper {
protected $request;
protected $commands = [];
protected $adapters = [];
public function __construct(Request $request) {
$this->request = $request;
}
public function addCommand(Command $command) { }
public function getEachCommand() { }
public function adapt(Rule $rule) {
$type = get_class($rule);
return isset($this->adapters[$type])
? $this->adapters[$type]
: $this->adapters[$type] = $rule->getAdapter($this);
}
public function commit(){
foreach($this->adapters as $adapter) {
$adapter->commit($this->request);
}
}
}
abstract class Adapter {
protected $wrapper;
public function __construct(Wrapper $wrapper) {
$this->wrapper = $wrapper;
}
abstract public function commit(Request $request);
}
So a given user-land Rule accepts the expected user-land Adapter. If the Adapter needs information about the request, it's routed through Wrapper, in order to preserve the integrity of the original Request.
As the Wrapper aggregates Adapter objects, it will pass existing instances to subsequent Rule objects, so that the state of an Adapter is preserved from one Rule to the next. Once an entire tree has passed, Wrapper::commit() is called, and each of the aggregated Adapter objects will apply it's state as necessary against the original Request.
We are then left with an array of Command objects, and a modified Request.
What the hell is the point?
Well, I didn't want to recreate the prototypical "routing table" common in many PHP frameworks/applications, so instead I went with a "routing tree". By allowing arbitrary rules, you can quickly create and append an AuthRule (for example) to a Node, and no longer is that whole branch accessible without passing the AuthRule. In theory (in my head) it's like a magical unicorn, preventing code duplication, and enforcing zone/module organization. In practice, I'm confused and scared.
Why I left this wall of nonsense?
Well, this is the implementation for which I need to fix the LSP problem. Each Rule corresponds to an Adapter, and that ain't good. I want to preserve the relationship between each Rule, as to ensure type safety when constructing the tree, etc., however I can't declare the key method (evaluate()) in the abstract Rule, as the signature changes for subtypes.
On another note, I'm working on sorting out the Adapter creation/management scheme; whether it is the responsibility of the Rule to create it, etc.
</too-much-information>
To properly answer this question, we must really take a step back and look at the problem you're trying to solve in a more general manner (and your question was already pretty general).
The Real Problem
The real problem is that you're trying to use inheritance to solve a problem of business logic. That's never going to work because of LSP violations and -more importantly- tight coupling your business logic to the application's structure.
So inheritance is out as a method to solve this problem (for the above, and the reasons you stated in the question). Fortunately, there are a number of compositional patterns that we can use.
Now, considering how generic your question is, it's going to be very hard to identify a solid solution to your problem. So let's go over a few patterns and see how they can solve this problem.
Strategy
The Strategy Pattern is the first that came to my mind when I first read the question. Basically, it separates the implementation details from the execution details. It allows for a number of different "strategies" to exist, and the caller would determine which to load for the particular problem.
The downside here is that the caller must know about the strategies in order to pick the correct one. But it also allows for a cleaner distinction between the different strategies, so it's a decent choice...
Command
The Command Pattern would also decouple the implementation just like Strategy would. The main difference is that in Strategy, the caller is the one that chooses the consumer. In Command, it's someone else (a factory or dispatcher perhaps)...
Each "Specialized Consumer" would implement only the logic for a specific type of problem. Then someone else would make the appropriate choice.
Chain Of Responsibility
The next pattern that may be applicable is the Chain of Responsibility Pattern. This is similar to the strategy pattern discussed above, except that instead of the consumer deciding which is called, each one of the strategies is called in sequence until one handles the request. So, in your example, you would take the more generic argument, but check if it's the specific one. If it is, handle the request. Otherwise, let the next one give it a try...
Bridge
A Bridge Pattern may be appropriate here as well. This is in some sense similar to the Strategy pattern, but it's different in that a bridge implementation would pick the strategy at construction time, instead of at run time. So then you would build a different "consumer" for each implementation, with the details composed inside as dependencies.
Visitor Pattern
You mentioned the Visitor Pattern in your question, so I'd figure I'd mention it here. I'm not really sure it's appropriate in this context, because a visitor is really similar to a strategy pattern that's designed to traverse a structure. If you don't have a data structure to traverse, then the visitor pattern will be distilled to look fairly similar to a strategy pattern. I say fairly, because the direction of control is different, but the end relationship is pretty much the same.
Other Patterns
In the end, it really depends on the concrete problem that you're trying to solve. If you're trying to handle HTTP requests, where each "Consumer" handles a different request type (XML vs HTML vs JSON etc), the best choice will likely be very different than if you're trying to handle finding the geometric area of a polygon. Sure, you could use the same pattern for both, but they are not really the same problem.
With that said, the problem could also be solved with a Mediator Pattern (in the case where multiple "Consumers" need a chance to process data), a State Pattern (in the case where the "Consumer" will depend on past consumed data) or even an Adapter Pattern (in the case where you're abstracting a different sub-system in the specialized consumer)...
In short, it's a difficult problem to answer, because there are so many solutions that it's hard to say which is correct...
The only one known to me is DIY strategy: accept simple Argument in function definition and immediately check if it is specialized enough:
class SpecializedConsumer extends Consumer {
public function consume(Argument $argument) {
if(!($argument instanceof SpecializedArgument)) {
throw new InvalidArgumentException('Argument was not specialized.');
}
// move on
}
}
I'm building a web application that needs to be able to write data to either a mysql db or an xml file, depending on the online status of the application.
In my model, I have a super class (Dao is data access object)...
abstract class Dao {
static function getInstance($online_status) {
if $online_status = 'online' {
return new DaoMySQL;
} else {
return new DaoXML;
}
}
abstract function dao_select();
abstract function dao_insert();
abstract function dao_update();
abstract function dao_delete();
}
Now, here is the part I'm confused about. I have a domain model/entity class that selects the appropriate Dao using:
$this->dao = Dao::getInstance($online_status);
So, now I have the correct data access object selected. But, the problem is I still two implementations of dao_select() and the other functions. Now, the main implementations are in the respective classes DaoMySQL and DaoXML, but dao_select() in each of those classes require different things. i.e. the DaoMySQL version needs two parameters, $table and $where_statement. DaoXML (which I haven't implemented) will need the element name, and maybe another argument, I don't know.
So, in my domain model class, after calling
$this->dao = Dao::getInstance($online_status);
is this where I need to include two separate local implementations (pertaining to the domain model/entity class only) of dao_select(), or this wrong? It just seems like I'm taking the elegance out of the process by doing something like this:
class EntityModel {
$this->dao = Dao::getInstance($online_status);
if($this->dao->type = 'mysql') {
$result = $this->dao->dao_select($table, $where);
} else {
$result = $this->dao->dao_select($xml_params);
}
}
I feel like I'm taking the simplicity out of the system... Does this approach make sense, or is there a better one?
You are doing it wrong.
Few notes to begin with:
in OOP the extends statement signifies is a relationship. Which means that, while class Duck extends Bird is all fine, writing class User extends Table is NOT.
in MVC the Model is not a class or an instance of a class. Instead it is a layer of application, mostly made of two types of elements:
domain objects: containing domain business rules and logic
data access structures: usually datamapper dealing with storage and retrieval of information
I would argue, that the third significant part of Model layer are services. But there are options on whether it is part-of or above Model.
Currently what you are trying to do is forcing a ActiveRecord (which is fine for small things, but as project grows, it becomes a burden on architecture .. which is what you are face with now) patterns to work with dynamic data sources. And to do so you are resorting to procedural calls.
Anyway, the point is that you should inject your DAO instance into your Domain Objects (what you calls "models"). And you should leave the creating of your DAO to a separate factory instance, which would be responsible for initializing them and providing them with data source (instance of PDO or file path). This way you can, not only separate the responsibilities, but also swap the storage destination "on fly".
To learn more you should investigate what is dependency injection. Here are few video that might help:
Don't Look For Things!
Global State and Singletons