I have a PHP trait that I will use in any model that can do a certain set of actions. For example one of these actions is completion where completed_at is marked with a timestamp.
The trait method is:
/**
* #return $this
* #throws Exception
*/
public function markCompleted(){
if($this->canDoAction('complete')){
$this->completed_at = Carbon::now();
return $this;
}
}
In my controller I am calling this on a model that can do this action like below.
$app->markCompleted()->save();
The $app when I view its contents it is not null.
Running this command returns an error like
local.ERROR: Call to a member function save() on null
Wouldn't $this represent the model that uses this trait?
Another variation on what The Alpha said.
/**
* #return $this
* #throws Exception
*/
public function markCompleted(){
if($this->canDoAction('complete')){
$this->completed_at = Carbon::now();
}
return $this;
}
This way you always return a model, and you can chain other functions before the save is performed if you needed.
If the condition doesn't meet then null will be returned, so instead of calling the save separately, do that inside that method, for example:
public function markCompleted()
{
if ($this->canDoAction('complete')) {
$this->completed_at = Carbon::now();
return $this->save(); // true/false
}
}
Then use it like:
$app->markCompleted();
The way way, you coded, the save method will be called even if the condition doesn't match and that's a side effect.
Related
I've tried to combine the Laravel docs on implicit binding and optional parameters and have the following code.
routes file:
Route::get('go/{example?}', [ExampleController::class, 'click'])->name('example');
And in the controller:
public function click(Example $example = null)
{
// Execution never reaches here
}
Execution never reaches the controller unless there is an Example with the correct slug, as it throws a 404. I want to check if $example is null in the controller and use custom logic there. How can this be accomplished?
Try this
Route::get('go/{example?}', [ExampleController::class, 'click'])->name('example');
public function click($example)
{
if($example != null){
$example = Example::findOrfail($example);
}
}
in model binding it will automatically run findOrfail to that model so don't you that so you will have control over it then you can manage
the #ettdro answer is perfect (and all credit is to him), but i think an answer with actual code would be useful:
routes:
Route::get('go/{example?}', [ExampleController::class, 'click'])->name('example');
controller:
public function click(Example $example)
{
// Stuff
}
Model of Example:
/**
* Retrieve the model for a bound value.
*
* #param mixed $value
* #param string|null $field
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
$result=$this->where('id', $value)->first();
return ($result)?$result:new Example();
}
You should obtain in the controller always a valid object, empty or not.
Had the same problem, and i'm happy with this solution.
To do that, you need use 'id' as your primary key in database and model,
if you are using another name for your pimary key, then you need to define it at your route:
Route::get('go/{example:number?}', [...]);
After creating a model with -mcr (php artisan make:model Institution -mrc), the show function in controller was scaffolded as:
/**
* Display the specified resource.
*
* #param \App\Organization\Institution $institution
* #return \Illuminate\Http\Response
*/
public function show(Institution $institution)
{
return view('institutions.show', ['institution' => $institution]);
}
The return view... was inserted by me. I was expecting it to have it populated with the object whose id was sent in the parameters.
/institutions/1
But, after using dd($institution), I verified that it has the ID, not the object.
Shouldn't this variable return me the object?
This is called Route Model Binding. Your route will need to look something like:
Route::get('institutions/{institution}', 'InstitutionController#show');
and then as per your controller
public function show(Institution $institution)
{
return view('institutions.show', compact($institution))
}
You can read more on this here.
I imagine your route had the parameter called {id} rather than {institution}.
Replace the parameter of show function
public function show(Institution $institution)
{
return view('institutions.show', compact($institution))
}
becomes
public function show($id)
{
$institution = App\Institution::findOrFail($id);;
return view('institutions.show', compact('institution'));
}
and in your routes
Route::get('institutions/{id}', 'InstitutionController#show');
I am Using Resource controller in Laravel 5.6. I am following this tutorial. I found here Resource controller uses the Route model binding, This means that you dont need to fetch the specified task by the id. Laravel will do it for you. $task variable which is passed into the show() method is passed to the view via compact method.
In my code I am using below code in Controller.
/**
* Display the specified resource.
*
* #param \App\sura $sura
* #return \Illuminate\Http\Response
*/
public function show(Sura $sura)
{
return $sura;
}
Here I am getting the Whole Sura object not the id.
Why I am getting the whole object not the id ? Where is the issue ?
https://laravel.com/docs/5.6/routing#route-model-binding
When dependency inject model
public function show(Sura $sura)
{
return $sura; // it is instance of Sura
}
For get id use this
public function show($suraId)
{
dd($suraId);// return integer number
}
What I'm trying to do is to append the comments of each article to the articles object, but the problem is that I need to request different number of comments each time.
and for some reason I need to use mutators for that, because some times I request 50 articles and I don't want to loop through the result and append the comments.
So is it possible to do something like the following and how to pass the extra argument.
This the Model:
class Article extends Model
{
protected $appends = ['user', 'comments', 'media'];
public function getCommentsAttribute($data, $maxNumberOfComments = 0)
{
// I need to set maxNumberOfComments
return $this->comments()->paginate($maxNumberOfComments);
}
}
Here is the controller:
class PostsController extends Controller
{
public function index()
{
//This will automatically append the comments to each article but I
//have no control over the number of comments
$posts = Post::user()->paginate(10);
return $posts;
}
}
What I don't want to do is:
class PostsController extends Controller
{
public function index()
{
$articles = Post::user()->all();
$number = 5;
User::find(1)->articles()->map(function(Article $article) {
$article['comments'] = $article->getCommnets($number);
return $article;
});
return Response::json($articles);
}
}
Is there a better way to do it? because I use this a lot and it does not seams right.
Judging from the Laravel source code, no – it's not possible to pass an extra argument to this magic accessor method.
The easiest solution is just to add another, extra method in your class that does accept any parameters you wish – and you can use that method instead of magic property.
Eg. simply rename your getCommentsAttribute() to getComments() and fire ->getComments() instead of ->comments in your view, and you are good to go.
I just set a public property on the model. At the accessing point, I update that property to my desired value. Then, in the attribute method, I read the desired arguments from that property. So, putting all of that together,
// Model.php
public $arg1= true;
public function getAmazingAttribute () {
if ($this->arg1 === false)
$this->relation()->where('col', 5);
else $this->relation()->where('col', 15);
}
// ModelController.php
$instance->arg1 = false;
$instance->append('amazing');
It is been a while for this question, but maybe someone will need it too.
Here is my way
{
/**
* #var string|null
*/
protected ?string $filter = null;
/**
* #return UserSettings[]|null
*/
public function getSettingsAttribute(): ?array
{
return services()->tenants()->settings($this)->getAll();
}
/**
* #return FeatureProperty[]|null
*/
public function getFeaturePropertiesAttribute(): ?array
{
return services()->tenants()->featureProperty($this)->getListByIds($this->filter);
}
/**
* #param string|null $filter
* #return Tenant
*/
public function filter(string $filter = null): Model
{
$this->filter = $filter;
return $this;
}
Accessor is using some service to get values. Service accepts parameters, in my case string, that will be compared with featureProperty->name
Magic happens when you return $this in filter method.
Regular way to call accessor would be:
$model->feature_properties
Extended way:
$model->filter('name')->feature_properties
Since filter argument can be null, we can have accessor like this:
$filter = null
$model->filter($filter)->feature_properties
In case you would like to play with it a little more you can think about overriding models getAttribute or magic __call methods implementing filter in manner which will be similar to laravel scopes
I know its an old question, but there is another option, but maybe not the best:
$articles = Post::user()->all();
$number = 5;
$articles->map(function($a) use($number){
$a->commentsLimit = $number;
return $a;
});
And then in getCommentsAttribute():
return $this->comments()->paginate($this->commentsLimit);
Namespaces omitted for brevity...
I have written the following service provider and registered in config/app.php:
class OfferServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerLossControlManager();
}
protected function registerLossControlManager()
{
$this->app->bind('LossControlInterface', 'LossControl');
}
}
Here is my LossControlInterface
interface LossControlInterface
{
/**
* #param int $demandId
* #param float $offerTotal
* #param float $productTotal
* #param null|int $partnerId
* #return mixed
*/
public function make($demandId, $offerTotal, $productTotal, $partnerId = null);
/**
* #return float
*/
public function getAcceptableLoss();
/**
* #return bool
*/
public function isAcceptable();
/**
* #return bool
*/
public function isUnacceptable();
/**
* #return null
*/
public function reject();
}
Now within the controller, I can inject the LossController as follows:
use LossControlInterface as LossControl;
class HomeController extends BaseController {
public function __construct(LossControl $lossControl)
{
$this->lossControl = $lossControl;
}
public function getLossThresholds()
{
$lossControl = $this->lossControl->make(985, 1000, null);
var_dump('Acceptable Loss: ' . $lossControl->getAcceptableLoss());
var_dump('Actual Loss: ' . $lossControl->calculateLoss());
var_dump('Acceptable? ' . $lossControl->isAcceptable());
}
}
However if I try to dependency inject the LossControlInterface from within a custom class called by a command:
[2014-09-02 13:09:52] development.ERROR: exception 'ErrorException' with message 'Argument 11 passed to Offer::__construct() must be an instance of LossControlInterface, none given, called in /home/vagrant/Code/.../ProcessOffer.php on line 44 and defined' in /home/vagrant/Code/.../Offer.php:79
It appears as though I am unable to dependency inject the interface into a custom class, but I can when dependency injecting into a controller.
Any thoughts on what Im doing wrong or have omitted to get the automatic resolution working?
The IoC is automatic within controllers, and you don't see the injection because Laravel handles the construction of controllers for you. When creating any other custom class by using the new keyword, you will still need to send in all of the parameters needed to it's constructor:
$myClass = new ClassWithDependency( app()->make('Dependency') );
You can hide this, to a degree, by funneling creation of your custom class through a service provider:
// Your service provider
public function register()
{
$this->app->bind('ClassWithDependency', function($app) {
return new ClassWithDependency( $app->make('Dependency') );
});
}
Then just have the IoC make it whenever you need it:
$myClass = app()->make('ClassWithDepenency');
In your case, you can change your code to look like this:
private function setOffer(Offer $offer = null) {
$this->processOffer = $offer ?:
new Offer( app()->make('LossControlInterface') );
}
A perhaps cleaner approach could be to create a service provider and an OfferFactory which gets injected into your controller. The controller can then request the factory to create the offer whenever it needs one:
// Controller
public function __construct(OfferFactory $offerFactory)
{
$this->offerFactory = $offerFactory;
}
public function setOffer(Offer $offer = null)
{
$this->processOffer = $offer ?: $this->offerFactory->createOffer();
}
// OfferFactory
class OfferFactory
{
public function createOffer()
{
return app()->make('Offer');
}
}
This has the benefit of completely decoupling your controller from the logic behind the creation of the offer, yet allowing you to have a spot to add any amount of complexity necessary to the process of creating offers.
In Laravel 5.2 the simplest solution for your particular problem would be to replace
new Offer();
with
App::make('Offer');
or even shorter
app('Offer');
which will use Laravel Container to take care of dependencies.
If however you want to pass additional parameters to the Offer constructor it is necessary to bind it in your service provider
App::bind('Offer', function($app, $args) {
return new Offer($app->make('LossControl'), $args);
});
And voila, now you can write
app('Offer', [123, 456]);
In laravel 5.4 (https://github.com/laravel/framework/pull/18271) you need to use the new makeWith method of the IoC container.
App::makeWith( 'App\MyNameSpace\MyClass', [ $id ] );
if you still use 5.3 or below, the above answers will work.