The effects of adding a Service Layer to a Laravel application - laravel

I have been developing with Zend Framework for a number of years and am now learning Laravel.
In my previous applications I usually have a Service Layer that is called by controllers. The Service Layer sits across the top of a Mapper and a Domain Model and is responsible for some application logic, raising events, some input filtering, etc.
Is there any reason why I should not implement a Service Layer in Laravel? In the examples that I have seen so far, controllers work directly with domain objects (or more accurately, active records).
If my Laravel controllers called my Service Layer, would I lose any of the advantages of Laravel? (As far as I can see I can still use Route/Model binding).
As a secondary question - what would be the best way to implement my Service Layer? As a collection of Service Providers, perhaps?

I also switched to Laravel coming from Zend and missed my Services. To sooth myself I have implemented a Service namespace which sits in namespace App\Services. In there I do all my Model or data handeling I don't want to see in my controller etc.
An example of my controller layout:
<?php
namespace App\Http\Controllers;
use App\Services\Contact as ContactService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Lang;
class IndexController extends Controller
{
/**
* Create a new controller instance.
*
* #param Request $request
* #return void
*/
public function __construct(Request $request)
{
$this->_request = $request;
}
/**
* Standard contact page
*
* #return contact page
*/
public function contact(ContactService $contactService)
{
$errors = null;
$success = false;
if ($this->_request->isMethod('post')) {
$validator = $contactService->validator($this->_request->all());
if ($validator->fails()) {
$errors = $validator->errors();
} else {
$contactService->create($validator->getData());
$success = true;
}
}
return view('pages/contact', ['errors' => $errors, 'success' => $success]);
}
}
The services return validators, handle cruds, basically do everything that I don't want to see in my Controller just like I had it in my Zend projects.
Example of Service:
<?php
namespace App\Services;
use Validator;
use Mail;
use App\Models\Contact as ContactModel;
class Contact
{
/**
* Get a validator for a contact.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
public function validator(array $data)
{
return Validator::make($data, [
'email' => 'required|email|max:255',
'phone' => 'max:255',
'firstName' => 'required|max:255',
'lastName' => 'required|max:255',
'message' => 'required'
]);
}
/**
* Create a new contact instance after a valid form.
*
* #param array $data
* #return ContactModel
*/
public function create(array $data)
{
//Handle or map any data differently if needed, just for illustration
$data = [
'email' => $data['email'],
'firstName' => $data['firstName'],
'lastName' => $data['lastName'],
'language' => $data['language'],
'phone' => $data['phone'],
'message' => $data['message']
];
// Send an email
Mail::send('emails.contact', ['data' => $data], function ($m) use ($data) {
$m->from(config('mail.from.address'), config('mail.from.name'));
$m->to(env('MAIL_TO', 'hello#world.com'), env('MAIL_TO'))->subject('Contact form entry from: ' . $data['firstName']);
});
return ContactModel::create($data);
}
}

See this post: “Design Pattern : Service Layer with Laravel 5” #Francoiss https://m.dotdev.co/design-pattern-service-layer-with-laravel-5-740ff0a7b65f.. in addition to the above answer you can also encapsulate the validation code inside a separate validator class

Related

How to use the ignore rule in Form Request Validation

this is PostsRequest.php in http/request:
<?php
namespace App\Http\Requests;
use App\Post;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class PostsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => ['required','max:255', Rule::unique('posts')->ignore($this->id)],
'slug' => ['required', Rule::unique('posts')->ignore($this->id),],
'content' => 'required',
'type' => 'required|in:blog,download,page',
'status' => 'required',
];
}
}
and this is edit() method in PostController.php
public function update(PostsRequest $request, $id)
{
$validated = $request->validated();
$validated['user_id'] = auth()->user()->id;
$post = Post::find($id)->fill($validated);
$post->save();
return redirect()->action('PostController#index');
}
Problem: show error in update page that this value is already exists.
who to resolve problem unique fields in edit form?
Problem Solved
change:
Rule::unique('posts')->ignore($this->route('id'))
with:
Rule::unique('posts')->ignore($this->route('post'))
If you're wanting to resolve the $id from the route then you can use the route() method in your request class e.g.
Rule::unique('posts')->ignore($this->route('id'))

