Laravel Route Model Binding to Route::controller() - php

I've got the following routes:
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController',
'admin' => 'AdminController',
'site' => 'SiteController'
]);
And then I've got the following method in the SiteController:
/**
* Get site details and pass to view
*
* #param Site $site
* #return mixed
* #internal param $site_id
*/
public function getDetails( Site $site )
{
return $site;
}
When I go to the URL site.com/site/details/13 it doesn't return the site object.
I've added $router->model( 'one', 'App\Site' ); into the RouteServiceProvider and it works, but what if later down the road I want to add another controller like this but use it for jobs, and use the getDetails method again and pass through the App\Job object? It will automatically send the App\Site model instead.
So is there a way I can prevent this from happening?

My limited knowledge of Laravel tells me you can't have a model/object as parameter in your route controller functions, and that you don't need something like $router->model( 'one', 'App\Site' ); to do this.
I'm assuming you'd want to do something like this:
As for your routes:
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController',
'admin' => 'AdminController',
'site' => 'SiteController',
'jobs' => 'JobController',
]);
In your SiteController:
use Illuminate\Http\Request;
use App/Site; //replace with namespace of model
public function getDetails($id)
{
//code for fetching the site object, depends on how your structure is,
//like $site = App\Site::find($id); etc
return $site;
}
Similarly, your JobController will be something like:
use Illuminate\Http\Request;
use App/Job; //replace with namespace of model
public function getDetails($id)
{
//code for fetching the job object, depends on how your structure is,
//like $job = App\Job::find($id); etc
return $job;
}
Look here: Laravel Docs - Implicit Controllers

Related

Laravel Backpack CRUD not working

i followed this tutorial to setup a CRUD example. But i can't get it to work and i don't know why. here is my code
SomeitemCrudController.php
<?php
namespace App\Http\Controllers\Admin;
use Backpack\CRUD\app\Http\Controllers\CrudController;
// VALIDATION: change the requests to match your own file names if you need form validation
use App\Http\Requests\SomeitemRequest as StoreRequest;
use App\Http\Requests\SomeitemRequest as UpdateRequest;
class SomeitemCrudController extends CrudController
{
public function setUp()
{
/*
|--------------------------------------------------------------------------
| BASIC CRUD INFORMATION
|--------------------------------------------------------------------------
*/
$this->crud->setModel("App\Models\Someitem");
$this->crud->setRoute("admin/someitem");
$this->crud->setEntityNameStrings('someitem', 'someitems');
/*
|--------------------------------------------------------------------------
| BASIC CRUD INFORMATION
|--------------------------------------------------------------------------
*/
$this->crud->setFromDb();
// $this->crud->setColumns(['nama']);
$this->crud->addField([
'nama' => 'Nama',
'keterangan' => 'Keterangan',
'harga' => 'Harga'
]);
}
public function store(StoreRequest $request)
{
$redirect_location = parent::storeCrud();
return $redirect_location;
}
public function update(UpdateRequest $request)
{
$redirect_location = parent::updateCrud();
return $redirect_location;
}
}
the model, Someitem.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
class SomeItem extends Model
{
use CrudTrait;
//
protected $table = 'someitem';
protected $fillable = ['nama', 'keterangan', 'harga'];
public $timestamps = true;
}
the request, SomeitemRequest.php
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class SomeitemRequest extends \Backpack\CRUD\app\Http\Requests\CrudRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return \Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
// 'name' => 'required|min:5|max:255'
];
}
/**
* Get the validation attributes that apply to the request.
*
* #return array
*/
public function attributes()
{
return [
//
];
}
/**
* Get the validation messages that apply to the request.
*
* #return array
*/
public function messages()
{
return [
//
];
}
}
and then, the routes
<?php
Route::group([
'prefix' => config('backpack.base.route_prefix', 'admin'),
'middleware' => ['admin'],
'namespace' => 'Admin'
], function() {
CRUD::resource('pelayanan', 'Admin\PelayananCrudController');
Route::get('/test', function () {
return view('welcome');
});
});
i can access http://localhost/myapp/public/admin/test successfully, but i can't access http://localhost/myapp/public/admin/someitem , it always return Error 500
i'm new to laravel (and PHP), any helps is appreciated. thanks!
I want to make my answer very detailed, so that's why I've started it from the beginning. The version for backpack CRUD I use is ^3.2.
look at this file your-project/vendor/backpack/crud/src/CrudServiceProvider.php
Yes I know that class name is not CRUD but CrudServiceProvider. That is because they have register method in CrudServiceProvider and in it they do $loader->alias('CRUD', \Backpack\CRUD\CrudServiceProvider::class); which makes CRUD class alias to CrudServiceProvider. To know more about how it works inside read through Laravel Service Providers, Service Container and Facades.
So we've figured out that CRUD::resource() actually means CrudServiceProvider::resource(), so get to this method, there you will find only one line return new CrudRouter($name, $controller, $options); as far as I know (if I'm wrong correct me) it's called Factory pattern/method. So go to CrudRouter constructor (Github). Read through it. So the basic thing it does is instead of this
CRUD::resource('pelayanan', 'Admin\PelayananCrudController');
it will put something like this
Route::post('pelayanan/search', [
'as' => 'crud.pelayanan.search',
'uses' => 'Admin\PelayananCrudController#search',
]);
Route::get('pelayanan/reorder', [
'as' => 'crud.pelayanan.reorder',
'uses' => 'Admin\PelayananCrudController#reorder',
]);
Route::post('pelayanan/reorder', [
'as' => 'crud.pelayanan.save.reorder',
'uses' => 'Admin\PelayananCrudController#saveReorder',
]);
Route::get('pelayanan/{id}/details', [
'as' => 'crud.pelayanan.showDetailsRow',
'uses' => 'Admin\PelayananCrudController#showDetailsRow',
]);
Route::get('pelayanan/{id}/translate/{lang}', [
'as' => 'crud.pelayanan.translateItem',
'uses' => 'Admin\PelayananCrudController#translateItem',
]);
Route::get('pelayanan/{id}/revisions', [
'as' => 'crud.pelayanan.listRevisions',
'uses' => 'Admin\PelayananCrudController#listRevisions',
]);
Route::post('pelayanan/{id}/revisions/{revisionId}/restore', [
'as' => 'crud.pelayanan.restoreRevision',
'uses' => 'Admin\PelayananCrudController#restoreRevision',
]);
You said that you can't acces you public/admin/someitem and that's true, because you don't have route for it (actually check routes in cli with artisan route:list). But Backpack developers made one thing (don't actually know why) they put extra routes in destructor method by calling
`Route::resource($this->name, $this->controller, $options_with_default_route_names);`.
And by it should actually work, if it's not, check how index (CrudController) behaves.
One more thing maybe you forgot to php artisan vendor:publish --provider="Backpack\Base\BaseServiceProvider" and you don't have view files and that's why you get 500 error.
If you have more questions, just ask.

Laravel authorization via middleware

