Laravel 5.1 Eloquent Events - php

I am using Laravel Model Events. My requirement is to pass additional parameters to event.
I am trying like that:
$feedback = new Feedback();
$feedback->user_id = $this->user_id;
$feedback->feedback = $request->feedback;
$data = array(
'message' => $request->feedback,
'from' => $this->data->user->email,
'name' => $this->data->user->displayname
);
$feedback->save($data);
My event is:
public function boot()
{
Feedback::saved(function ($item) {
//\Event::fire(new SendEmail($item));
});
}
But it only send Model object while i am trying to sending:
$data = array(
'message' => $request->feedback,
'from' => $this->data->user->email,
'name' => $this->data->user->displayname
);
How i send this to event?

There definitely are ways around this problem. The first one that comes to mind is getting the Auth data inside the Provider where your event lives.
You'll need to do something like this:
use Auth; //Assuming this is how you are handling authentication
public function boot()
{
Feedback::saved(function ($item) {
$user = Auth::user();
$data = [
'message' => $item->feedback,
'from' => $user->email,
'name' => $user->displayname
];
\Event::fire(new SendEmail($data));
});
}
You may be able to do $item->user->email instead, and not bother with Auth, I'm just not able to know the relationships from what you've posted so far.
The code might need a little adjustment to work within your application, let me know if anything else comes up!

Related

Access form request from Observer laravel

I am trying to cleanup my controller. I have a lot form fields so I want to use observer to insert for the other model that have relationship with the main model
I have already successfully insert the request to the database in a controller but it seems to long and heavy. See code below
function insert(Request $request){
$bankStatementName = time().'.'.request()->bankStatement->getClientOriginalExtension();
request()->bankStatement->move(public_path('bankStatement'), $bankStatementName);
$identityName = time().'.'.request()->identity->getClientOriginalExtension();
request()->identity->move(public_path('identity'), $identityName);
$passportName = time().'.'.request()->passport->getClientOriginalExtension();
request()->passport->move(public_path('passport'), $passportName);
$customer = Customer::find(Auth::user()->id);
$relations = new Customer_relationship([
'kinName' => $request->kinName,
'kinGender' => $request->kinGender,
'kinEmail' => $request->kinEmail,
'kinRelation' => $request->kinRelation,
'kinAddress' => $request->kinAddress
]);
$company = new Customer_company([
'compName' => $request->compName,
'compEmail' => $request->compEmail,
'compPhone' => $request->compPhone,
'compAddress' => $request->compAddress
]);
$bank = new Customer_bank([
'accNumber' => $request->accNumber,
'bankName' => $request->bankName,
'accName' => $request->accName
]);
$document = new Customer_document([
'identity' => $identityName,
'bankStatement' => $bankStatementName,
'passport' => $passportName
]);
$customer->relation()->save($relations);
$customer->company()->save($company);
$customer->bank()->save($bank);
$customer->document()->save($document);
Customer::where('user_id', Auth::user()->id)
->update([
'title' => $request->title,
'middlename' => isset($request->middlename) ? $request->middlename : "",
'phone' => $request->phone,
'gender' => $request->gender,
'DOB' => $request->DOB,
'marital' => $request->marital,
'residential_address' => $request->residential_address,
'city' => $request->city,
'state' => $request->state,
'lga' => $request->lga,
'nationality' => $request->nationality,
'complete_registration' => 1 ]);
}
So how can I access the form request field from Updating function from observer to do a controller cleanup
Welcome to SO!
If you want to use Observers here, you should start by reading up on https://laravel.com/docs/5.8/eloquent#observers and https://laravel.com/docs/5.8/queues
This will likely work if you have all the data needed on your parent model, since you would just pass that model into the job that was triggered by the observer. If not, then observer/job might not be the best solution in your case. Instead I would probably create some sort of service, where you move the responsibility for creating these relationships. That way you can keep a clean controller level that only calls a service to create the models and then returns the result.
An example of this could be:
namespace App\Http\Controllers;
use App\Models\Something\SomeService;
class SomeController extends Controller
{
/**
* #var SomeService
*/
private $someService;
public function __construct(SomeService $someService)
{
$this->someService = $someService;
}
public function store()
{
$request = request();
$name = $request->input('name');
$something = $this->someService->create($name);
return response()->json(['data' => $something]);
}
}
namespace App\Models\Something;
class SomeService
{
public function create(string $name): Something
{
// Do whatever in here...
}
}
This is a simplified example of how I would do it. Hope it helps you a bit.
If you still want to use a job to take care of this, then I still don't think an observer is the right solution for you, as those are triggered on model events, such as created. This mean that you will not have access to the request object at that time, but only was was created (The model). Instead you could dispatch a job directly from the controller/service. That is all described in the queue link I posted at the top of the answer.

