Move logic from Controller to Service class - php

I'm working on a simple login page which works already, but I wanna experiment with Service classes and how they work.
How would I be able to properly move my logic from my controller to a service class, and call that service class in my controller?
AuthController:
public function postLogin(Request $request)
{
$request->validate([
'email' => 'required',
'password' => 'required',
]);
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
return redirect()->intended('dashboard')
->withSuccess('You have successfully logged in');
}
return redirect("login")
->withSuccess('Oops! You have entered invalid credentials'); // MIGHT CHANGE TO withSuccess
}
I've made a UserService.php file under App\Services
I've tried pasting the function into my Service class, then calling it in the controller with
public function postLogin(Request $request)
{
(new UserService())->postLogin($request->validated());
}

Related

How to override the validation rule login Laravel\Fortify?

This described in the class Laravel\Fortify\Http\Requests\LoginRequest
I want to add one more validation line
namespace Laravel\Fortify\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Laravel\Fortify\Fortify;
class LoginRequest extends FormRequest
{
/..
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
];
}
}
But I cannot do the customizations in the vendor.
My question is how to do this?
Just to add to the comment above, it's a bad idea to make changes in the vendor folder as stated earlier. For one, any code pushes to a repository will not reflect these changes (unless you modify the ignore file).
For Laravel/Fortify adding new fields and changing the default validation rules, even the password requirements is very straightforward. It's not clear to me what your requirement is, but it might be easier to simply use a Validator. That is what Fortify uses as well. For example, Fortify publishes two files:
App\Actions\Fortify\CreateNewUser.php
App\Actions\Fortify\PasswordValidationRules.php
To add a new validation rule for a field, simply add it the CreateNewUser.php under the Validator::make method that Fortify itself is using. You can follow the same logic in your custom implementation. For example to add a firstname field, modify as follows:
Validator::make($input, [
'firstname' => ['required', 'string', 'max:255'],
])->validate();
return User::create([
'firstname' => $input['firstname'],
]);
You can add as many field as you want. To change the password requirements, make changes to the passwordRules() function in the PasswordValidationRules.php file as follows:
protected function passwordRules()
{
return ['required',
'string',
(new Password)->requireUppercase()
->length(10)
->requireNumeric()
->requireSpecialCharacter(),
'confirmed'];
}
All this info can be found at the official docs https://jetstream.laravel.com/1.x/features/authentication.html#password-validation-rules
In short, I solved the problem like this
copy vendor\laravel\fortify\src\Http\Controllers\AuthenticatedSessionController.php to
app\Http\Controllers\Auth\LoginController.php (change namespace and class name)
copy vendor\laravel\fortify\src\Http\Requests\LoginRequest.php to app\Http\Requests\LoginFormRequest.php (change namespace and class name)
add new route in routes/web.php
use App\Http\Controllers\Auth\LoginController;
//
Route::post('/login', [LoginController::class, 'store'])
->middleware(array_filter([
'guest',
$limiter ? 'throttle:'.$limiter : null,
]));
in LoginController changed LoginRequest to LoginFormRequest and
public function store(LoginFormRequest $request)
{
return $this->loginPipeline($request)->then(function ($request) {
return app(LoginResponse::class);
});
}
in LoginFormRequest add my new rule(s)
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
'myNewRule' => 'required|string',
];
}
Here's how you can use your own validation rules when authenticating with Fortify:
Create a file LoginRequest.php in App\Http\Requests that extends Fortify's LoginRequest.php class
I would just copy that class and update the namespace:
namespace App\Http\Requests;
use Laravel\Fortify\Http\Requests\LoginRequest as FortifyLoginRequest;
use Laravel\Fortify\Fortify;
class LoginRequest extends FortifyLoginRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
'myAttribute' => 'rules' // Customizations...
];
}
}
Add a binding in your AppServiceProviders.php boot method to resolve Fortify's class with your own:
public function boot()
{
$this->app->bind('Laravel\Fortify\Http\Requests\LoginRequest', \App\Http\Requests\LoginRequest::class);
}
I've found that page: https://dev.to/aibnuhibban/login-customization-in-laravel-8-2gc8
Go to vendor > laravel > fortify > src > Rule > Password.php
There you can change those config.

