I use Eloquent to implement my models. It has a lot of methods that makes working with models much easier. However, in some cases I have to implement functionality that is accessing the database, but doesn't return an Eloquent model.
For example, imagine a common User model. Eloquent helps me a lot in CRUD operations for the User model, but what if I have to implement a method that returns some statistics about users (ex. how many total users, how many active users, etc.).
Where should I implement this functionality? In the Eloquent model, as a public static function, (e.g. User::getStats()), or should have a different class for this.
It seems a little bit unnatural to have these methods on a Eloquent model, because they are not Eloquent related.
It's really up to you, but it's a good idea to decide on a convention and stick to it.
My rule of thumb is this:
if it's related to a single item that is normally represented by an Eloquent model, do it as a public static method on that model. For example, in my current project, I have a Contract model. I needed a method that would generate a contract ID string (for compatibility with legacy systems) for new contracts. This is related to a single item (a contract) but for technical reasons needed to be generated separately from the Eloquent model (ie. it was based on a separate database with a different connection). So I created a public static method: public static function generateContractIdentifier($id, $salesRep). If you're using a repository for access to your database, you could put it there.
If it's more general (ie. not tied to an instance of the Eloquent model), I put it into a separate library. For example, in the same application I have a queue for processing items for printing (an industrial process). The application has a dashboard and I needed to show the current queue status for management. For this I created a ProcessStatus library and implemented a method: public function getStatus() which runs a query and provides the results as an array for display in a view. This is not related to any particular item in the queue, so I put it in a separate library.
As always, it depends on your project and what role the user plays in it.
But basically, no, I don't think the logic for building reports belongs on the user model. While it may be related to the user, regarding the SOLID-principle the User class should only have one responsibility, which in this case is to handle the User entity.
This contains getting and setting properties on an instance, in a simple project it's probably also fine to define some scopes on the model, e.g. to select only active users, like User::getActive();
But as your project grows, you should consider using more specific classes.
For instance, you could abstract the Eloquent functionality into a User-Repository. So now you have a handler for operations on the entitiy itself, like
$userRepo->getAll();
$userRepo->getActive();
$userRepo->getInactive();
and a handler for a User instance:
$user->getName();
$user->setStatus();
Creating reports and statistics is yet a completely different topic. So you could have something like a UserReportBuilder oder UserStatisticsService:
$userStats->getMostActive();
$userStats->getRegistrationsPerDay();
A simple example:
// UserRepository:
class UserRepository
{
protected $model = $model;
public function __construct($model)
{
// you pass in an instance of the actual Eloquent model
// so you have the whole power of eloquent in here
$this->model = $model;
}
public function getActive()
{
// this returns a collection of models
return $this->model->where('status', 'active')->get();
}
}
$userRepo = new UserRepo(new User);
And that's pretty much it. You can still work with Eloquent, but you have separated the functionality in parts with a clewar responsibility. So your UserStats class would only be resposible for building user statistics:
class UserStats
{
// You could pass in the Repository through the constructor
// or just use the Eloquent model directly
public function getRegistrationsPerDay()
{
return User::groupBy('day')->get(
[
DB::raw('DATE(created_at) as day'),
DB::raw('count(*) as registration_count')
]
);
}
}
The User instance or the UserStats-builder do not need to know how to fetch all users, and the User instance or the UserRepository do not need to know how to calculate registrations per day, so it makes sense to split that functionality into separate, independent parts that do exactly one thing.
I think you get the idea and I hope it makes sense. Maybe you should make yourself more familiar with the SOLID-principles and try to keep them in mind when you get stuck on problems like that.
Related
One of the things that I don't fully understand about the MVC model is loading multiple data tables from the model.
Let's say we have a Model called user.
public class User{
protected $username;
protected $email;
protected $id;
public function setUsername($name, $id) {
$db->set->name($name)
$db->set->id($id)
}
public function getUsernameById($id) {
return $db->get->name($id);
}
As far as I've learned, the model is equivalent to a single row in the database (called user).
Now I have written two functions. The first is for setting the username and the other is for retrieving the username through an ID.
Suppose you want to retrieve all users. Let's say through the getAllUsers() method. This method does not fit on the Model as it is not a single object.
Now I understand that for example, you can call this method (function) in your controller itself. But where can you define this method (function)? Since you don't do this on the model.
I also like to hear it if I am wrong :)
Basically your definition is correct:
Model objects hold data and define the logic for manipulating that
data. For example, a Student object in the Basic sample application is
a model object. It holds data describing facts about the object like
the first and last name of the student and has methods that can access
and change this data (getters & setters). Model objects are not
directly displayed. They often are reusable, distributed, persistent
and portable to a variety of platforms.
(from: http://best-practice-software-engineering.ifs.tuwien.ac.at/patterns/mvc.html)
So where to put the getAllUsers() function in case you do not just put it into the model?
Well first of all when we are talking about the term "model" we do not always mean model. In MVC the model (M) contains all of the business logic. This means it contains the domain model which is the model you are meaning in this question. But it also contains the service layer.
The service layer usually consists of various classes which provide functionalities like for instance getAllUsers() or getAllUsersWhichOwnAGreenTruck().
Here is a great graphic demonstrating this:
(Source: Domain Model and Service Layer patterns in P of EAA)
In modern applications e.g. built with the Laravel PHP Framework you usually generate the database as well as the model and the database mapping/communication automatically (Example Eloquent ORM: https://laravel.com/docs/5.8/eloquent). But you start implementing complex application logic within the service layer.
I'm working on a site that has extended Models, eg.
class Asset extends Model {
public function project() {
return $this->belongsTo(Project::class);
}
}
class Video extends Asset {
}
Do I need to establish the same belongsTo Eloquent relationship with the extended class, or by virtue of the parent class having it, will Laravel do the rest?
Additionally, is there any documentation anywhere that goes into detail about how to structure such relationships (ie. in terms of Controllers)? I can't find anything on the (usually excellent) Laracasts website.
You don't need to instance the extended method twice, unless you want to override it with a different behaviour.
I personally use a lot of inheritance in my applications, and it works just as you would expect, every relation keeps working and querying using the parent default values or the specific protected variables you declare.
For example, if you declare a protected $table = 'foo', the child will also take that variable to perform its query, or you could override it on the child to query a different table from the parent.
In terms of documentation, the reason you are not finding much information I think it's because this is more a PHP and OOP issue than a framework specific one.
If you want to declare polymorphic relations, which are a really common way to implement multiple inheritance in your SQL, Laravel has your back, with specific Eloquent relations and migration commands, like $table->morphs('asset');.
Hope this helps you.
I have a question regarding how to populate objects from the Data base, it's more about the architecture than populating it self.
Let's say I have a table called receipts which has: receipt_id, user_id, issue_date, business_id, product_id abd type.
Now I want to create a Receipt class which will be populated from that table, I would do something like this:
class Receipt {
public function __construct($receipt_id = null) {
if(!is_null($receipt_id))
$this->populate($receipt_id);
}
public function populate($receipt_id){
//Get data from data base
}
public function save(){
//Saves the current receipt into the data base.
}
public static function getReceiptsFromUser($user_id){
}
}
My question is about getReceiptsFromUser, should it be static?. It makes sense for the User class to have a method called getReceipts which would return an array of receipts objects calling this static method. I think it should be static because it doesn't make any sense to create an empty receipt to generate the user's receipts. Is there any flaws in this architecture or better aproaches?
Yes, it should be static. As you already mentioned yourself, you don't need a Receipt object in order to retrieve other Receipts.
From the PHP documentation:
Declaring class properties or methods as static makes them accessible without needing an instantiation of the class.
By the way, what you're doing here is called the Active Record pattern. If you're going to use this approach, it's a good idea to create a base class (you can call it Record or something similar) that defines the shared methods (such as save, find, etc.) and let your models extend them (class Receipt extends Record, class User extends Record, etc.). This way, you don't need to repeat this code in all of your models.
A nice (PHP) example for this approach is given by the PHP ActiveRecord project. A look into their documentation should give you some ideas about how it works.
Another approach is the Data Mapper pattern. The benefit is that your models don't know anything about how they're being saved, so you have great flexibility in how you want to persist your data, instead of being tied to the limitations of ActiveRecord.
I have a practical doubt ,
I am using OO php , and i am new to it. I am using zend framework to be clear.
I am writing controllers(class) and actions(methods) with in it say PatientMapper.php which has all single mysql table related actions and Patient.php which has all setter and getter functions.
I get a doubt that when should i write a new controller.
Should i write a controller for all the actions on a single mysql table .
or a single controller for all actions related to a module.
As opposed to the previous answers, I would say that your controller should not be related to your DB.
Controllers don't handle business logic, your models do.
Besides that, you can write a controller for each entities.
User is an entity, which can be wrapped in a controller, even if it depends on several tables.
If your controller is getting bigger and bigger, you can switch to module (Zend Framework terminology) and create an User module, which has an Account controller, etc...
I think you should write controller for single mysql table, because if your application will grow up you can end with few thousand line controllers.
A controller groups actions that conceptually belong together somehow. The controller might use one specific model class (which is not necessarily a database accessing class) only, but it may also use many of them.
Important is, that the controller should not contain the logic of the model classes. The sole responsibility of a controller is to receive and delegate the input for a specific interaction you allow users to do with your application to the model. And in webbased MVC, it is usually also responsible to update the View according to the result of the operation.
The most important distinction one has to understand in MVC is that M should be able to live on it's own while V and C belong together. V and C form just the UI to your application and they should not do anything beside that. Basically, your M is your application, while your VC just sits on top of it. Should really be M|VC imho
With that said, if you feel your application can get away with a single Controller, then use a single appController. But once you find yourself adding actions that could conceptually fit into a separate controller, then add a new controller. If your application is a simple CRUD app, then have one controller per database table. If it does something else, then give it something that fits the scenario.
IMHO I would suggest a action for each mysql table related action
for maintainability. These would be ligtweight actions that just call your model for that table
#gordon - Yeah my application is a CRUD application. SO i need to create a model for each table..This is the most accepted way rite. And yeah i am bit clear abt controllers..
i have a page where in i need data from 3 tables. So i will need to call all 3 model methods to get the data rite...
I have a small doubt...i use code like this at the beginning of the beginning of class .and this was written by other developer....i am using it.. i am finding it difficult to use joins ,bcos this is locking the DB..any help on how to use joins over here
protected $_dbTable;
public function setDbTable($dbTable)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
public function getDbTable()
{
if (null === $this->_dbTable) {
$this->setDbTable('Application_Model_DbTable_Appointment');
}
return $this->_dbTable;
}
this is the select part..not able to use join
$select = $this->getDbTable()->select()
->from("patient_appointment",array('pa_pd_patient_id',
'DATE_FORMAT(pa_datetime,"%h:%i %p") as pa_time','pa_datetime','pa_um_id'
,'pa_created_date','pa_mode','pa_type'))
->where('date(pa_datetime) = ?', $date)
->where('pa_pd_patient_id = ?', $patientId)
->order('time(pa_datetime)');
$resultSet = $this->getDbTable()->fetchAll($select);
Now I have an model User which represents an user in the application. And I use an UserRepository with methods like getById($id) and getAll().
An user can post reviews. If I want to show the 5 or 10 or maybe 20 last reviews of an user it's most logical to ask the user I think. So I would have a method $user->getLastReviews(5).
But what's the best way to implement this? Give each user an instance of the ReviewRepository (with a method $reviewRepository->getByUser(User $user) for example)? Or are there better ways?
I think it's fine to have models contain and use instances of other models, so your way is fine, to have User model contain an instance of the Review model, and let that Review model handle the logic of getting said reviews.
Also, you could add a method to the UserRepository to handle it like so:
class UserRepository extends Model{
$review = new reviewRepository();
function getReviews($numOfReviews){
return $this->review->getReviews($user_id);
}
Another option would be to create a repository method where you passed in both variables. Something like $reviewRepository->getLastReviewsByUser(5, $user).
Usually this is a job for the ORM. Almost every framework uses one implementation (ie. Doctrine for PHP/Symfony or Hibernate for Java) but naturally you can implement your own ORM (ORM are often implemented using introspection).
Once you have an ORM library you define relations between Models in a "setup phase" (in your case you'll have "user has many reviews"). Then you'll use the ORM methods which knows how to deal with those ones (often relations are mutual ie. "review belongs to user"). The concept is that this setup phase will discharge you from dealing with issues like the one you pointed.
My suggestion is to use one of the already existing ORM implementations which already supplies facilities for getter and setter methods of related Models. In the other case, you have to write specialized getters and setters by yourself for every Model.