My Laravel controller has an index method on it which accepts an optional query string parameter.
How should this be represented in the method's PHPDoc block?
EmployerController.php:
/**
* #param Request $request
* #return Response
* Returns list of Employers
*/
public function index(Request $request) {
// This is the optional parameter which needs to be represented
// in the PHPDoc block
$collectiveAgreement = $request->query('collective_agreement');
...
}
If your purpose is to document those fields, I'd recommend to create a FormRequest that handles all that logic, and then inject the form request into the controller. This way, you know where the request is formed and then go to that class to see the fields and even better: the rules for them to pass the validation.
php artisan make:request ListUsersRequest
ListUsersRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ListUsersRequest extends FormRequest {
public function rules()
{
return [
'collective_agreement' => ['here', 'goes', 'your', 'rules'],
'another_field' => ['here', 'goes', 'more', 'rules'],
];
}
public function authorize()
{
return true;
}
}
Then, back to your controller
UserController.php
public function index(ListUsersRequest $request)
{ // ^^^^^^^^^^^^^^^^
$data = $request->validated();
$collectiveAgreement = $data['collective_agreement'];
// Or as you did:
// $collectiveAgreement = $request->query('collective_agreement');
}
Keep description in first line and #param and #return under that. It would be sort of standard order. Feel free to add description as you would like and how it would help other who read the code. In this case you've already documented param since that query string is part of $request object but you can extend description to be like:
/**
* Returns list of Employers.
* Request object may have optional query string parameter 'collective_agreement'
* used for this and this and that and that
*
* #param Request $request
* #return Response
*/
public function index(Request $request)
{
// This is the optional parameter which needs to be represented
// in the PHPDoc block
$collectiveAgreement = $request->query('collective_agreement');
...
}
Related
Merry Christmass team!
I have a problem trying to figure out how to pass a method with my own constraints in a controller that is bound to request patter paradigm:
Sample Controller Code:
class SampleController
{
protected $model = SampleModel::class;
protected $indexRequest = IndexRequest::class;
}
Request Class
class IndexRequest 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 [];
}
Assume I have a method that I want to do something different.Say I want to fetch some data based on some column constraints.
Whats the approach?
I'm using laravel 5.5
I have a Request that I've built but the required rule is not working correctly.
Route
Route::get('v1/learning_centre/user/{userId}/course/list', 'API\LearningCentre#userCourses');
Controller
public function userCourses(GetUserCourses $request)
{
$courses = User::findOrFail($request->userId)
->courses()
->get();
return new CourseResourceCollection($courses);
}
Request
namespace App\Http\Requests\LearningCentre;
use Illuminate\Foundation\Http\FormRequest;
class GetUserCourses 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 [
'userId' => 'required|integer'
];
}
/**
* Get the error messages for the defined validation rules.
*
* #return array
*/
public function messages()
{
return [
'userId.required' => 'A User is required',
];
} }
If I turn off the required rule I can get to the controller. If I have the required rule in the request I get a 302. I am passing in a valid userId in phpunit. Without the request rules my code works as intended.
Any ideas?
You should be using route model binding to validate a required GET parameter in this situation, not a FormRequest class, which, as the name should indicate, are intended for form requests.
Your route:
Route::get('v1/learning_centre/user/{user}/course/list', 'API\LearningCentre#userCourses');
Your controller:
public function userCourses(User $user) {
If a user ID is missing (or an invalid one used), your controller will automatically throw a ModelNotFoundException, which Laravel by default returns as a 404.
My use case is that an user is/owns a company, which has employees.
Using form controllers along with model policies i am trying to figure out what the best/proper way to do it should be.
routes:
Route::resource('company', \App\Http\Controllers\Api\v1\CompanyController::class);
Route::resource('employee', \App\Http\Controllers\Api\v1\EmployeeController::class);
employee store request:
namespace App\Http\Requests;
use App\Models\Employee;
use Illuminate\Foundation\Http\FormRequest;
class EmployeeStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return $this->user()->can('create', Employee::class);
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'company_id' => 'required|integer|exists:companies,id'
];
}
}
employee policy:
...
/**
* Determine whether the user can create employees.
*
* #param \App\User $user
* #return mixed
*/
public function create(User $user)
{
return $user->can('update', Company::find(
app('request')->get('company_id')
));
}
...
So i am not particularly happy in how the policy checks if the user can edit the company the employee will belong to, since this only happens on http, for console/tests this will break.
Then the most logical way to add this check is in the form request's authorize() function, but then you are checking permissions outside the policies, which sounds illogical.
So in short, the question: how & why would you do this using form requests & model policies?
You only need to add the id of the company to the EmployeePolicy#create method, and you will be able to use it outside http
EmployeePolicy
public function create(User $user, $companyId)
{
return $user->can('update', $companyId);
}
EmployeeStoreRequest
public function authorize()
{
return $this->user()->can('create', Employee::class, $this->request->get('company_id'));
}
You can test it outside http with tinker
php artisan tinker
$user = User::find(2); // or whatever user you want to test with
$user->can('create', Employee::class, 3); // 3 = company_id
In Drupal there is a simple url rewrite system that stores path aliases and the real route in the database.
For example:
/category/hello => node/5
I would like to imitate this system in Laravel.
I know how to create the database structure. What I would like suggestions for is actually overriding and remapping the incoming request.
I've taken a glance at the router. No events are really sticking out. What I would like to avoid is adding every permutation as a static route. I would like to for this to be completely dynamic.
I was reading middleware with a redirect would work but don't know if that is the best route to go. Keep in mind that the aliases could be anything. There isn't any set pattern.
The actual business case for this is the application has a hierarchy of categories like for a catalog on an ecommerce site. For every path a dynamic page will need to exist and possibly also allow pass-thrus to other pages.
Ex.
/sports/football/nfl => \App\Http\Controllers\Category::lp(2)
Even something like:
/sports/football/nfl/:game/lines => \App\Http\Controllers\Lines::lp(:game)
However, I don't want to have every permutation in the db. Just the base one and allow everything after /sports/football/nfl/* pass thru to a completely different location.
If I do recall in Symfony this could be done with a custom route matcher. However, I don't see anything like that in Laravel. Unless I'm just missing something. It looks like you either add a static route or nothing all but I haven't taken the deep dive into that code yet so could be wrong.
I was able to implement a dynamic routing system by creating my own custom route and adding to the route collection manually.
Custom Route
use Illuminate\Routing\Route as BaseRoute;
use Modules\Catalog\Routing\Matching\CategoryValidator;
use Illuminate\Routing\Matching\MethodValidator;
use Illuminate\Routing\Matching\SchemeValidator;
use Illuminate\Routing\Matching\HostValidator;
use Illuminate\Http\Request;
use Modules\Event\Repositories\CategoryRepository;
use Illuminate\Routing\ControllerDispatcher;
/**
* Special dynamic touting for catalog categories.
*/
class CategoryRoute extends BaseRoute {
protected $validatorOverrides;
/**
* #param CategoryRepository
*/
protected $categoryRepository;
/**
* Create a new Route instance.
*
* #param CategoryRepository $categoryRepository
* The category repository.
*/
public function __construct(CategoryRepository $categoryRepository)
{
$this->categoryRepository = $categoryRepository;
$action = [
'uses'=> function() use ($categoryRepository) {
$path = app('request')->path();
$category = $categoryRepository->findOneByHierarchicalPath($path);
$controller = app()->make('Modules\Catalog\Http\Controllers\Frontend\CategoryController');
return $controller->callAction('getIndex', ['categoryId'=>$category->getId()]);
}
];
$action['uses']->bindTo($this);
parent::__construct(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'],'_catalog_category',$action);
}
/**
* Determine if the route matches given request.
*
* #param \Illuminate\Http\Request $request
* #param bool $includingMethod
* #return bool
*/
public function matches(Request $request, $includingMethod = true)
{
$this->compileRoute();
$validators = $this->getValidatorOverrides();
foreach ($validators as $validator) {
/*if (! $includingMethod && $validator instanceof MethodValidator) {
continue;
}*/
if (! $validator->matches($this, $request)) {
return false;
}
}
return true;
}
/**
* Get the route validators for the instance.
*
* #return array
*/
public function getValidatorOverrides()
{
if (isset($this->validatorOverrides)) {
return $this->validatorOverrides;
}
$this->validatorOverrides = [
new MethodValidator, new SchemeValidator,
new HostValidator, /*new UriValidator,*/
new CategoryValidator($this->categoryRepository)
];
return $this->validatorOverrides;
}
}
Custom Route Validator
<?php
namespace Modules\Catalog\Routing\Matching;
use Illuminate\Routing\Matching\ValidatorInterface;
use Illuminate\Routing\Route;
use Illuminate\Http\Request;
use Modules\Event\Repositories\CategoryRepository;
class CategoryValidator implements ValidatorInterface
{
protected $categoryRepository;
public function __construct(CategoryRepository $categoryRepository) {
$this->categoryRepository = $categoryRepository;
}
/**
* Validate a given rule against a route and request.
*
* #param \Illuminate\Routing\Route $route
* #param \Illuminate\Http\Request $request
* #return bool
*/
public function matches(Route $route, Request $request)
{
$path = $request->path() == '/' ? '/' : '/'.$request->path();
$category = $this->categoryRepository->findOneByHierarchicalPath($path);
return $category?true:false;
}
}
To satisfy the requirements of the category repository dependency I had to also create a subscriber that adds the route after all the providers had been booted. Simply placing it in the routes.php file would not work because there was no guarantee that all the dependencies for IoC would be configured when that file gets loaded.
Bootstrap Subscriber
use Modules\Catalog\Routing\CategoryRoute;
use Modules\Event\Repositories\CategoryRepository;
use Illuminate\Support\Facades\Route as RouteFacade;
class BootstrapSubscriber {
public function subscribe($events) {
$events->listen(
'bootstrapped: Illuminate\Foundation\Bootstrap\BootProviders',
'Modules\Catalog\Subscribers\BootstrapSubscriber#onBootstrappedBootProviders'
);
}
public function onBootstrappedBootProviders($event) {
$categoryRepository = app(CategoryRepository::class);
RouteFacade::getRoutes()->add(new CategoryRoute($categoryRepository));
}
}
I will probably expand on this but that is the basic way to do it.
I have a Laravel 3 application that has several REST-ful controllers.
The controllers that take no parameters (e.g. a controller that handles the URL /api/books) works fine, but when I try and access the URL of a controller that takes parameters (e.g. /api/book/1), it doesn't work. However, if I append the method name to the URL (e.g. /api/book/index/1), it does work properly.
Is there a way to not be required to use the keyword "index" on a controller?
An example of one of the non-functioning controllers--
<?php
class API_Book_Controller extends Base_Controller {
/**
* Indicates the controller is RESTful
* #var boolean
*/
public $restful = true;
/**
* Fetch a book by ID
* #param integer $id ID number of the book
* #return Response HTTP response
*/
public function get_index($id = null){
$book = Book::find($id);
if(is_null($book)){
return Response::error('404');
}
return Response::eloquent($book);
}
Route::get('api/book/(:num?)', 'API_Book_Controller#get_index');