Laravel Gates and/or Policies

Right now Im thinking about implementing policies into my Project.
So I want 3 types of roles: User, Admin and Office.
I have a Customer class which has the following CRUD functions:
class CustomerController extends Controller
{
public function getAllCustomer()
{
return Customer::get();
}
public function addNewCustomer(Request $request)
{
$validatedData = $request->validate([
'Title' => 'required',
'Name' => 'required|max:255',
'Surname' => 'required|max:255',
'Email' => 'required',
'Phone' => 'required',
'Password' => 'required',
'dateofBirth' => 'required'
]);
return \app\model\Customer::create($request->all());
}
public function update (Request $request , Customer $id)
{
$id->update($request->all());
}
public function destroy (Customer $id)
{
$id->delete();
}
What I want is that only the Admin should be able to use the CRUD functions for all the Customers. A User should only be able to use CRUD functions on his own data (so the Customer can only do it with his own data).
I read this article:
https://code.tutsplus.com/tutorials/gates-and-policies-in-laravel--cms-29780
But I still don't know exactly how to implement it in my Project, and whether I should use Gates or policies (it seems like policies are easier, or?).
I would be very thankful if somebody could write me an example on how to implement it on my work.
Create policies for each of your model. Bind policies to routes https://laravel.com/docs/5.6/authorization#via-middleware
Route::post('/post/{post}', function () {
// The current user may edit post...
})->middleware('can:edit,post');
Create a parent policy which all the other policies will extend and add to it before method which will basically check if your user is admin and if so no further checking is needed.
public function before(User $authenticatedUser, $ability)
{
// super admin can access everything
if ($authenticatedUser->role == 'super_admin') {
return true;
}
return null; //fall through to the policy method
}
Create a Post policy which will have edit permission check
class PostPolicy extends Policy
{
public function edit(User $authenticatedUser, Post $post)
{
return $authenticatedUser->id == $post->author_id;
}
}
The general idea, on how to work with policies is as simple as that.

Auth::viaRemember() doesnot work (means always return false even after successful login) in laravel 5.4

I make use of php artisan make:auth in laravel 5.4, as we know laravel default login only supports email so, I put login() in AuthController like below (login working)-
public function login(Request $request)
{
$remember = ($request->input('remember')=='1')?true:false;
// this will remember the user as given in laravel documentation.
if (Auth::attempt(['user_name' => $request->user_name, 'password' => $request->password],$remember)) {
return redirect()->intended('admin');
}
elseif (Auth::attempt(['email'=> $request->user_name, 'password' => $request->password],$remember)) {
return redirect()->intended('admin');
}
else {
return redirect('/login')->with(['error'=>'Invalid user name and password'])
->withInput();
}
}
Now, whenever session expired simply typing url should redirect to admin/dashboard, But couldnot do so... By default it redirects to login page.
I put Auth::viaRemember() in constructor of AuthContoller class before checking for middleware like below-
public function __construct()
{
// login with remember me before checking whether its logged in or not
Auth::viaRemember();
$this->middleware($this->guestMiddleware(), ['except' => 'logout']);
}
What i follow in stack overflow -
Via Remember me always return false -this link is similar to mine
viaRemember not work - laravel
Laravel 5 viaRemember() always false
Laravel: Implement Auth::viaRemember() across the website
protected function attemptLogin(Request $request)
{
// dd($request->has('remember'));
$remember =$request->has('remember');
return $this->guard()->attempt(
$this->credentials($request), $remember
);
}
use this in AuthenticateUsers.php it worked for me
public function __construct()
{
Auth::viaRemember()
Auth::check();
Auth::viaRemember();
$this->middleware($this->guestMiddleware(), ['except' => 'logout']);
}
try this

404 Issue in Laravel API when required param value not passed

Request class
class LoginRequest extends Request
{
public function authorize() {
return true;
}
public function rules() {
return [
'EmailAddress' => 'required',
'Password' => 'required',
];
}
public function messages() {
return [
"EmailAddress.required" => trans("login.RequiredEmailAddress"),
"Password.required" => trans("login.RequiredPassword")
];
}
}
Route
Route::post('/AuthenticateUser',
array(
'uses' => 'API\Login\apiLoginController#AuthenticateUser',
'as' => 'AuthenticateUser'
)
);
Controller Action Method
I have a controller, I did so far for request class only to validate the input parameters. below is the action method
public function AuthenticateUser(LoginRequest $request) {
dd("Hello");
}
Url
localhost:85/Laravel/public/api/v1/AuthenticateUser
I am using Postman Chrome extension to test the Url. So, as we can see that in the Request class both Email Address and the password are required parameters.
When I pass both parameters value. there is not issue and everything works. When I keep the Email Address value empty...I got 404 error and here is the screenshot.
Am I missing something to get rid of 404 error when Email address is not given? I am expecting an error message to enter Email Address
Below is the working state when I pass both email and password
Solution 1:
I managed to get rid of the 404 and return a 422 by adding the following header in the request:
accept:application/json
This is not really a bug in Laravel as Taylor pointed out but a way to differentiate if it is an AJAX/API request or not.
Solution 2:
Alternatively, if you don't want the client to specify that header, you can create a middleware that will add the header accept:application/json on every API requests. Here's how:
Create a new middleware: app/Http/Middleware/ForceJsonResponse.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ForceJsonResponse
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
In /app/Http/Kernel.php, inside $middlewareGroups.api, specify the namespace to your newly created middleware:
protected $middlewareGroups = [
'web' => [...],
'api' => [
[...]
\App\Http\Middleware\ForceJsonResponse::class,
],
];
Finally got it working by changing the request class like below.
class LoginRequest extends Request
{
public function wantsJson() {
return true;
}
public function authorize() {
return true;
}
public function rules() {
return [
'EmailAddress' => 'required',
'Password' => 'required',
];
}
public function messages() {
return [
"EmailAddress.required" => trans("login.RequiredEmailAddress"),
"Password.required" => trans("login.RequiredPassword")
];
}
}
just added below code.
public function wantsJson() {
return true;
}
It is because you are validating directly on route handling and not matching throughs NotFoundException. You need to pass the Request to your Controller as is and do:
$this->validate($request, [
'EmailAddress' => 'required|email',
'Password' => 'required',
]);

Redirecting to the login page after fail request validation in laravel 5.1

I am creating Rest Full Api for mobile application, I am validating request it redirects me to the login page with errors.
Here is my ApiController (I have created for all api):
use App\User as UserModel;
use App\Fb_friend as FbFriendsModel;
use App\Http\Requests\UserRequest;
class ApiController extends Controller
{
/**
* Create a new movie model instance.
*
* #return void
*/
public function __construct(UserModel $user, FbFriendsModel $fb_friends){
$this->user = $user;
$this->fb_friends = $fb_friends;
}
public function createUser (UserRequest $request) {
// some code here
}
Route:
Route::post('createUser', ['as' => 'createUser', 'uses' => 'ApiController#createUser']);
UserRequest.php:
public function rules()
{
return [
'fb_id' => 'required|unique:users',
'username' => 'required|unique:users',
'email' => 'required|unique:users',
'image' => 'required',
'device_id' => 'required',
'status' => 'required',
];
}
I have override a function Request.php for error formatting:
abstract class Request extends FormRequest
{
protected function formatErrors(Validator $validator)
{
return [$validator->messages()->toJson()];
}
}
When I try to call service via postman, it returns me error in json format but it also print the login page, I m not getting why?
If you are using Postman for testing API's, it is not necessary to override the response() in Request class, One can follow the following steps,
make return type in authorize() in your custom Request as true,
public function authorize()
{
//make it true
return true;
}
Go to headers section in your Postman and define Accept type,
Accept:application/json
Now hit the endpoint of your API and bam..working fine for me.
It has been done by override the response method in app/Http/Requests/Request.php
public function response(array $errors) {
if ($this->ajax() || $this->wantsJson() || Request::isJson()) {
$newError = [];
$newError['result'] = false;
$newError['errors'] = $errors;
// in the above three lines I have customize my errors array.
return new JsonResponse($newError, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors);
}
We also need to use JsonResponse class at the top
use Illuminate\Http\JsonResponse;
Source: https://laracasts.com/discuss/channels/general-discussion/laravel-5-validation-formrequest

Categories