Lumen - validation rules in model?

I am creating a REST API and would like to attach some validation rules to the CREATE and UPDATE endpoints. The validation rules for these two endpoints would be exactly the same, so I would like to be able to specify them in one place only. Coming from a Laravel background I normally create Form Requests, or put the validation rules in the model within a rules() function.
However I think Lumen does validation a bit differently and their documentation suggests to put the validation logic in the router: https://lumen.laravel.com/docs/master/validation
However personally I don't think that is the best place for it and would prefer to put in the model instead. I tried using the rules() function within the model but that doesn't seem to do anything.
My create and update methods look like this:
public function create(Request $request)
{
$product = Product::create($request->all());
return response()->json($product, 201);
}
public function update($id, Request $request)
{
$product = Product::findOrFail($id);
$product->update($request->all());
return response()->json($product, 200);
}
Is it possible for me to put my validation rules within my Product model and have them run automatically?
Here is how I have attempted to do it in my model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'price', 'description',
];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [];
/**
* Set model validation rules.
*
* #return array
*/
public function validate()
{
return [
'name' => 'required',
'price' => 'required',
'description' => 'required',
];
}
}
As you mentioned and in accordance with the Lumen validation documentation (as of 5.8):
Form requests are not supported by Lumen. If you would like to use form requests, you should use the full Laravel framework.
Lumen doesn't have anything similar to the automatic validation provided by Form Requests in Laravel, therefore you will need to perform validation manually.
Product
class Product extends Model
{
/**
* Get model validation rules.
*
* #return array
*/
public static function getValidationRules()
{
return [
'name' => 'required',
'price' => 'required',
'description' => 'required',
];
}
}
The above defines your Product model validation rules as a static methods, the rules should be the same for all your Product objects. You may want to consider scenarios where a field should be unique in your database, providing an argument to the method would be an option.
ProductController
class ProductController extends Controller
{
public function create(Request $request)
{
// Perform your validation
$validatedData = $request->validate(Product::getValidationRules());
// The Product is valid
$product = Product::create($request->all());
return response()->json($product, 201);
}
public function update($id, Request $request)
{
$product = Product::findOrFail($id);
// Perform your validation
$validatedData = $request->validate(Product::getValidationRules());
// The Product is valid
$product->update($request->all());
return response()->json($product, 200);
}
}
Where your validation fails with the above, Lumen will automatically redirect the user back to the previous location and errors flashed to the session (as with Laravel). Obviously you can alter this workflow if you desire.
The same validate method should be available at the controller level as well. So if you want to reuse the rules you could do something like this:
private $product_rules = [
'name' => 'required',
];
public function create(Request $request)
{
$this->validate($request, $this->product_rules);
$product = Product::create($request->all());
return response()->json($product, 201);
}
public function update($id, Request $request)
{
$product = Product::findOrFail($id);
$this->validate($request, $this->product_rules);
$product->update($request->all());
return response()->json($product, 200);
}

Specifying Middleware Within Controller's Constructor

