I have this code in my CompanyController
use App\Services\CompanyService;
public function store(CompanyService $companyService) {
$result = $companyService->store();
return response()->json($result);
}
And this code in my CompanyService
use stdClass;
use App\Company;
use Illuminate\Http\Request;
public function store(Request $request, Company $company) {
// this also not work
dd($request->all());
$data = new stdClass;
$data->status = 1;
$data->message = 'success';
return $data;
}
When i run this code, Laravel show error
Too few arguments to function App\Services\CompanyService::store() 0
passed but exactly 1 expected
I know that type hint dependecy injection issue, because it work in Controllers but not work in my CompanyService when i call store() without params
How can i fix this and make it work in my CompanyService ?
Edit:
You can use call() method of service container
In your case:
public function store()
{
$companyService = app(CompanyService::class);
$result = $companyService->call('store');
return response()->json($result);
}
Unfortunately it's not possible to method inject dependencies but you can inject your dependencies in your construct method, so in your case it would be like this:
use stdClass;
use App\Company;
use Illuminate\Http\Request;
class Foo
{
protected $request;
protected $company;
public function __construct(Request $request, Company $company)
{
$this->request = $request;
$this->company = $company;
}
public function store()
{
// this works
dd($this->request->all());
// Also injected
dd($this->company);
$data = new stdClass;
$data->status = 1;
$data->message = 'success';
return $data;
}
}
PS:
I believe laravel handles method injection in one of it's middlewares so as far as I know there is not possible way to perform method injection you can read about Service Container for more info
Related
I am implementing the Repository Pattern (service) in a Laravel application and I have some doubts about the usage of interfaces with these services.
I have created an interface called CRUD (code bellow) to serve as a way to always keep the same names for the services that are going to implement CRUD methods.
<?php
namespace App\Interfaces;
interface CRUD
{
public function create(array $data);
public function update(int $id, array $data);
public function delete(string $ids);
};
Bellow there's an example of how I call my service and the service itself, and that's where my doubts are. Usually I'll see people witing an interface for each service and demanding the controller to have injected an objet of that type. Because of that, people will have to bind a specific type (interface) to the controller. It seems redundant and thus I simply passed the service I need.
Now, is this ok or I should pass the CRUD interface to the controller in this case? Or should I even create another interface specifically for each service?
<?php
namespace App\Http\Controllers\Cms;
use App\Http\Controllers\Controller;
use App\Http\Requests\GroupRequest;
use App\Models\Group;
use App\Services\GroupsService;
use Illuminate\Http\Request;
class GroupsController extends Controller
{
private $service;
public function __construct(GroupsService $service)
{
$this->service = $service;
}
public function store(GroupRequest $request)
{
$result = $this->service->create($request->all());
return redirect()->back()->with('response', $result);
}
public function update(GroupRequest $request, $id)
{
$result = $this->service->update($id, $request->all());
return redirect()->back()->with('response', $result);
}
public function destroy($groups_id)
{
$result = $this->service->delete($groups_id);
return redirect()->back()->with('response', $result);
}
}
<?php
namespace App\Services;
use App\Models\Group;
use App\Interfaces\CRUD;
use Exception;
class GroupsService implements CRUD
{
public function listAll()
{
return Group::all();
}
public function create(array $data)
{
$modules_id = array_pop($data);
$group = Group::create($data);
$group->modules()->attach($modules_id);
return cms_response(trans('cms.groups.success_create'));
}
public function update(int $id, array $data)
{
try {
$modules_ids = $data['modules'];
unset($data['modules']);
$group = $this->__findOrFail($id);
$group->update($data);
$group->modules()->sync($modules_ids);
return cms_response(trans('cms.groups.success_update'));
} catch (\Throwable $th) {
return cms_response($th->getMessage(), false, 400);
}
}
public function delete(string $ids)
{
Group::whereIn('id', json_decode($ids))->delete();
return cms_response(trans('cms.groups.success_delete'));
}
private function __findOrFail(int $id)
{
$group = Group::find($id);
if ($group instanceof Group) {
return $group;
}
throw new Exception(trans('cms.groups.error_not_found'));
}
}
If you want to use Repository Design Patteren You have to create seprate Interface for each service accroing to SOLID Principle. You have to create custom service provider and register your interface and service class and then inject interface in construtor of controller.
You can also follow below article.
https://itnext.io/repository-design-pattern-done-right-in-laravel-d177b5fa75d4
I did something with repo pattern in laravel 8 you might be interested:
thats how i did it:
first of all, you need to implement a provider
in this file i created the binding:
App\ProvidersRepositoryServiceProvider.php
use App\Interfaces\EventStreamRepositoryInterface;
use App\Repositories\EventStreamRepository;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(EventStreamRepositoryInterface::class, EventStreamRepository::class);
}
}
then in file:
app\Interfaces\EventStreamRepositoryInterface.php
interface EventStreamRepositoryInterface {
public function index();
public function create( Request $request );
public function delete($id);
}
in file:
App\Repositories\EventStreamRepository.php
class EventStreamRepository implements EventStreamRepositoryInterface{
public function index()
{
return EventStream::with(['sessions'])
->where([ ["status", "=", 1] ] )
->orderBy('created_at', 'DESC')
->get();
}
public function create(Request $request)
{
request()->validate([
"data1" => "required",
"data2" => "required"
]);
$EventStream = EventStream::create([
'data1' => request("data1"),
'data2' => request('data2')
]);
return $EventStream->id;
}
public function delete($id)
{
return EventStream::where('id', $id)->delete();
}
}
in file:
App\Http\Controllers\EventStreamController.php
use App\Interfaces\EventStreamRepositoryInterface;
class EventStreamController extends Controller{
private EventStreamRepositoryInterface $eventStreamRepository;
public function __construct(EventStreamRepositoryInterface $eventStreamRepository)
{
$this->eventStreamRepository = $eventStreamRepository;
}
public function index():JsonResponse
{
$this->eventStreamRepository->index();
}
public function store(Request $request ):JsonResponse
{
$this->eventStreamRepository->create($request);
}
public function destroy($id):JsonResponse
{
$this->eventStreamRepository->delete($id);
}
}//class
note: i think i removed all unnecessary -validations- and -returns- in controller for better reading.
Hope it helps!!
I created a route & contronller:
Route::group(['prefix' => 'test'], function () {
Route::get('product/{id}', ['uses' => 'ProductController#getProduct']);
});
ProductController:
class ProductController extends MyController {
public $_params = null;
public function __construct(Request $request) {
$this->request = $request;
$this->_params = $request->all();
$options = array();
parent::__construct($options);
}
public function getProduct() {
dd($this->_params);
}
}
I requested: http://localhost/test/product/123
But the id = 123 not exist in $this->_params
Request params are input data like POST data. To access route params you will need to make another class property like "$routeParams"
class ProductController extends MyController {
public $_params = null;
public $routeParams = null;
public function __construct(Request $request) {
$this->request = $request;
$this->_params = $request->all();
$this->routeParams = $request->route()->parameters();
$options = array();
parent::__construct($options);
}
public function getProduct() {
dd($this->routeParams);
}
}
I understand need to implement your logic on top of the Laravel, but I would suggest that you do that in some Services, Actions, Domain.... Maybe this can help: https://laravel-news.com/controller-refactor
You can try do it like basic controller from documentation
and make some custom service for complex stuff.
class ProductController extends Controller
{
public function getProduct($id)
{
$productService = new ProductService($id);
//....
}
}
If you want to get an array of the route parameters, you need to use $request->route()->parameters(), not $request->all()
$request->all() returns the query parameters for GET requests
$request->all() only get parameter from header, body and URL but can't get parameter from route product/{id}
You should replace func getProduct to param id
public function getProduct($id) { dd($id); }
İf you want all params in request use $request->all() method but you want only id in url $request->id
Someone please help me enlighten in LARAVEL !!!
in LARAVEL controller i difine static function like this :
namespace App\Http\Controllers\MyAPI;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class MyAPIController extends Controller {
const acceptMethod = ['GET','POST','PUT','DELETE']
public function handler(Request $request) {
$acceptMethod = self::acceptMethod;
$ctrl = new PromotionController;
$method = $request->method()
// This is my question :((
if ($method == 'GET')
$ctrl::read($request);
if ($method == 'GET')
$ctrl::post($request);
$ctrl::put($request);
...
//I want to be like this :
foreach($acceptMethod as $method) {
// Not work
$ctrl::($method)($request);
}
}
public static function read(Request $request) {
return something;
}
public static function post(Request $request) {
return ...;
}
public static function put(Request $request) {
return ...;
}
public static function delete(Request $request) {
return ...;
}
}
And then i must use controll like :
if ($method == 'get')
$ctrl::read($request);
if ($method == 'post')
$ctrl::post($request);
$ctrl::put($request);
But i have a array :
and i want to be like this :
$acceptMethod = ['GET','POST','PUT','DELETE'];
foreach($acceptMethod as $functionName) {
// Not work
$ctrl::$functionName($request);
}
Is there any way to make this possible ??
Use {};
Please, try this inside the loop:
$fn = strtolower($functionName)
$ctrl::{$fn}($request);
You can call a attribute too..
$instance->{'attribute_name'};
Routes
The proper way to do it would be to define a RESTful resource for your object so you get all the routes RESTfully. In your routes/api.php
Route::resource('thing','MyAPIController');
That will magically route:
GET api/thing to index()
GET api/thing/create to create()
POST api/thing to store()
GET api/thing/{id} to show($id)
GET api/thing/{id}/edit to edit()
PATCH api/thing/{id} to update()
DELETE api/thing/{id} to destroy()
If you have multiple objects to REST, you'd just add a controller for each.
Whats wrong $ctrl::{$fn}($request)
Injection is always on the OWASP top 10 each year, and this opens up potential function injection. You can mitigate that risk by making sure you white list the method. But, I'd rather just use Laravel the way it was intended.
I've just learned of model observers and would like to move some of my logic from controller to observer. Here's what I have:
AppServiceProvider.php
public function boot()
{
WorkOrder::observe(WorkOrderObserver::class);
}
WorkOrderObserver.php
namespace App\Observers;
use App\Site;
use App\WorkOrder;
use Carbon\Carbon;
use App\WorkOrderNumber;
class WorkOrderObserver
{
public function creating(WorkOrder $workOrder)
{
$branchOfficeId = Site::findOrFail($request->site_id)->branch_office_id;
$today = Carbon::today('America/Los_Angeles');
$todaysWorkOrderCount = WorkOrder::where('created_at_pst', '>=', $today)->count();
$workOrder->work_order_number = (new WorkOrderNumber)
->createWorkOrderNumber($branchOfficeId, $todaysWorkOrderCount);
$workOrder->completed_by = null;
$workOrder->status_id = 1;
$workOrder->work_order_billing_status_id = 1;
$workOrder->created_at_pst = Carbon::now()->timezone('America/Los_Angeles')
->toDateTimeString();
}
}
Problem is accessing the request from within the observer. I don't see anything in the docs. I found one thread here that refers to this and it suggested using the request helper function. I tried request('site_id') but it was empty.
This is so simple I'm a bit embarrassed I posted it. Anyway, in case someone finds this thread, here's the solution. In your observer, add a constructor that accepts the request and sets a property.
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
You can request object using app helper function of Laravel.
protected $request;
public function __construct(WorkOrderNumber $workorder)
{
$this->request = app('request');
}
get request data in observer laravel
request() helper should work:
if (request()->has('password')) {
$user->password = bcrypt(request()->password);
}
ref: https://www.codegrepper.com/code-examples/php/get+request+data+in+observer+laravel
I am not sure if I am using this correctly, but I am utilising the requests in Laravel 5, to check if the user is logged in and if he is the owner of an object. To do this I need to get the actual object in the request class, but then I need to get the same object in the controller?
So instead of fetching it twice, I thought, why not just set the object as a variable on the request class, making it accessible to the controller?
It works, but I feel dirty? Is there a more appropriate way to handle this?
Ex.
Request Class
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::find(Input::get('comment_id'));
$user = Auth::user();
if($this->comment->user == $user)
return true;
return false;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Ex. Controller:
public function postDeleteComment(DeleteCommentRequest $request) {
$comment = $request->comment;
$comment->delete();
return $comment;
}
So what is my question? How do I best handle having to use the object twice when using the new Laravel 5 requests? Am I possibly overextending the functionality of the application? Is it ok to store the object in the application class so I can reach it later in my controller?
I would require ownership on the query itself and then check if the collection is empty.
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::where('id',Input::get('comment_id'))->where('user_id',Auth::id())->first();
if($this->comment->is_empty())
return false;
return true;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Since you're wanting to use the Model in two different places, but only query it once I would recommenced you use route-model binding.
In your RouteServiceProvider class (or any relevant provider) you'll want to bind the comment query from inside the boot method. The first parameter of bind() will be value that matches the wildcard in your route.
public function boot()
{
app()->router->bind( 'comment_id', function ($comment_id) {
return comment::where('id',$comment_id)->where('user_id',Auth::id())->first();
} );
}
Once that's set up you can access the Model from your DeleteCommentRequest like so
$this->comment_id
Note: The variable is Comment_id because that's what matches your route, but it will contain the actual model.
From your controller you just inject it like so
public function postDeleteComment(Comment $comment, DeleteCommentRequest $request) {
$comment->delete();
return $comment;
}