I have tried to replace laravel/ui into livewire component.
RegisterController.php and ResetPasswordController.php are the target to replace.
Laravel version is 10.21.0 and Livewire version is v3.0.5.
First of all, following is the partial of route/web.php:
Auth::routes([
'login' => true, 'logout' => true, 'confirm' => true, 'verify' => true,
]);
Route::middleware('guest')->group(function () {
Route::get('register', AppLivewireAuthRegisterComponent::class)
->name('register');
Route::post('register', [AppLivewireAuthRegisterComponent::class, 'register']);
Route::get('password/reset/{token}', AppLivewireAuthResetPasswordComponent::class)
->name('password.reset');
Route::post('password/reset', [AppLivewireAuthResetPasswordComponent::class, 'update'])
->name('password.update');
});
AppLivewireAuthRegisterComponent.php is based from IlluminateFoundationAuthRegistersUsers.php.
The change is that I need to validate $name, $email and $password instead of $request as follows:
<?php
namespace AppLivewireAuth;
use ...
class RegisterComponent extends Component
{
use RedirectsUsers;
public string $title = '';
public string $name = '';
public string $email = '';
public string $password = '';
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/email/verify';
/**
* Handle a registration request for the application.
*
* @param Request $request
*
* @return Application|IlluminateFoundationApplication|RedirectResponse|Redirector
*
* @throws ValidationException
*/
public function register(Request $request)
{
$this->validator()->validate();
event(new Registered($user = $this->create()));
$this->guard()->login($user);
return redirect($this->redirectPath());
}
/**
* Get the password reset validation rules.
*
* @return array
*/
public function rules(): array
{
return [
User::NAME => ['required','string','max:255'],
User::EMAIL => ['required','string','email','max:255','unique:users'],
User::PASSWORD => ['required',Password::min(12)->letters()->mixedCase()->numbers()->symbols()->uncompromised(),'zxcvbn:3'],
];
}
public function render(): View
{
return view('livewire.auth.register-component')
->layout('components.layouts.auth');
}
public function mount(): void
{
$this->title = config('app.name') . ' - ' . 'Registration';
}
/**
* Get a validator for an incoming registration request.
*/
protected function validator(): IlluminateContractsValidationValidator
{
return Validator::make([
User::NAME => $this->name,
User::EMAIL => $this->email,
User::PASSWORD => $this->password,
], $this->rules());
}
/**
* Get the guard to be used during registration.
*
* @return StatefulGuard
*/
protected function guard(): StatefulGuard
{
return Auth::guard();
}
/**
* The user has been registered.
*
* @param mixed $user
* @return mixed
*/
protected function registered($user)
{
//
}
/**
* Create a new user instance after a valid registration.
*/
protected function create(): User
{
return User::create([
User::NAME => $this->name,
User::EMAIL => $this->email,
User::PASSWORD => Hash::make($this->password),
User::API_TOKEN => Str::random(80),
User::ROLE_ID => RoleType::USER,
]);
}
}
And the part of view corresponding with this component is as follows:
<form wire:submit="register">
<div class="form-group row py-2">
<label for="name" class="col-md-4 col-form-label text-md-end">{{ __('Name') }}</label>
<div class="col-md-6">
<input wire:model="name" required autofocus type="text" class="form-control @error('name') is-invalid @enderror" id="name" autocomplete="name" />
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row py-2">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input wire:model="email" required type="email" class="form-control @error('email') is-invalid @enderror" id="email" autocomplete="email" />
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row py-2">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6 input-group">
<input wire:model="password" required type="password" class="form-control @error('password') is-invalid @enderror" id="password" autocomplete="current_password" />
<span class="mt-1 small">
{{ __('application.user.columns.password.note') }}
</span>
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row py-2">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-outline-warning">
{{ __('Sign up') }}
</button>
</div>
</div>
</form>
AppLivewireAuthResetPasswordComponent.php is also based from IlluminateFoundationAuthResetsPasswords.php. This is also to validate $token, $email and $password instead of $request, too.
mount() method try to solve $token from the route.
<?php
namespace AppLivewireAuth;
use ...
class ResetPasswordComponent extends Component
{
use RedirectsUsers;
use RedirectByStatus;
public const TOKEN = 'token';
public string $token = '';
public string $email = '';
public string $password = '';
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = '/home';
public function update(Request $request)
{
$validated = $this->validator()->validate();
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials(), function ($user, $password) {
$this->resetPassword($user, $password);
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $response == IlluminateSupportFacadesPassword::PASSWORD_RESET
? $this->sendResetResponse($request, $response)
: $this->sendResetFailedResponse($request, $response);
}
/**
* Get the password reset validation rules.
*
* @return array
*/
public function rules(): array
{
return [
self::TOKEN => ['required'],
User::EMAIL => ['required','email'],
User::PASSWORD => ['required',Password::min(12)->letters()->mixedCase()->numbers()->symbols()->uncompromised(),'zxcvbn:3'],
];
}
/**
* Get the broker to be used during password reset.
*
* @return PasswordBroker
*/
public function broker()
{
return IlluminateSupportFacadesPassword::broker();
}
public function render(): View
{
return view('livewire.auth.reset-password-component')
->layout('components.layouts.auth');
}
public function mount(string $token): void
{
$this->token = $token;
$this->title = config('app.name') . ' - ' . 'Reset Password';
}
/**
* Get a validator for an incoming registration request.
*/
protected function validator(): IlluminateContractsValidationValidator
{
return Validator::make([
self::TOKEN => $this->token,
User::EMAIL => $this->email,
User::PASSWORD => $this->password,
], $this->rules());
}
/**
* Get the password reset validation error messages.
*
* @return array
*/
protected function validationErrorMessages()
{
return [];
}
/**
* Get the password reset credentials from the request.
*
* @param Request $request
* @return array
*/
protected function credentials()
{
return [
self::TOKEN => $this->token,
User::EMAIL => $this->email,
User::PASSWORD => $this->password,
];
}
/**
* Reset the given user's password.
*
* @param IlluminateContractsAuthCanResetPassword $user
* @param string $password
* @return void
*/
protected function resetPassword($user, $password)
{
$this->setUserPassword($user, $password);
$user->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
$this->guard()->login($user);
}
/**
* Set the user's password.
*
* @param IlluminateContractsAuthCanResetPassword $user
* @param string $password
* @return void
*/
protected function setUserPassword($user, $password)
{
$user->password = Hash::make($password);
}
/**
* Get the response for a successful password reset.
*
* @param Request $request
* @param string $response
* @return RedirectResponse|IlluminateHttpJsonResponse
*/
protected function sendResetResponse(Request $request, $response)
{
return redirect($this->redirectPath())
->with('status', trans($response));
}
/**
* Get the response for a failed password reset.
*
* @param Request $request
* @param string $response
* @return RedirectResponse
*/
protected function sendResetFailedResponse(Request $request, $response)
{
return redirect()->back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
/**
* Get the guard to be used during password reset.
*
* @return StatefulGuard
*/
protected function guard()
{
return Auth::guard();
}
}
And the part of view corresponding with this component is as follows:
<form wire:submit="update">
<div class="form-group row">
<div class="col-md-6">
<input wire:model="token" type="hidden" class="form-control @error('token') is-invalid @enderror" id="token" />
@error('token')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row py-2">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input wire:model="email" required autofocus type="email" class="form-control @error('email') is-invalid @enderror" id="email" autocomplete="email" />
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row py-2">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6 input-group">
<input wire:model="password" required type="password" class="form-control @error('password') is-invalid @enderror" id="password" autocomplete="current_password" />
<span class="mt-1 small">
{{ __('application.user.columns.password.note') }}
</span>
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row py-2 mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Reset Password') }}
</button>
</div>
</div>
</form>
I also wrote a test for these components.
RegisterComponentTest.php is for RegisterComponent.php.
<?php
namespace FeatureLivewireAuth;
use AppLivewireAuthRegisterComponent;
use AppModelsUser;
use LivewireLivewire;
use TestsTestCase;
class RegisterComponentTest extends TestCase
{
/**
* @test
*/
public function renders_successfully()
{
Livewire::test(RegisterComponent::class)
->assertStatus(200);
}
}
And ResetPasswordComponentTest.php is for ResetPasswordComponent.php.
<?php
namespace FeatureLivewireAuth;
use AppLivewireAuthResetPasswordComponent;
use AppModelsUser;
use LivewireLivewire;
use TestsTestCase;
class ResetPasswordComponentTest extends TestCase
{
/**
* @test
*/
public function renders_successfully()
{
$token = 'token';
Livewire::test(ResetPasswordComponent::class)
->call('mount', $token)
// ->set('token', $token)
->assertStatus(200);
}
}
Finally everything is on the table.
When I ran ./vendor/bin/phpunit tests/Feature/Livewire/Auth/RegisterComponentTest.php, all the test passed but when I ran ./vendor/bin/phpunit tests/Feature/Livewire/Auth/ResetPasswordComponentTest.php, the following error was thrown.
There was 1 error:
1) FeatureLivewireAuthResetPasswordComponentTest::renders_successfully
IlluminateViewViewException: Unable to resolve dependency [Parameter #0 [ <required> string $token ]] in class AppLivewireAuthResetPasswordComponent (View: /var/www/html/storage/framework/views/a168b0ebcff266df6b662fc4b6d06e88.blade.php)
/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:188
/var/www/html/vendor/livewire/livewire/src/Mechanisms/ExtendBlade/ExtendedCompilerEngine.php:60
/var/www/html/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php:60
/var/www/html/vendor/livewire/livewire/src/Mechanisms/ExtendBlade/ExtendedCompilerEngine.php:22
/var/www/html/vendor/laravel/framework/src/Illuminate/View/Engines/CompilerEngine.php:72
/var/www/html/vendor/livewire/livewire/src/Mechanisms/ExtendBlade/ExtendedCompilerEngine.php:10
/var/www/html/vendor/laravel/framework/src/Illuminate/View/View.php:195
/var/www/html/vendor/laravel/framework/src/Illuminate/View/View.php:178
/var/www/html/vendor/laravel/framework/src/Illuminate/View/View.php:147
/var/www/html/vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php:333
/var/www/html/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:353
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:26
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/CallableDispatcher.php:40
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php:237
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php:208
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:799
/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:141
/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:116
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:800
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:777
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:741
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:730
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:200
/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:141
/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:116
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:175
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:144
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:564
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:32
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/RequestBroker.php:30
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:33
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/Render.php:24
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:34
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:17
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/Testable.php:35
/var/www/html/vendor/livewire/livewire/src/LivewireManager.php:157
/var/www/html/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:353
/var/www/html/tests/Feature/Livewire/Auth/ResetPasswordComponentTest.php:19
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:174
Caused by
IlluminateContractsContainerBindingResolutionException: Unable to resolve dependency [Parameter #0 [ <required> string $token ]] in class AppLivewireAuthResetPasswordComponent
/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:188
/var/www/html/vendor/livewire/livewire/src/ImplicitlyBoundMethod.php:21
/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:36
/var/www/html/vendor/laravel/framework/src/Illuminate/Container/Util.php:41
/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:93
/var/www/html/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:37
/var/www/html/vendor/livewire/livewire/src/Wrapped.php:23
/var/www/html/vendor/livewire/livewire/src/Features/SupportLifecycleHooks/SupportLifecycleHooks.php:125
/var/www/html/vendor/livewire/livewire/src/Features/SupportLifecycleHooks/SupportLifecycleHooks.php:20
/var/www/html/vendor/livewire/livewire/src/ComponentHook.php:19
/var/www/html/vendor/livewire/livewire/src/ComponentHookRegistry.php:45
/var/www/html/vendor/livewire/livewire/src/EventBus.php:60
/var/www/html/vendor/livewire/livewire/src/helpers.php:100
/var/www/html/vendor/livewire/livewire/src/Mechanisms/HandleComponents/HandleComponents.php:57
/var/www/html/vendor/livewire/livewire/src/LivewireManager.php:72
/var/www/html/storage/framework/views/d76adc34f3e5651d58b1e86454689044.php:7
/var/www/html/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php:124
/var/www/html/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php:125
/var/www/html/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php:58
/var/www/html/vendor/livewire/livewire/src/Mechanisms/ExtendBlade/ExtendedCompilerEngine.php:22
/var/www/html/vendor/laravel/framework/src/Illuminate/View/Engines/CompilerEngine.php:72
/var/www/html/vendor/livewire/livewire/src/Mechanisms/ExtendBlade/ExtendedCompilerEngine.php:10
/var/www/html/vendor/laravel/framework/src/Illuminate/View/View.php:195
/var/www/html/vendor/laravel/framework/src/Illuminate/View/View.php:178
/var/www/html/vendor/laravel/framework/src/Illuminate/View/View.php:147
/var/www/html/vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php:333
/var/www/html/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:353
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:26
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/CallableDispatcher.php:40
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php:237
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php:208
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:799
/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:141
/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:116
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:800
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:777
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:741
/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php:730
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:200
/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:141
/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:116
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:175
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:144
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:564
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:32
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/RequestBroker.php:30
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:33
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/Render.php:24
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:34
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/InitialRender.php:17
/var/www/html/vendor/livewire/livewire/src/Features/SupportTesting/Testable.php:35
/var/www/html/vendor/livewire/livewire/src/LivewireManager.php:157
/var/www/html/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:353
/var/www/html/tests/Feature/Livewire/Auth/ResetPasswordComponentTest.php:19
/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:174
As you see, RegisterComponent.php and ResetPasswordComponent.php are very similar but I don’t know why RegisterComponentTest.php passes but ResetPasswordComponentTest.php fails.
Can anyone let me know what is the cause of error and how I should fix it.
Thanks in advance.
2
Answers
The "Unable to resolve dependency" error in Laravel Livewire typically occurs when Livewire cannot automatically resolve a dependency for a component. This often happens when you are using dependency injection in your Livewire component’s constructor or when Livewire cannot determine the type or value of a property.
Here are some common scenarios and solutions to resolve this error:
https://askmequora.com/posts/how-to-solve-laravel-livewire-error-of-illuminate-view-viewexception-unable-to-resolve-dependency
When I look at the docs it doesn’t seem like you should be calling the
mount
method viacall
, instead you should be passing data when initially setting up the test component: