I keen to make my code decouple and ready for testing.
I have an Eloquent model getBudgetConvertedAttribute is depend on sentry user attribute.
public function getBudgetConvertedAttribute()
{
return Sentry::getUser()->currency * $this->budget;
}
This throw error while testing because Sentry::getUser is return null.
My question is, How shall I code to inject user into model from controller or service provider binding or testing?
Inject a $sentry object as a dependency in the constructor instead of using the Sentry Facade.
Example
use Path\To\Sentry;
class ClassName
{
protected $sentry
public function __construct(Sentry $sentry)
{
$this->sentry = $sentry;
}
public function methodName()
{
$this->sentry->sentryMethod();
}
}
Why not just create a method on the model, then takes a Sentry user object as a parameter?
public function getBudgetConverted(SentryUser $user)
{
return $user->currency * $this->budget;
}
You’ll need to change the type-hint (SentryUser) to the actual name of your user class.
If this is to aid testing, you could go one step furhter and type-hint on an interface (which you should be any way), that way you could test your method with a mock user object rather than one that may have a load of other dependencies like a database connection, which Eloquent models do.
Related
In my application, users can belong to different accounts and have different roles on those accounts. To determine which account is "current" I am setting a session variable in the LoginController in the authenticated() method.
$request->session()->put('account_id', $user->accounts()->first()->id);
Then, throughout the application I am doing a simple Eloquent query to find an account by ID.
While this "works", I am basically repeating the same exact query in every single Controller, Middleware, etc. The maintainability is suffering and there are duplicate queries showing in Debugbar.
For example, in every controller I am doing:
protected $account;
public function __construct()
{
$this->middleware(function($req, $next){
$this->account = Account::find($req->session()->get('account_id'));
return $next($req);
});
}
In custom middleware and throughout the entire application, I am essentially doing the same thing - finding Account by ID stored in session.
I understand you can share variable with all views, but I need a way to share with the whole application.
I suppose much in the same way you can get the auth user with auth()->user.
What would be the way to do this in Laravel?
I would create a class to handle this logic. Making it a singleton, to ensure it is the same class you are accessing. So in a provider singleton the class you are gonna create in a second.
$this->app->singleton(AccountContext::class);
Create the class, where you can set the account in context and get it out.
class AccountContext
{
private $account;
public function getAccount()
{
return $this->account;
}
public function setAccount($account)
{
$this->account = $account;
}
}
Now set your account in the middleware.
$this->middleware(function($req, $next){
resolve(AccountContext::class)->setAccount(Account::find($req->session()->get('account_id')));
return $next($req);
});
Everywhere in your application you can now access the account, with this snippet.
resolve(AccountContext::class)->getAccount();
As we know in Laravel 5.2 Route::controller() and Route::controllers() method was deprecated but it was very handy for reducing the number of routes. I was able to write simple route like this Route::controller('admin/invoice','InvoiceController'). With this simple one route, I can manage all things related to making invoice related work by a controller.
class InvoiceController extends Controller{
public function getInvoices(){ }
public function getInvoiceDetails(){ }
public function postStoreInvoice(){ }
public function postUpdateInvoice(){ }
public function postStoreInvoiceDetails(){ }
public function postupdateInvoiceDetails(){ }
public function postDeleteInvoice(){ }
public function postDeleteInvoiceDetails(){ }
....
}
but unfortunately this Route::controller() and Route::controllers() no longer available laravel version > 5.1. An option available Route::resource() but it has a limited number of the route. The laravel route is Macroable, there is an option to extend the route features like
Illuminate\Routing\Router::macro('controller', function ($routes) {
// implementation
});
Is there anyone who implements Route::controller() and Route::controllers() method for Laravel 5.8, 6 ? or suggest any way.
You can use Route::resource() or Route::resources().
Example:
Route::resource('books', 'BookController');
this will assumes you have
class BookController extends Controller {
// to list resources.
public function index();
// to show create form.
public function create();
// to store resource in database.
public function store();
// to show single resource.
public function show();
// to show edit form.
public function edit();
// to edit and then store the modified resource in database.
public function update();
// to delete a resource from database.
public function destroy();
}
You should read https://laravel.com/docs/master/controllers#resource-controllers for more information.
Edit
Implicit controllers was removed in version 5.2 for some reason.
If you come from the CodeIgniter world, then you may have warm and fuzzy
feelings for implicit routing. You know, where the URI matches up to
the controller method that will be called. You might even want this
for your Laravel development (which Laravel can do).
Though it might seem useful at first to simply call
Route::controller('admin', 'AdminController') and then declare all of
your desired routes from the controller, there are a number of
setbacks to this. Think about how you would, when using implicit routing,
leverage named routes, or create nested resources, or even do
something as simple as rename your controller class without affecting
your URI design.
No, when it comes to implicit routing, just say no.
source: https://laracasts.com/lessons/say-no-to-implicit-routing
However if you want this functionality you can use this package:
Laravel Routes Publisher or Laravel Advanced Route
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 usually use parameters like this:
public function test($parameter)
{
echo 'Parameter value: ' . $parameter;
}
While looking at laravel service container I see this code.
public function __construct(UserRepository $users)
{
$this->users = $users;
}
According to the documentation it uses reflection.But i dont understand.
I dont know how the parameter UserRepository $users works. Is that an alias or something?
This is called type-hinting and is used to inject dependencies in a constructor or to validate the right type of argument is passed to a function. The injection simply means that if the class is called with the make method, Laravel will automatically provide an instance of the class required by your constructor.
For example if you have a function public function something(string $something) it would throw an error if any other type than a String is passed to this function, making sure the right data is used.
From the laravel documentation:
Alternatively, and importantly, you may "type-hint" the dependency in the constructor of a class that is resolved by the container, including controllers, event listeners, queue jobs, middleware, and more. In practice, this is how most of your objects should be resolved by the container.
For example, you may type-hint a repository defined by your application in a controller's constructor. The repository will automatically be resolved and injected into the class:
Laravel has a great service container and it makes all dependency injections, so you don't need to pass a class a parameter, laravel do it for you.
without container you have to pass this parameter
class A {
public $foo;
public function __construct (Foo $foo){
$this->foo
}
$classA = new A((new Foo))
When laravel encounter with these classes, it resolves them.
Also you can define manually these classes using singleton() or bind() methods
$this->app->singleton('FooBar', function($app)
{
return new FooBar($app['SomethingElse']);
});
Or you may use interfaces. You can bind implemented class for to the interface and laravel when encounter with that interfance, it will resolve as you wish
$this->app->bind('App\ICacheManager', 'App\RedisManager');
public $redis;
public function __contruct(ICacheManager $redis){
$this->redis = $redis;
}
for more further check out laravel service container
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.