Laravel phpunit test failing authorization

I have a working api only application.
I am required to write a test decided to use laravel's phpunit test. This simple app allows only authenticated users can store, update or delete a book. Everyone else (authenticated or not) can retrieve a list of all books or view details of one book.
For my books test, I have written a test that first creates a user then a random token for the user. Then the token is passed using withHeaders when posting a new book record
class BooksTest extends TestCase
{
public function test_onlyAuthenticatedUserCanAddBookSuccessfully()
{
$user = factory(User::class)->create();
$token = str_random(10);
$book = factory(Book::class)->create();
$response = $this->withHeaders(['Authorization' => "Bearer $token"])
->json('POST', '/api/books', [
'title' => 'book post',
'author' => 'post author'
]);
$response->assertStatus(201);
}
}
Here I am using the default Laravel 5.6 UserFactory and my own BookFactory
$factory->define(Book::class, function (Faker $faker) {
return [
'title' => $faker->sentence,
'author' => $faker->name,
'user_id' => 1
];
});
$factory->define(Rating::class, function (Faker $faker) {
return [
'user_id' => 1,
'book_id' => mt_rand(1, 2),
'rating' => mt_rand(1, 5)
];
});
When I run the test, it fails and I get 401 instead of 200 which means the user is unauthorized.
I have a feeling that I have probably not set the $user in my test properly to be used during POST but I am not sure and really need help to get it right.
you can send headers in the fourth params of json() method as
$response = $this->json('POST', '/api/books', [
'title' => 'book post',
'author' => 'post author'
],['Authorization' => "Bearer $token"]);
since json method itself has provision to pass headers
or you can use post() method as
$response = $this->post('/api/books', [
'title' => 'book post',
'author' => 'post author'
],['Authorization' => "Bearer $token"]);
Try this instead hope this solves your issues
Not sure how authentication is hooked on your application, but you could try this:
...
$this->actingAs($user)
->jsonPost('/api/books', [
// ...
]);
$response->assertStatus(201);

Laravel Backpack - Set extra attributes before page save

In my PageTemplates.php I have a field like this:
$this->crud->addField([
'name' => 'adres',
'label' => 'Adres',
'type' => 'address',
'fake' => true,
]);
Now I would like to save also the latitude and longitude of the address they give in (if it can be found). I've copied the PageCrudController and changed the config in config/backpack/pagemanager.php to:
return [
'admin_controller_class' => 'App\Http\Controllers\Admin\PageCrudController',
'page_model_class' => 'App\Models\Page',
];
In my store function I have:
public function store(StoreRequest $request)
{
$address = $request->request->get('adres');
$addressObj = app('geocoder')->geocode($address)->get()->first();
if($addressObj)
{
}
$this->addDefaultPageFields(\Request::input('template'));
$this->useTemplate(\Request::input('template'));
return parent::storeCrud();
}
But what do I place in the if statement? How can I add (= set) an extra field to the extras field in my database?
In backpack 4.1, I solved my issue by the following way :
Override the store method in my controller, set my extra field in request and then call the backpack store method
Don't forget to add include backpack trait
Hope the solution will help someone
use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation { store as traitStore; }
public function store()
{
$this->crud->setOperationSetting('saveAllInputsExcept', ['save_action']);
$this->crud->getRequest()->request->add(['updated_by' => backpack_user()->id]);
return $this->traitStore();
}
Fixed it by doing the following:
Add latitude and longitude as hidden fields:
$this->crud->addField([
'name' => 'latitude',
'type' => 'hidden',
'fake' => true,
]);
$this->crud->addField([
'name' => 'longitude',
'type' => 'hidden',
'fake' => true,
]);
Set attributes by doing the following:
if($addressObj)
{
$request['latitude'] = $addressObj->getCoordinates()->getLatitude();
$request['longitude'] = $addressObj->getCoordinates()->getLongitude();
}
}
Change parent::updateCrud to parent::updateCrud($request);.
For people still looking at this issue, I'd recommend you follow the advice in the note under the Callbacks section of Laravel Backpack's docs if you don't just want to observe changes made from the Backpack admin panel, you just need to create an Observable.
To do this you can do the following:
Create an Observer class: php artisan make:observer YourObserver --model=YourModel
Add your code to the generated event methods you wish to observe.
Register the Observer by calling the observe method on the model you wish to observe in your EventServiceProvider's boot method like so:
public function boot()
{
YourModel::observe(YourObserver::class);
}
Or equally you can register the Observer to the $observers property of your applications' EventServiceProvider class:
protected $observers = [
YourModel::class => [YourObserver::class],
];

