Laravel: How to prevent eloquent model save using events inside a package? - php

I am trying to prevent a model from saving to database, if it has certain value in a property.
Inside service provider:
public function register()
{
$this->registerSaveRelated();
$this->registerEvents();
}
public function registerEvents()
{
$app = $this->app;
$app['events']->listen('eloquent.saving*', function ($model) use ($app) {
$app['my_service']->checkModel($model);
});
}
public function registerSaveRelated(){
// More code...
checkModel($model) gets fired, as expected. However, nothing happens if I return false. The model continues to save. What is the correct way to do it?
It needs to be done in the package, not model. So Model::saving() is not an option.

There are two solutions that I can think of.
A. Use Model::saving() and just differentiate between the packages using a boolean or other simple logic.
B. Override the save method and put the logic in that function itself.

I think you are on the right track. You are correct that checkModel needs to return true or false. The important part, however, is what the closure passed to listen returns. That is what determines whether it saves or not. Right now its not returning anything. Try adding return like so:
public function registerEvents()
{
$app = $this->app;
$app['events']->listen('eloquent.saving*', function ($model) use ($app) {
return $app['my_service']->checkModel($model);
});
}

Related

Using Request, Response in constructor in router class Slim Framework 3

I am using Slim Framework for my application. I am using routes. All is working fine. But now I want to do some pre-process working under my constructor on Request and Response.
So that I should not rework on every function of the class. Like getting host and token in every function. I am using middle-ware for many pre-process. But I also want to do some work in class constructor. When I am trying to access request and response interface in constructor, It is showing the error, Please show me the right way of using Request and Response in a class constructor. Will I have to append $app, or will need to work with container.
If it can be done without help of middleware, It will be great for me.
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
$app->group('/products', function() {
new \Products($this);
});
And I have a class called Products.
class Products
{
public function __construct($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
//I want to use Request and Response here in constructor. But it is showing error.
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
public function createupdate($request, $response, $args) {
//This is working fine.
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
}
When you really want to do this, then you could get the request/response object from the container.
class Products
{
public function __construct($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
$request = $app->getContainer()->get('request');
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
// [..]
}
But, also this will not make much difference $this->req_data['request_token'] is nearly as long as $request->getAttribute('request_token'); so you should use this inside the code.
Note: I expect you to set this attribute already inside middleware, so it may not be available here, because first the container will create a new request object and second cause the middleware is not run when php executes your constructor code.
When you now still want to use $this->req_data['request_token'] inside your class then you should do this:
$products = new \Products();
$app->group('/products', function() use ($products) {
$products->addRoutes($this);
})->add($products); // add the class as middleware as well to set there the class attributes (__invoke function)
class Products
{
public function addRoutes($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
}
public function __invoke($request, $response, $next) // this is middleware function
{
$this->req_data['request_token'] = $request->getAttribute('request_token');
return $next($request, $response); // next in this example would be your route function like createupdate()
}
}
Ofcourse you will get an error, $request is not defined.
public function __construct($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
// Where does $request comes from?!
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
Slim way of doing pre-processing is by using middlewares.
When a route is called, it is automatically injected with the Request and Response objects (and the request route params if any), but when a class is created for the route, it is not automatically injects those instances to the constructor, so they are not available "out of the blue".
If you have pre-processing, I would stick to middlewares, it is much cleaner code (although this is my opinion).

how to run method in model instead of controller in laravel 5.1?

I have following method in my controller:
public function store()
{
$data=Input::all();
User::create($data);
}
The above code works perfectly. My question is can we run the above method in model without writing in controller? And which is the best approach?
you can try following way
in your model
public function insetUser()
{
$input = Input::all();
User::create($input);
//here instead of User,you can use self like self::create($input);
}
in controller you can
public function store()
{
User::insetUser();
}
If it is in model, how you are going to trigger it?
It is in fact only one line of code
User::create(Input::all());
What it is here is the instance of model User and method create with injected model Input. Of couse you may set (model) User.php:
public function storedata()
{
return $this->create(Input::all());
}
And then run it in your controller:
User::storedata();
But is it better? ;-)
In this circumstance, I don't think you gain much from moving things arounds.
If this is your Controller method:
public function store()
{
$data=Input::all();
User::create($data);
}
then this makes sense. The input data is being handled by the controller method, and is being passed to the create method on your User Model.
If however, there was more logic required before creating a User record, then it'd be perfectly valid to abstract that logic to your User model. But as it stands though, I think your current implementation is appropriate.

Laravel: retrieve bound model from request

Is there any easy way of retrieving the route binded model within a Request?
I want to update a model, but before I do, I want to perform some permissions checks using the Requests authorize() method. But I only want the owner of the model to be able to update it.
In the controller, I would simply do something like this:
public function update(Request $request, Booking $booking)
{
if($booking->owner->user_id === Auth::user()->user_id)
{
// Continue to update
}
}
But I'm looking to do this within the Request, rather than within the controller. If I do:
dd(Illuminate\Http\Request::all());
It only gives me the scalar form properties (such as _method and so on, but not the model).
Question
If I bind a model to a route, how can I retrieve that model from within a Request?
Many thanks in advance.
Absolutely! It’s an approach I even use myself.
You can get the current route in the request, and then any parameters, like so:
class UpdateRequest extends Request
{
public function authorize()
{
// Get bound Booking model from route
$booking = $this->route('booking');
// Check owner is the currently authenticated user
return $booking->owner->is($this->user());
}
}
Unlike smartman’s (now deleted) answer, this doesn’t incur another find query if you have already retrieved the model via route–model binding.
However, I’d also personally use a policy here instead of putting authorisation checks in form requests.
Once you did your explicit binding (https://laravel.com/docs/5.5/routing#route-model-binding) you actually can get your model directly with $this.
class UpdateRequest extends Request
{
public function authorize()
{
return $this->booking->owner->user_id == $this->booking->user()->id;
}
}
Even cleaner!
To add on to Martin Bean's answer, you can access the bound instance using just route($param):
class UpdateRequest extends Request
{
public function authorize()
{
$booking = $this->route('booking');
return $booking->owner->user_id == $this->user()->id;
}
}
Note: This works in Laravel 5.1. I have not tested this on older versions.
If you are not using the bindings middleware or if you want to access the bound $model anywhere else apart from FormRequest and Controller you can use the following:
$book = app(Book::class)->resolveRouteBinding(request()->route('book'));

DI object method

How do I inject dependency in some of the object methods instead of the constructor?
The example below works fine for __constructor injection
How do I inject the DateTime object in indexAction?
app.php
$app['index.controller'] = $app->share(function() use ($app) {
return new Controllers\IndexController(new \DateTime());
});
IndexController.php
namespace Moo\Controllers;
class IndexController
{
private $date;
public function __construct(\DateTime $date)
{
$this->date = $date;
}
public function indexAction()
{
return $this->date->format('y-m-d');
}
}
If your class has different dependencies depending on which method is called, than these methods should probably be defined in separate classes.
In case of controllers I think the rules are simple. Dependencies your action methods need should be passed via the constructor. Anything coming with a request should come as a method argument.
I'm not sure what kind of dependencies you're trying to inject. If they're just services, than you should split your controller to multiple classes. Big number of constructor arguments is a code smell. It's good you're concerned by it, but you're trying to solve it in a wrong way.
If the dependency is coming with a request, you should inject it into your controller method (action). A controller method should accept a request and return a response.
All route placeholders are automatically registered as a Request attribute. So if your date comes from a request:
$app->get('/my/path/{date}', 'index.controller:indexAction');
It will be available as a request attribute:
public function indexAction(Request $request)
{
$request->attributes->get('date');
}
Any request attributes can be directly injected into the controller:
public function indexAction($date)
{
}
This also means, that if you manually set a request attribute, it will be possible to inject it to your controller. It's matched by name:
// somewhere in your code (event perhaps)
$request->attributes->set('myDate', new \DateTime());
// in your controller
public function indexAction(\DateTime $myDate)
{
}
Finally you can convert simple types coming with the request, to more complex ones with route variable converters.
$callback = function ($post, Request $request) {
return new Post($request->attributes->get('slug'));
};
$app->get('/blog/{id}/{slug}', 'your.controller:indexAction')
->convert('post', $callback);
Read the docs for more.
Just for the record, as you won't probably want to do this, its is, indeed, possible to do method injection in Silex (in fact it is the Pimple container the one responsible to do it) by using the extend method:
<?php
$app['some_service'] = $app->share(function() use ($app) {
return SomeClass($app['some_dependency']);
});
$app['some_service'] = $app->extend('some_service', function($instance, $app) {
$instance->setSomeDependency($app['another_dependency']);
return $instance;
});
Having said that you should take into account what #JakubZalas is explaining as you will not want to call your controller action inside the extend method (you want to be called by the dispatcher).

laravel model callbacks after save, before save, etc

Are there callbacks in Laravel like:
afterSave()
beforeSave()
etc
I searched but found nothing. If there are no such things - what is best way to implement it?
Thanks!
The best way to achieve before and after save callbacks in to extend the save() function.
Here's a quick example
class Page extends Eloquent {
public function save(array $options = [])
{
// before save code
parent::save($options);
// after save code
}
}
So now when you save a Page object its save() function get called which includes the parent::save() function;
$page = new Page;
$page->title = 'My Title';
$page->save();
Adding in an example for Laravel 4:
class Page extends Eloquent {
public static function boot()
{
parent::boot();
static::creating(function($page)
{
// do stuff
});
static::updating(function($page)
{
// do stuff
});
}
}
Actually, Laravel has real callback before|after save|update|create some model. check this:
https://github.com/laravel/laravel/blob/3.0/laravel/database/eloquent/model.php#L362
the EventListener like saved and saving are the real callbacks
$this->fire_event('saving');
$this->fire_event('saved');
how can we work with that? just assign it to this eventListener example:
\Laravel\Event::listen('eloquent.saving: User', function($user){
$user->saving();//your event or model function
});
Even though this question has already been marked 'accepted' - I'm adding a new updated answer for Laravel 4.
Beta 4 of Laravel 4 has just introduced hook events for Eloquent save events - so you dont need to extend the core anymore:
Added Model::creating(Closure) and Model::updating(Closure) methods for hooking into Eloquent save events. Thank Phil Sturgeon for finally pressuring me into doing this... :)
In Laravel 5.7, you can create a model observer from the command line like this:
php artisan make:observer ClientObserver --model=Client
Then in your app\AppServiceProvider tell the boot method the model to observe and the class name of the observer.
use App\Client;
use App\Observers\ClientObserver;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Client::observe(ClientObserver::class);
}
...
}
Then in your app\Observers\ you should find the observer you created above, in this case ClientObserver, already filled with the created/updated/deleted event hooks for you to fill in with your logic. My ClientObserver:
namespace App\Observers;
use App\Client;
class ClientObserver
{
public function created(Client $client)
{
// do your after-model-creation logic here
}
...
}
I really like the simplicity of this way of doing it. Reference https://laravel.com/docs/5.7/eloquent#events
Your app can break using afarazit solution*
Here's the fixed working version:
NOTE: saving or any other event won't work when you use eloquent outside of laravel, unless you require the events package and boot the events. This solution will work always.
class Page extends Eloquent {
public function save(array $options = [])
{
// before save code
$result = parent::save($options); // returns boolean
// after save code
return $result; // do not ignore it eloquent calculates this value and returns this, not just to ignore
}
}
So now when you save a Page object its save() function get called which includes the parent::save() function;
$page = new Page;
$page->title = 'My Title';
if($page->save()){
echo 'Page saved';
}
afarazit* I tried to edit his answer but didn't work
If you want control over the model itself, you can override the save function and put your code before or after __parent::save().
Otherwise, there is an event fired by each Eloquent model before it saves itself.
There are also two events fired when Eloquent saves a model.
"eloquent.saving: model_name" or "eloquent.saved: model_name".
http://laravel.com/docs/events#listening-to-events

Categories