I created a policy in laravel 5.3 with this two actions:
class ProjectPolicy {
...
public function index(User $user)
{
return true;
}
public function create(User $user)
{
return true;
}
...
}
and then I tried to make authorization via route group middleware:
Route::group(['middleware' => ['can:index,create,App\Project']], function () {
Route::resource('projects', 'ProjectController');
});
ofcourse I have created Project model and controller correctly, but calling index and create actions always returns 403 forbidden response. where is the problem?
Update:
removing one of actions from route middleware, results correct response. something like this:
Route::group(['middleware' => ['can:index,App\Project']], function () {
Route::resource('projects', 'ProjectController');
});
Looking through the docs the can middleware doesn't really lend itself to resources. You could use multiple middleware calls on the group but this would mean that your use would require all privileges to access the routes.
Your alternatives are:
Add $this->authorize(new App\Project) to your index and create methods in your controller. Laravel will use reflection to figure out what policy to use based on the method it is called from.
Or
In the __construct() method of your controller you could use:
$this->authorizeResource(App\Project::class);
This will require you to
create update, view and delete methods inside your Policy class. Each of these methods will be passed User $user, Project $project e.g.
public function view(User $user, Project $project)
{
return true;
}
FYI, if you leave off the method name with authorize() or you use authorizeResource() Laravel will map certain method names to different policy methods i.e. :
[
//ControllerMethod => PolicyMethod
'show' => 'view',
'create' => 'create',
'store' => 'create',
'edit' => 'update',
'update' => 'update',
'destroy' => 'delete',
];
You can override this by adding a resourceAbilityMap() method to your controller and returning a different array to the one above.
Hope this helps!
Spend hours on that ...
If your controller method does not take a Model as parameter, you also have to override the resourceMethodsWithoutModels().
protected function resourceAbilityMap()
{
Log::info("Inside resourceAbilityMap()");
return array_merge(parent::resourceAbilityMap(), [
//ControllerMethod => PolicyMethod
'index' => 'index',
'search' => 'search'
]);
}
protected function resourceMethodsWithoutModels(){
return array_merge(parent::resourceMethodsWithoutModels(), [
'search'
]);
}

Laravel 5.2 Controller middleware for more than one role