Here's a code snippet from laravel in-built RegisterController.
public function __construct(){
$this->middleware('guest'); //What does it actually do?
}
I know that it's a good practice to define middleware in the
controller constructor but I need to know what
$this->middleware('guest') actually does and what parameter (where
?) it sets.
Second question: within the same controller (RegisterController), we use RegisterUser (defined in namespace Illuminate\Foundation\Auth) but it seems we never use it throughout the controller (overriding methods or attributes). I'm a little bit confused. Thnaks in advance!
class RegisterController extends Controller
{
use RegistersUsers; //?????
/**
* Where to redirect users after registration.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}
1) $this->middleware(...) called in a constructor of a controller is only adding what ever value is in ... to an array named middleware on the controller. That is it at that point. Nothing is "ran" at that point. You can see this functionality in the Controller your Controllers extend from, Illuminate\Routing\Controller.
Later when the Router needs to dispatch the Request it will build a middleware stack to pass the request through by gathering the middleware which includes asking the instance of the controller for its defined middleware via getMiddleware.
public function getMiddleware()
{
return $this->middleware;
}
2) The trait is including functionality and variables into your controller. There are routes pointing to some of these methods on the controller.
You could take all that code and paste it into your controller and it would be the same thing basically.
If you need to change any functionality of that controller or customize it you will end up redefining some of those methods or adjusting the variables.

Laravel 5.1 FatalErrorException in RegistersUsers.php line 32

since a few time I started programming in Laravel for a schoolproject. Now I tried to implement a login system. My database for userinformation is running on my homestead virtual machine, running with Postgresql. I already migrated the tables, so they exist and the database is running. Problem is: When I fill in the registrationform and send it, I get an error:
FatalErrorException in RegistersUsers.php line 32: Call to a member function fails() on null
You can see the code of RegistersUsers.php underneath:
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
trait RegistersUsers
{
use RedirectsUsers;
/**
* Show the application registration form.
*
* #return \Illuminate\Http\Response
*/
public function getRegister()
{
return view('auth.register');
}
/**
* Handle a registration request for the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function postRegister(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
Auth::login($this->create($request->all()));
return redirect($this->redirectPath());
}
}
When I try to log in it gives the warning the log in credentials doesn't exist in the database (because I haven't registered yet), so it seems that the database connection works properly.
Does someone know a solution for this?
----UPDATE----
Underneath I added my AuthController is included. You can also see my project on my github, so you can look into my other files if needed: https://github.com/RobbieBakker/LaravelProject56
class AuthController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
|
*/
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
private $redirectTo = '/';
/**
* Create a new authentication controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
if(!$validator->fails())
{
session(['email' => $data['email']]);
}
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
I eventually solved it just by commenting the function which called this error. I'm not sure if it's supposed to be solved like that, but my login system seems to work fine now.
Thanks for your support!
You forgot return validator instance from your validator function ))
protected function validator(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
if(!$validator->fails())
{
session(['email' => $data['email']]);
}
return $validator;
}
I had same error )
I think you need to "make" the validator.
try changing it to this..
$validator = $this->validator->make($request->all(), $rules);
see if that works...

Validation fileds from ajax request [Laravel 5]

Here is my validation request :rules
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Support\Facades\Auth;
class UpdateCommentRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize() {
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules() {
$user = Auth::user()->id;
return [
'comment' => 'required|between:15,600',
'projectID' => "required|exists:project_group,project_id,user_id,$user|numeric",
'order' => "required|numeric",
'level' => "required|numeric"
];
}
}
And in my model I have like this:
public function apiUpdateComment(UpdateCommentRequest $request){
$comment = Comment::find(Input::get("order"));
$comment->text = Input::get('comment');
if($comment->save()){
return 'success';
}
}
This fileds I need to validate agins rules array:
array(
'comment' => Input::get('comment'),
'projectID' => Input::get('projectID'),
'order' => Input::get("order"),
'level' => Input::get("level"),
);
I need to check if all rules are ok and then update comment... Anyone can help?
public function apiUpdateComment(UpdateCommentRequest $request){
$comment = Comment::find($request->get("order"));
$comment->text = $request->get('comment');
if($comment->save()){
return 'success';
}
}
The logic behind the code:
A post request is send the the server and the route file sends it the the apiUpdateComment with all variables inside the $request. But before the code of the function is executed the validator checks the rules in your UpdateCommentRequest. If the test fails it will return errors. If it pass a comment with the id will be updated.

Resources