Laravel/Octane: How to reset route controllers' state - laravel

In Laravel v9/Octane/Swoole, I do have private properties in route controllers, e.g.
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
class SignupController extends Controller
{
/** #var ?\App\SignupCode A verification code object */
protected $code;
It looks like the property is "shared" between requests under Octane. I have more controllers like this. How do I make sure the controller state gets reset on every request? I've read the whole Octane documentation a few times, and it's still unclear how to do that.

I solved it by created listener
<?php
namespace App\Listeners;
use Illuminate\Routing\Router;
class ResetControllerState
{
/**
* Handle the event.
*
* #param mixed $event
* #return void
*/
public function handle($event): void
{
/** #var Router $router */
$router = $event->sandbox->make(Router::class);
$currentRoute = $router->current();
if($currentRoute && $currentRoute->controller)
$currentRoute->controller = null;
}
}
and add it to array of listeners in octane config
RequestReceived::class => [
...Octane::prepareApplicationForNextOperation(),
...Octane::prepareApplicationForNextRequest(),
\App\Listeners\ResetControllerState::class
//
],
I do not know what the consequences may be, but so far it works well.

Related

Unit Testing Events in Laravel 8 throw EventFake::__construct() must implement interface Illuminate\Contracts\Events\Dispatcher, null given

I'm trying to fake an event using Unit Testing, so no DB, no Application, just pure classes.
I'm following the documentation for Laravel 8, my current version, but I'm still getting the error
1) Tests\Unit\HandShake\ConfirmApplicationHandShakeActionTest::BasedOnAnExistentAppIConfirmTheHandShake
TypeError: Argument 1 passed to Illuminate\Support\Testing\Fakes\EventFake::__construct() must implement interface Illuminate\Contracts\Events\Dispatcher, null given, called in /Users/pablo/Workspace/xxxx/vendor/laravel/framework/src/Illuminate/Support/Facades/Event.php on line 38
As you can see is asking for an object implementing the interface Dispatch, my Event has the trait dispatch, but anyways here it says that I'm passing null
My Test code is the one as follow
<?php
namespace Tests\Unit\HandShake;
use App\Actions\ConfirmApplicationHandShakeAction;
use App\Events\HandShakeReceivedEvent;
use App\Exceptions\NotFoundApplicationException;
use App\Models\Application;
use Illuminate\Support\Facades\Event;
use Mockery\MockInterface;
use Mockery;
use PHPUnit\Framework\TestCase;
class ConfirmApplicationHandShakeActionTest extends TestCase
{
/**
*
* #test
* #throws NotFoundApplicationException
*/
public function BasedOnAnExistentAppIConfirmTheHandShake()
{
$appName = 'testDummy';
Event::fake([HandShakeReceivedEvent::class]);
$applicationModel = Mockery::mock(Application::class, function (MockInterface $mock) {
$mock->shouldReceive('exists')
->andReturn(true)
->once();
});
$confirmation = new ConfirmApplicationHandShakeAction($applicationModel);
$confirmation->execute($appName);
The Class that I'm testing is
<?php
declare(strict_types=1);
namespace App\Actions;
use App\Events\HandShakeReceivedEvent;
use App\Exceptions\NotFoundApplicationException;
use App\Models\Application;
/**
* Class ConfirmApplicationHandShakeAction
* #package App\Actions
*/
class ConfirmApplicationHandShakeAction
{
/**
* #var Application
*/
private $application;
/**
* ConfirmApplicationHandShakeAction constructor.
* #param Application $application
*/
public function __construct(Application $application)
{
$this->application = $application;
}
/**
* #param string $appName
* #throws NotFoundApplicationException
*/
public function execute(string $appName)
{
if ( ! $this->application->exists($appName)) {
throw new NotFoundApplicationException('The Application do not exists or is not active');
}
HandShakeReceivedEvent::dispatch($appName);
}
}
Lastly the event is
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class HandShakeReceivedEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
Probably is one of those moments where I'm missing a stupid character, but I went through this error, and I cannot find the right solution.
UPDATE
I keep digging and I found that the getFacadeRoot() is returning null, so I'm assuming that something is not properly initialized, still not clue
static::swap($fake = new EventFake(static::getFacadeRoot(), $eventsToFake));
Thank you in advance if you find the issue or can point me in the right direction. I also tried FakeFor and Fake without sending the event as a parameter
Problem solved
When you create the test files with artisan, for some reasons the PHPUnit\Framework\TestCase they use is the one in PHPUnit, but they should use the one in Laravel Tests\TestCase
Extending your tests with Tests\TestCase will sort the problem

Eloquent how to do something when delete or update operate on a special model

Most of my db table contain create_user_id and update_user_id
How can l update this two field automatic when l use save(), update(), insert(), createOrUpdate() and etc method.
For example, l execute this script:
$model = Model::find(1);
$model->model_f = 'update';
$model->save();
then this record's model_f updated, and update_user_id updated, too.
l know eloquent can manage update_time automatic and l have use it already. But l want to do something else when update or insert or delete
PS: l have a constant named USERID to remember current user's id
You could make use of Observers.
You can hook to the following events on your Model:
retrieved
creating
created
updating
updated
saving
saved
deleting
deleted
restoring
restored
Let me give you an example where we are trying to hook into the events emitted by the App/User model. You can change this to match your particular Model later on.
To create an observer, run the following command:
php artisan make:observer UserObserver --model=User
Then you can hook to specific events in your observer.
<?php
namespace App\Observers;
use App\User;
class UserObserver
{
/**
* Handle the User "saved" event.
*
* #param \App\User $user
* #return void
*/
public function saved(User $user)
{
//
}
/**
* Handle the User "created" event.
*
* #param \App\User $user
* #return void
*/
public function created(User $user)
{
//
}
/**
* Handle the User "updated" event.
*
* #param \App\User $user
* #return void
*/
public function updated(User $user)
{
//
}
}
Since, in your particular case, you want to hook into these 3 events, you can define the events above and perform additional operations to your model when those events are called.
Don't forget to register this observer in your AppServiceProvider.
<?php
namespace App\Providers;
use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
User::observe(UserObserver::class);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}
There is pretty simple way to automatically update the create_user_id and update_user_id
Step1:
Open you app folder and create the new file named as UserStampsTrait.php
Step:2
and paste the following code
<?php
namespace App;
use Illuminate\Support\Facades\Auth;
trait UserStampsTrait
{
public static function boot()
{
parent::boot();
// first we tell the model what to do on a creating event
static::creating(function($modelName='')
{
$createdByColumnName = 'create_user_id ';
$modelName->$createdByColumnName = Auth::id();
});
// // then we tell the model what to do on an updating event
static::updating(function($modelName='')
{
$updatedByColumnName = 'update_user_id';
$modelName->$updatedByColumnName = Auth::id();
});
}
}
Thats it
Step:3
Open you model which needs to updated the corresponding models automatically
for Example it may be Post
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\UserStampsTrait;
class Post extends Model
{
use UserStampsTrait;
}
Thats it

Laravel Testing Request - Service Provider - Middleware Issue

I have a Laravel 5.5 App where I have a Service Provider which I use to put some stuff in the request->attributes to access it everywhere (simplified):
namespace App\Providers;
use App\Models\Domain;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
class GlobalVarsServiceProvider extends ServiceProvider
{
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
/**
* Bootstrap the application services.
*
* #param Request $request
*
* #return void
*/
public function boot(Request $request)
{
$domain = .. get domain with language and some logic and cache because of multiple domains ..
$request->attributes->add(['domain' => $domain]);
}
}
I do this in a Service Provider, because then I can already use it in other Service Providers like my ViewComposerServiceProvider, where I compose some stuff for the Views. I'm able to access $domain everywhere like this:
$this->domain = $request->attributes->get('domain');
It works great. BUT not in testing. When I want to access $domain in a Unit Test in a middleware the $request->attributes are empty (In UnitTests as in DuskTests either).
It looks like the testing environment uses a different Request Lifecycle? If yes, what else is different in the testing environment?
What am I doing wrong?
-- Edit --
Test Example:
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function testBasicTest()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
TestCase uses trait MakesHttpRequests which has method call. When you use get method in your tests, it's simply a shortcut to this.
In your test you can use it like this:
$this->call('GET', '/url/here', $yourRequestParametersHere);

Laravel 5.4: Passing a variable via Request to controller

Generally speaking this should be a rather simple problem. IT should be very similar to the following question on Stack Overflow
But seeing as it has been two years, maybe some of the syntax has changed.
All I want to do is pass a variable from the middleware to the controller, so I'm not duplicating mysql queries.
Here is my middleware:
namespace App\Http\Middleware;
use Closure;
class CheckRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$id = $request->user()->id;
$rr = $request->user()->isSuperAdmin();
if ($request->user()->isSuperAdmin()) {
$request->merge(['group' => 123]);
return $next($request);
}
echo "not admin";
}
}
So the middleware works fine and if I DD($request) on the middleware I see my group => 123 on the page. (Right now it's 123 for the sake of simplicity.)
So I want to pass it to my AdminController:
<?php
namespace SleepingOwl\Admin\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use SleepingOwl\Admin\Form\FormElements;
use SleepingOwl\Admin\Form\Columns\Column;
use SleepingOwl\Admin\Display\DisplayTable;
use Illuminate\Contracts\Support\Renderable;
use SleepingOwl\Admin\Display\DisplayTabbed;
use Illuminate\Validation\ValidationException;
use SleepingOwl\Admin\Contracts\AdminInterface;
use SleepingOwl\Admin\Model\ModelConfiguration;
use Illuminate\Contracts\Foundation\Application;
use SleepingOwl\Admin\Contracts\Form\FormInterface;
use SleepingOwl\Admin\Contracts\ModelConfigurationInterface;
use SleepingOwl\Admin\Contracts\Display\ColumnEditableInterface;
class AdminController extends Controller
{
/**
* #var \DaveJamesMiller\Breadcrumbs\Manager
*/
protected $breadcrumbs;
/**
* #var AdminInterface
*/
protected $admin;
/**
* #var
*/
private $parentBreadcrumb = 'home';
/**
* #var Application
*/
public $app;
/**
* AdminController constructor.
*
* #param Request $request
* #param AdminInterface $admin
* #param Application $application
*/
public function __construct(Request $request, AdminInterface $admin, Application $application)
{
$this->middleware('CheckRole');
So as you can see I call the middleware on this constructor. After calling it I should be able do something like:
$request->get('group'); or $request->group;
After trying for quite a while nothing seems to be working and I keep getting a null value. Fundamentally, this shouldn't be terribly difficult, but I seem to have my syntax off or not using the right name spaces?
Instead of this code line:
$request->merge(['group' => 123]);
You can try:
$request->request->add(['group' => 123]);
What this code line will do is if a parameter named group exists in the $request it will overwrite with the new value, otherwise it will add a new parameter group to the $request
In your controller, you can get the value of group parameter as:
$group = $request->group; OR $group = $request->input('group');
Thanks to the joint help of #Rahul-Gupta and #shock_gone_wild. It was a joint effort I guess.
The first issue is that I'm using sleepingOwl laravel boilerplate. Probably not the best idea for someone new to Laravel. (not new to MVC / PHP).
Based on #shock_gone_wild comment, decide move my test over to a simple controller, and not the sleeping owl nonsense. (they have a lot of code.) Anyways, I believe that helped. I did leave the middleware in the constructor because I didn't apply the middleware to the routes.
Then I followed #Rahul-Gupta syntax.
So here is final result, hopefully this will save someone sometime someday...
namespace App\Http\Middleware;
use Closure;
class CheckRole {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next) {
if ($request->user()->isSuperAdmin()) {
$request->request->add(['group' => 123]);
return $next($request);
} else {
echo "not admin";
}
}
}
Then here is the simple controller.
use Illuminate\Http\Request;
use App\task;
use App\User;
use App\HasRoles;
class TaskController extends Controller {
public function __construct() {
// constructor code...
$this->middleware('auth');
$this->middleware('CheckRole');
}
public function index(Request $request) {
$group = $request->input('group');
echo "---->" . $group;
$tasks = Task::all();
return view('test_task', compact('tasks'));
}
}

Problems creating a ComposerServiceProvider in Laravel 5.4

Im working on a Laravel 5 app and im trying to set up a ComposerServiceProvider to pass data to a couple of views (im trying now to add it to the layout/app.blade.php).
I did this following the documentation but the data im trying to add is still undefined..
In my config/app.php I added to the providers:
App\Providers\ComposerServiceProvider::class,
On ComposerServiceProvider.php
boot method:
View::composer(['layouts.app'], 'App\ViewComposers\LayoutAppComposer');
On the new created LayoutAppComposer.php
compose(View $view) method:
$metaTitle = 'MetaTitle';
$view->with('metaTitle', $metaTitle)
But When i access the url I still get:
Undefined variable: metaTitle (View: .../resources/views/layouts/app.blade.php)
Am I missing something here??
ServiceProvider
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
View::composer(['layouts.app'], 'App\ViewComposers\LayoutAppComposer');
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
}
}
LayoutAppComposer
<?php
namespace App\ViewComposers;
use Illuminate\Support\Facades\Session;
use Illuminate\View\View;
class LayoutAppComposer {
protected $metaTitle;
public function __construct($metaTitle)
{
$this->metaTitle = $metaTitle;
}
/**
* Bind data to the view.
*
* #param View $view
* #return void
*/
public function compose(View $view) {
$this->metaTitle = 'MetaTitle';
$view->with('metaTitle', $this->metaTitle);
}
}
Try changing:
$metaTitle = 'MetaTitle';
$view->with('metaTitle', $metaTitle)
to
$this->metaTitle = 'metaTitle';
$view->with('metaTitle', $this->metaTitle)
setup $this->metaTitle as a protected class member and assign it in the composer constructor. it may be that $metaTitle is getting garbage collected before you use it since this being resolved at the service provider level.
Since you're registering the composer with your app layout, instead you may need to use the wildcard character in place of app.layout like such:
View::composer('*', function ($view) {
//
});
To resolve $metaTile for the View Composer, try binding in your AppServiceProvider:
$this->app->bind('metaTitle', 'the string i want displayed across all views');

Resources