I'm in the process of implementing an ultra-light MVC framework in PHP. It seems to be a common opinion that the loading of data from a database, file etc. should be independent of the Model, and I agree. What I'm unsure of is the best way to link this "data layer" into MVC.
//controller
public function update()
{
$model = $this->loadModel('foo');
$data = $this->loadDataStore('foo', $model);
$data->loadBar(9); //loads data and populates Model
$model->setBar('bar');
$data->save(); //reads data from Model and saves
}
Seems a bit verbose and requires the model to know that a datastore exists.
//controller
public function update()
{
$model = $this->loadModel('foo');
$data = $this->loadDataStore('foo');
$model->setDataStore($data);
$model->getDataStore->loadBar(9); //loads data and populates Model
$model->setBar('bar');
$model->getDataStore->save(); //reads data from Model and saves
}
What happens if we want to save a Model extending a database datastore to a flatfile datastore?
//controller
public function update()
{
$model = $this->loadHybrid('foo'); //get_class == Datastore_Database
$model->loadBar(9); //loads data and populates
$model->setBar('bar');
$model->save(); //saves
}
This allows for Model portability, but it seems wrong to extend like this. Further, the datastore cannot make use of any of the Model's methods.
//controller extends model
public function update()
{
$model = $this->loadHybrid('foo'); //get_class == Model
$model->loadBar(9); //loads data and populates
$model->setBar('bar');
$model->save(); //saves
}
//model
public function __construct($dao)
{
$this->dao = $dao;
}
//model
public function setBar($bar)
{
//a bunch of business logic goes here
$this->dao->setBar($bar);
}
//controller
public function update()
{
$model = $this->loadModel('foo');
$model->setBar('baz');
$model->save();
}
Any input on the "best" option - or alternative - is most appreciated.
I don't think it belongs in controller - that's really part of the view, which can come and go. The business logic and persistence shouldn't depend on view or controller.
A separate service layer gives you the chance to expose that logic as distributed components to non-browser based clients (e.g., mobile views, batch processing, etc.)
I wouldn't have the model extend the data store in an object-oriented sense, because inheritance is an IS-A relationship. As you've noted, the model is not a relational database; that's merely one choice among many for persisting information.
I think it's more composition. The persistence layer could be a separate DAO that persists model objects without requiring that they be aware of the fact that they are longer-lived. OR you have mixin behavior where a model advertises the fact that it has CRUD operations, but it merely passes off requests to the implementation they're given.
The usual Spring idiom would have a service layer that's separate from the controller. It knows about use cases and units of work. It uses model and persistence objects to accomplish the goals of the use case.
MVC implies three actors. I would say there are more layers than that in a typical application - persistence and service need to be added.
UPDATE:
If the model object does not import anything from the persistence package, then it has no knowledge of it whatsoever. Here's a simple example, using a simple model class Person and its DAO interface, each in its own package:
package persistence.model;
public class Person
{
private String name;
public Person(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
Note that the DAO interface imports the model package, but not the other way around:
package persistence.persistence;
import persistence.model.Person;
import java.util.List;
public interface PersonDao
{
Person find(Long id);
List<Person> find();
void saveOrUpdate(Person p);
}
I think you're trying to build a framework that is too tightly tied to names and concepts. I've found the names and concepts in the Model-View-Controller idiom are not rigidly defined. I've also found that code working in that paradigm needs to be able to bend the rules sometimes.
@duffymo has a very important point about the Model component: a Model isn't a Datastore, it just has a Datastore. If that is not clear, another way of thinking about that it is to look at what an instance of each is supposed to be. An instance of a "datastore" object represents a resource which mediates access to an arbitrary amount of data. Usually it is a database connection. An instance of a "model" object is usually a discrete identity with multiple pieces of data. Usually it represents a database row in a table, but it could be a line in a text file, or one file in a file-store.
Applying the same quesion to the Controller and the View and you can see that the Model part is quite a different animal. Whilst only one Controller object and one View object will normally be in existence at one time, it is quite likely that several different Model objects of different types are in existence at one time.
Unfortunately, I also know that a lot of MVC "frameworks" define a "Model" as an API layer behind which you do SQL statements. In those frameworks, a "Model" is a static class or a single instance and does nothing more than provide near-useless namespace partitioning. And a lot of programmers think this is supposed to make sense and struggle with it. This is why I recommend you don't use the names and concepts of the MVC idiom.
My preferred way to write structured PHP only nods to the MVC paradigm. It has dispatcher and controller logic in the top, and HTML markup in the bottom. This works well because Controllers and Views are often tightly coupled and putting them in the same file is very PHP-ish. Yes, it does take discipline to not do controller-ish actions in view-ish code, but I'd rather have to do that myself than have the framework force me to. These pages don't stand alone, however, they include a lot of common logic and can import things like a lightweight dispatcher system, or even call whole swathes of common controller logic.
But the main thing the common logic provides is a data-abstraction layer -- basically the Model layer as described at the top of my answer. The data-abstraction layer I wrote is basically three things: a database handler, an "object" object and a "collection" object.
The database handler is the DataStore for all objects that live in the datbasee. It's main purpose is so that activities to the database that aren't making a query and getting a response are all in one place. Most database calls are in the object core. The database handler doesn't know anything about the object core: it just takes SQL and returns resultsets.
The other two objects are designed to be sub-classed and will not work if instantiated themselves. Using inheritance and a special extended declaration (done by a static class method), they know how to turn database data into an object. An instance of an inherited "object" object represents one row of the declared class. It is given an ID upon instantiation, and will query the database when it needs to to retreive its one row of data. It also keeps track of changes and can do a one-row UPDATE when told to. The API it presents is completely devoid of SQL: the object has fields you ->get() and ->set(), and you can ->save() when you're done.
The "collection" object knows how to get a filtered list of rows of a particular object and knows how to turn that into individual objects as appropriate, each of which function just like they were individually instantiated. (The collection object also supports being an object itself, but I rarely use this feature as it currently has some implementation limitations. And it's a concept I've found most programmers have trouble grasping.)
Most objects need only the core inheritance code and the declarations for their specific table.
The end effect is that the controller code gets to do obvious things like this:
$ticket = Ticket::Create($ticket_id);
$ticket->set('queue', $new_queue);
$ticket->set('queue_changed', date('Y-m-d H:i:s'));
$ticket->save();
... instead of something opaque like:
TicketModel::ChangeTicketQueue($ticket_id, $new_queue);
This approach also lets you code things in the Ticket object that might update other fields or other objects when the queue
field changes, and it will always occur when you change that field, instead of having to remember to do it in every function that can change a ticket's queue. It also means you can add other methods to a Ticket: it would probably make sense for $ticket->get_queue()
to do return TicketQueue::Create($this->get('queue'));
for you. Object oriented programming at it's finest! :-)
So, to finally answer your question, I would recommend none of the approaches in your question. Your model objects should present a unified API to the controller code and it is up to them as to how they store their data. If it is in a database, then they need to organise their own calls to do that. But a model isn't a datastore: however, it may well have one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With