Minimize factories in test?

Currently I am using many factories factory() in Test class, is there a way to reduce to 1 so I can only use factory(Something::class) in a test method?
Reason I used many because I have to pass some foreign keys.
$user = factory(User::class)->create();
$token = factory(Token::class)->create([
'user_id' => $user->id,
]);
$provider = factory(Provider::class)->create([
'user_id' => $user->id,
'token_id' => $token->id,
]);
$something = factory(Something::class)->create([
'provider_id' => $provider->id,
]);
// Now test with $something
You can use such syntax:
$factory->define(Something::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'provider_id' => function () {
return factory(Provider::class)->create()->id;
}
];
});
$factory->define(Provider::class, function ($faker) {
$user = factory(User:class)->create();
return [
'user_id' => $user->id,
'token_id' => function () {
return factory(Token::class)->create(['user_id' => $user->id])->id;
}
];
});
and then in your tests you can only use:
$something = factory(Something::class)->create();
Be aware depending on your needs it can cause some side effects - for example when using Provider factory, user will be always created what might be fine or not depending on your tests. Of course if needed you can always created helper method that will wrap all those methods you showed and return only something and then in your test you can only use:
$something = $this->createSomething();

Hooks in Laravel 5?

I'm creating a package and want hook functionality (the package should inject some extra validation rules when a user updates a field in my app).
I managed to do this using the event system. What I do is pass the $rules variable and $request into the listener, I modify the $rules variable and return it.
Would this be bad practice? What would be the recommended way of doing it?
I mean, it works. I'm just unsure if this is the best way to go about it.
Code below:
SettingsController.php (this is under App/ and where I'm validating on update)
public function update(Setting $setting, Request $request)
{
$rules = [
'package' => 'required|in:'.implode(config('app.packages'),','),
'name' => 'required|max:255|alpha_dash|not_contains:-|unique:auth_setting,name,'.$setting->id.',id,package,'.$setting->package,
'description' => '',
];
// Is this bad??
$rules = Event::fire(new SettingsWereSubmitted($request,$rules))[0];
$v = Validator::make($request->all(),$rules);
Then in my package (packages/exchange/src/Listeners) I got this listener (ValidateSettings.php):
public function handle(SettingsWereSubmitted $event)
{
if($event->request->package == 'exchange')
{
// Add rules
$rules = [
'fee' => 'required|decimal|min_amount:0|max_amount:1|max_decimal:8',
'freeze_trade' => 'required|in:1,0',
];
$event->rules['value'] = $rules[$event->request->name];
return $event->rules;
}
}
I'm looking at this piece of your code
if($event->request->package == 'exchange')
and think that you can achieve the same behaviour easier by using required_if validation rule.
$rules = [
'package' => 'required|in:'.implode(config('app.packages'),','),
'name' => 'required|max:255|alpha_dash|not_contains:-|unique:auth_setting,name,'.$setting->id.',id,package,'.$setting->package,
'description' => '',
'fee' => 'required_if:package,exchange|decimal|min_amount:0|max_amount:1|max_decimal:8',
'freeze_trade' => 'required_if:package,exchange|in:1,0',
];
ADDED:
By the way, I would suggest using Request classes to validate income requests and remove validation code from controllers because validation of request is responsibility of Request but not Controller.
It's pretty easy in Laravel. First, you create your request class in your Http\Requests folder:
class UpdateSomethingRequest extends Requst
{
public function rules()
{
return [
'package' => 'required|in:'.implode(config('app.packages'),','),
'name' => 'required|max:255|alpha_dash|not_contains:-|unique:auth_setting,name,'.$setting->id.',id,package,'.$setting->package,
'description' => '',
'fee' => 'required_if:package,exchange|decimal|min_amount:0|max_amount:1|max_decimal:8',
'freeze_trade' => 'required_if:package,exchange|in:1,0',
];
}
}
And then just remove that code from you Controller and type-hint new request class to update method like following:
public function update(Setting $setting, UpdateSomethingRequest $request)
{
// Your request is already validated here so no need to do validation again
}

Categories