I have a roles administrator, moderator and member in my laravel application. Application have fronted and backend sections. I want to allow access to backend section only for administrator and moderator. I create SuperUsersMiddleware:
<?php
namespace CMS\Http\Middleware;
use Closure;
class SuperUsersMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (! $request->user()->hasRole('administrator') || ! $request->user()->hasRole('moderator')) {
return redirect('/');
}
return $next($request);
}
}
Register in Kernel.php:
......
protected $routeMiddleware = [
'superusers' => \CMS\Http\Middleware\SuperUsersMiddleware::class,
'administrator' => \CMS\Http\Middleware\AdminMiddleware::class,
'moderator' => \CMS\Http\Middleware\ModeratorMiddleware::class,
'auth' => \CMS\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
'guest' => \CMS\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
.....
and in my backend folder I create Controller.php (all other controllers in backend section extends this controller) and in __construct() function set middleware:
...
public function __construct()
{
$this->middleware('superusers');
}
...
But this doesn't work for me. I also create administrator and moderator middleware and it works separately but I needed both - together. How to do that? I tray:
public function __construct()
{
$this->middleware('administrator');
$this->middleware('moderator');
}
But this also can't work. What is a best practice for this situation?
First off I wouldn't apply any Middleware on your master Controller as then middleware would be applied to everything. You should do this on each individual controller like UserController.
You can apply as many middleware instances to a route/function as you want. I'm not aware of any limitations on that. So I'm not sure why you say this doesn't work:
public function __construct()
{
$this->middleware('administrator');
$this->middleware('moderator');
}
You can apply the different middleware to the routes that require the different levels. You can do this in your routes.php or in your Controllers. If you want to do it in your Controller like you are doing above you would have something like this:
public function __construct()
{
$this->middleware('auth'); //this applies to all actions
$this->middleware('administrator', ['only' => ['adminFunction', 'otherAdminFunction','bothCanAccess']]);
$this->middleware('moderator',['only' => ['moderatorFunction','bothCanAccess']);
}
public function adminfunction()
{
...
}
public function otherAdminfunction()
{
...
}
public function moderatorFunction()
{
...
}
public function bothCanAccess()
{
...
}
So first off the auth middleware will apply to all actions. This means a user has to be logged in to access any function in here. Then you can apply specific middleware to each function. If you need more info on this check out the documentation:
https://laravel.com/docs/5.2/controllers#controller-middleware
To do this in your router you would do something like this:
Route::get('/admin', ['middleware' => ['auth', 'administrator'],'uses'=>'Controller#adminFunction']);
So in this case it will apply the auth middleware first to make sure someone is logged in, then fire the administrator middleware and make sure the user is an admin.
Hopefully that helps.

Getting the controller action before behaviour code runs in Yii2

I'm trying to execute some code inside a Yii2 controller as I need some code from the model to be accessible within the behaviors section so I can pass the model as a parameter and avoid running duplicate queries; however I also need to be able to find out what action is being called, but I am not having much luck.
I have tried using beforeAction but it seems this gets run AFTER the behaviours code runs, so that doesn't help me.
I then tried using init, but it seems the action isn't available via $this->action->id at that point.
Some example code:
class MyController extends Controller {
public $defaultAction = 'view';
public function init() {
// $this->action not available in here
}
public function beforeAction() {
// This is of no use as this runs *after* the 'behaviors' method
}
public function behaviors() {
return [
'access' => [
'class' => NewAccessControl::className(),
'only' => ['view','example1','example2'],
'rules' => [
[
'allow' => false,
'authManager' => [
'model' => $this->model,
'other_param' => $foo,
'other_param' => $bar,
],
'actions' => ['view'],
],
// everything else is denied
],
],
];
}
public function viewAction() {
// This is how it is currently instantiated, but we want to instantiate *before* the behavior code is run so we don't need to instantiate it twice
// but to be able to do that we need to know the action so we can pass in the correct scenario
$model = new exampleModel(['scenario' => 'view']);
}
}
authManager is simply a reference to a member variable inside an extension of the AccessRule class.
Is there anyway I can do this?
Well, if I get you right, you are looking for something like this:
public function behaviors()
{
$model = MyModel::find()->someQuery();
$action = Yii::$app->controller->action->id;
return [
'someBehavior' => [
'class' => 'behavior/namespace/class',
'callback' => function() use ($model, $action) {
//some logic here
}
]
];
}
Because behaviors() is just a method, you can declare any variables and add any logic that you want in it, the only one convention that you must follow - is that return type must be an array.
If you use your custom behavior, you are able to use events() method where you can bind your behavior's methods to certain events. E.g.
class MyBehavior extends Behavior
{
public function events()
{
return [
\yii\web\User::EVENT_AFTER_LOGIN => 'myAfterLoginEvent',
];
}
public function myAfterLoginEvent($event)
{
//dealing with event
}
}
In this example myAfterLoginEvent will be executed after user successfully login into application. $event variable will be passed by framework and depending of event type it will contain different data. Read about event object
UPDATE:
As I can see now my answer was more generic about events and behaviors. And now when you added code, I can suggest to you to override behavior's beforeAction($action) method with the following code:
public function beforeAction($action)
{
$actionID = $action->id;
/* #var $rule AccessRule */
foreach ($this->rules as &$rule) {
$model = &$rule->authManager['model'];
//now set model scenario maybe like this
$model->scenario = $actionID;
}
//now call parent implementation
parent::beforeAction($action);
}
Also take a look at AccessControl implementation of beforeAction method, it invokes for each rule allows method with passing current action to it as a parameter. So if you have class that extends AccessRule, you can either override allows($action, $user, $request) method or matchCustom($action) method to set appropriate model scenario. Hope this will help.
One more alternative:
override controller's runAction($id, $params = []) method. Here $id is actionID - exactly what you need. Check id, set appropriate model scenario and call parent::runAction($id, $params);

Laravel 4: Add username to every route url

How do I add username to url?
Example: www.domain.com/feeds/username
I want to add username to every route url.
::Route::
Route::get('feeds', array('before' => 'auth', 'as' => 'feeds', 'uses' => 'FeedsController#getFeedsPage'));
::Controller::
class FeedsController extends BaseController {
public function getFeedsPage() {
return View::make('feeds.index');
}
}
Thanks in advance.
Michael.
You may try this, declare the route like this (Notice {username}):
Route::get('feeds/{username}', array('before' => 'auth', 'as' => 'feeds', 'uses' => 'FeedsController#getFeedsPage'));
Declare the class:
class FeedsController extends BaseController {
// Use a parameter to receive the username
public function getFeedsPage($username) {
// You may access the username passed via route
// inside this method using $username variable
return View::make('feeds.index')->with('username', $username);
}
}
Now you may use a URI like these:
www.domain.com/feeds/username1
www.domain.com/feeds/michaelsangma

Categories