I'm trying to get a more concrete understanding of MVC and keeping the controller layer as thin as possible.
One thing I keep asking myself is "Where should I call modelname->save()?"
Looking at the Laravel documentation, they set data to the model and call save in the controller which doesn't seem right...
<?php
namespace App\Http\Controllers;
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class FlightController extends Controller
{
public function store(Request $request)
{
// Validate the request...
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
}
This is quite a simple example and might be why they do it all in the controller.
From my understanding and everything I've been reading, all business logic should sit inside the model, the controller is responsible for "traffic control" between the view and the model.
So would I be calling save inside the model itself? or should I be using a service layer?
Here is my current problem with example data.
I am updating the status of a model. The row already exists in the DB. I use PATCH /route/ to get to the controller method. From there I get the model.
class TimecardController extends Controller {
...
public function markAsPass(Request $request, $id) {
$test = Test::findOrFail($id);
//I don't think this is the corect way
//$test->status = "passed";
//$test->markedBy = "Teacher123";
//$test->save();
$test->passed();
...
return redirect($redirect_url);
}
}
class Test extends Model {
...
public function passed() {
$this->status = "passed";
//would I call save here?
//$this->save();
}
}
Do I take an approach like above? Or do I create a service layer where I would use the model instance to call the model functions and then call save on the model?
//in service class
public function makeTestAsPassed($test){
$test->passed();
$test->save();
}
Please let me know if any claification is needed.
You’re right in that business logic belongs in models. If you take a “resourceful” approach to your applications (in that you create controllers around entities) then you’ll find that your controller actions seldom call more than one model method.
Instead of calling save(), you can call create() and update() methods on your model. In your store() controller action, you can create a new entity with one line like this:
public function store(CreateRequest $request)
{
$model = Model::create($request->all());
}
And update an existing model in an update() action like this:
public function update(UpdateRequest $request, Model $model)
{
$model->update($request->all());
}
When it comes to business logic, you can call other methods on your models, too. To use resourceful controllers, you don’t have to have a model that relates to a database table.
Take shipping an order. Most people would be tempted to put a ship() method in an OrderController, but what happens when you ship an order? What entity could shipping an order result in? Well, you’d be creating a shipment, so that could instead be a store() method on an OrderShipmentController. This store() method could then just call a ship() method on your Order model:
class OrderShipmentController extends Controller
{
public function store(ShipOrderRequest $request, Order $order)
{
$order->ship();
}
}
So as you can see, with resourceful controllers and route–model binding, you can have “skinny controllers” with your application’s business logic living in your models.
MVC is designed for ease of maintenance.
The approach you don't recognize as "good" is the proper approach. All data handling related to business logic goes in the controller. Otherwise, a different coder will be confused as he/she would not find the data manipulation logic in the controller code.
Your thin controller goal defeats MVC.
Also note the model code is purposed to be thin as it is a place to define the database schema as mirror image to the database tables.
MVC is not object oriented abstration. MVC is a structure for code maintenance uniformity.
Related
I have two controllers (FirstController, SecondController) that use the same functions, to avoid rewriting them I thought of creating another controller (ThirdController) and subsequently calling the functions through it.
the problem is that if in ThirdController there are relationship query they give me the error that "they don't exist".
example:
User Model
class User extends Authenticatable implements AuthenticatableUserContract
{
use HasFactory, Notifiable;
public function comments(){
return $this->hasMany('App\Models\Comment');
}
ThirdController
class ThirdController extends Controller
{
public static function example($id){
$comments = Comment::find($id)->comments();
return $comments;
}
}
FirstController/SecondController
public function example2(Request $request){
return ThirdController::example($request->id);
When call the route it give me error:
BadMethodCallException: Method Illuminate\Database\Eloquent\Collection::comments does not exist.
my questions are:
Is there any better method instead of creating a third controller?
Is there a solution to this?
p.s. I know I could very well build the queries without exploiting the relationship, but where's the beauty of that? :D
You do not need to create 3rd controller. You can create a class and here you write the query in a function and use this class in controller1 and controller2 by dependency injection.
First thing that's not a best practice to define a static method in one controller and call it in another controller (not recommended way).
Second you're calling Comment::find($id) with comments() relation. you should call a User class, like below snippet:
class ThirdController extends Controller
{
public static function example($id){
$comments = User::find($id)->comments();
return $comments;
}
}
RECOMEND APPROACH:
Creat a one seperate service/repository class in which you'll define a common method i.e. getUserComments() and use it in both or all of three controllers (upto your requirement/needs). By this way you implementations will be on a centric place.
If you want learn about Repository pattern you can get basic idea from: Article#1
I know DI, Solid Principles, factory patterns, adapter pattern and many more maybe. Now let's say I am creating a laravel application and it's gonna be huge. Let's say I have a postscontroller which is resource and has CRUD methods. Now Let's say in that controller's functions, I have a post Model and use it to retrieve data from database. I have a store function where I am creating new Post() and then put it into database.
1) Is it a good practice to have Post model directly in PostController's function and use new Post() also? What is bad in it? I know that this way I am not using dependency injection and patterns, but still why is it bad? As you know , I can still mock the object without dependency injection since laravel has so many amazing testing features. Then why is it that bad to write new keyword in controller's functions and also use Post model directly?
To give you one answer to your question. There is this "Slim controllers, fat models" concept. I used to defer the object creation to the object itself by Named Constructers.
class UserController
{
public function create(UserCreateRequest $request)
{
$user = User::createFromRequest($request);
// do anything else
}
}
class User
{
public static function createFromRequest(UserCreateRequest $request)
{
$user = new User;
$user->first_name = $request->first_name;
// ...
$user->save();
return $user;
}
}
With this you can have more different constructors like User::createAdmin and its testable. You just need to mock the Request.
I have such function in the controller
public function actionNext(){
$category = $this->getCategory();
$not_finished = $this->getQuestionFromCategory($category);
if(!empty($not_finished)){
$next_question_id = getNextQuestionId();
$this->updateNextQuestion();
}
else{
addNextCategory();
}
}
My question is: all fuctions
getCategory
getQuestionFromCategory
getNextQuestionId
updateNextQuestion
addNextCategory
from the example should be in model or controller too (all functions is the requests to the db).
Normally the function related to the db are in model, tipically the model extend the active record and for this contain also the related sql/schema/model related function. in your case is think the function getCategory and probably getQuestionFormCategory
The function related to service for support controller action are place in controller. in your case genNextQuestionId, updateNewQuestion, addNextQuestion.
The main rules is: what's regarding the structural knwoledge of an entity is in model, what's regarding tactical behaviuor is in controller.
Obviously the part related to sort and find are placed in ...search class.
im using laravel 5.
I need to call a controller function but this should be done in another controller.
I dont know how to do this
public function examplefunction(){
//stuff
}
And i have a Route for this function, so at
public function otherfunctioninothercontroller(){
// I need examplefunction here
}
how Can i do this?
1) First way
use App\Http\Controllers\OtherController;
class TestController extends Controller
{
public function index()
{
//Calling a method that is from the OtherController
$result = (new OtherController)->method();
}
}
2) Second way
app('App\Http\Controllers\OtherController')->method();
Both way you can get another controller function.
If they are not in the same folder, place use namespace\to\ExampleClass; on top of your file, then you are able to instantiate your controller.
You can simply instantiate the controller and call the desired method as follows
FirstController.php:
namespace App\Http\Controllers;
class FirstController extends Controller {
public function examplefunction() {
// TODO: implement functionality
}
}
SecondController.php:
namespace App\Http\Controllers;
class SecondController extends Controller {
public function test() {
$object = new FirstController();
$object->examplefunction();
}
}
Now, after i've answered the question, i would like to add the following comment:
Controllers are classes, all rules that applies to normal classes can be applied to them
However, instantiating a controller directly inside another controller to call a desired method signifies a problem in your design for the following 2 reasons:
A controller cannot obtain an instance of another controller directly
Controller should contain as little business logic as possible, and if possible none
The closest possible solution to what you want (WITHOUT BREAKING MVC) is to make an HTTP request to the route that points to the desired method (using cURL, for example) and read the response as the returned data
But this still doesn't make much sense in this scenario because after all you're making an HTTP request from a method in a controller in your project on your server to a method in a controller in your project on your server, seems like unnecessary overhead, right ?
As i said earlier, a controller should contain as little business logic as possible because the logic should stay inside specialized classes (commonly known as Service Classes), and when a processing is requested the controller simply delegates the job of processing to the appropriate service class which does the processing and returns the results to the controller which in turn sends it back as a response
Now imagine if you've the following scenario:
We've got an application that consists of 3 functionalities:
A user can register an account from web application
There's a mobile application that talks to an API to register a user
There's an admin panel, which he can use to add new user
Obviously you need to create 3 controllers, but those controllers contains repeated logic, would you copy/paste the code everywhere ?
Why not encapsulate this logic inside a service class and call it from the controller when needed ?
Let's say I have Controller1 and Controller2. I want to call a function of Controller1 from inside a function placed in Controller2.
// Controller1.php
class Controller1 {
public static function f1()
{
}
}
And on the other controller:
// Controller2.php
use App\Http\Controllers\Controller1;
class Controller2 {
public function f2()
{
return Controller1::f1();
}
}
Points to be noted:
f1() is declared static
A call to a controller from inside another controller is a bad idea. There is no sense of meaning of controllers then. You should just redirect to web.php to save safe whole architecture like this:
class MyController {
public function aSwitchCaseFunction(Request $requestPrm){
...
//getting path string from request here
...
switch($myCase){
case CASE_1:
return redirect()->route('/a/route/path');
....
}
}
}
currently i am doing a project in zend the way i am doing is working perfectly but i am sure its not the way i am suppose to do i mean i am not following MVC and i want to apply MVC in my zend app.
i am pasting code of one simple module which will describe what i am doing .kindly correct me where i am making faults.
my controller
class ContactsController extends Zend_Controller_Action{
public function contactsAction(){
if(!Zend_Auth::getInstance()->hasIdentity()){
$this->_redirect('login/login');
}
else{
$request = $this->getRequest();
$user = new Zend_Session_Namespace('user');
$phone_service_id = $user->p_id;
$instance = new Contacts();
$select = $instance->Get_Contacts($p_id);
$adapter = new Zend_Paginator_Adapter_DbSelect($select);
$paginator = new Zend_Paginator($adapter);
.
.
//more code
}
plz note this 2 line in my controller
$instance = new Contacts();
$select = $instance->Get_Contacts($pid);
this is my contacts class in models
class Contacts extends Zend_Db_Table{
function Get_Contacts($p_id){
$DB = Zend_Db_Table_Abstract::getDefaultAdapter();
$select = $DB->select()
->from('contact', array('contact_id','contact_first_name','contact_mobile_no','contact_home_no','contact_email','contact_office_no'))
->where('pid = ?', $p_id)
->order('date_created DESC');
return $select;
}
}
after this i simple assign my result to my view.
note please
as its working but there is not private data members in my class,my class is not a blue print.there are no SETTERS AND GETTERS .how can i make my code that best suits MVC and OOP??
The most simple answer: you are already almost MVC. You use a Zend_Controller_Action to grab some data and pass this on to a view layer where you render the html. The only missing part is your model, which is mixed up between the controller and your data gateway (where you implemented a table data gateway pattern, that Zend_Db_Table thing).
I gave a pretty thorough explanation in an answer to another question how I'd properly set up the relations between Controller and Model. I also combined this with a Form, to handle data input, filtering and validation. Then to bundle some common functions, I introduced a Service layer between the Model and Controller.
With the controller, you perform some actions (list all my contacts, create a new contact, modify a contact) and the model is purely containing the data (id, name, phone, address). The service helps to group some functions (findContactByName, findContactById, updateContactWithForm).
If you know how to split Controller, Mode, Form and Service, your controller can become something like this:
class ContactsController extends Zend_Controller_Action
{
public function indexAction ()
{
if (!$this->hasIdentity()) {
$this->_redirect('login/login');
}
$service = new Application_Service_Contacts;
$contacts = $service->getContacts();
$paginator = $service->getPaginator($contacts);
$this->view->paginator = $paginator;
}
protected function hasIdentity ()
{
return Zend_Auth::getInstance->hasIdentity();
}
}
It is your personal taste what you want to do in your controller: I'd say you put as less as possible in your controllers, but you need to keep the control. So: a call to get data happens in the controller, retrieving this data happens somewhere else. Also: a call to convert a dataset into something else happens in the controller, the conversion happens somewhere else.
This way you can change the outcome in controllers extremely fast if you provided enough methods to your service classes to fetch the data. (Note I took the Zend_Auth to another function: if you have other actions, you can use this same function. Also, if you want to change something in your authentication, you have one place where this is located instead of every action in the controller)
keep one thing in mind when u learn new technology so first read thier own documentation. No one can explain better than them. Its hard to understand firstly but when you study it you will usedto and than u will love it like me Zend Offical Site