Assume, I have a model "Post" and I create two resource controllers for it - User/PostController and Admin/PostController.
So when I wish to consume the resource, my routes would look something like this:
/user/post/:id
/admin/post/:id
Is this correct according to the convention or am I doing it wrong?
It is recommended to use a separate model for its controller and vice versa. This will allow you to maintain consistency and readability in the application, keeping the application easy to development.
In the application where there are admin and user accounts, I suggest using Middleware + (roles + permissions) and do not stand out in routing /user/ and /admin/ a apply general /user/
In the my example I described, you can use one method in the PostController, for example: show, add permission (eg user-post-list, admin), add roles (e.g. admin, user), in the routing describe who has permission (eg user-post-list, admin) to given function (show) and assign: permissions to role, roles to user - one or many role to user. You can also add roles to the routing and assigning the appropriate permissions in controller.
You can load permissions from the file using fixtures, for example permissions.csv, roles.csv, role_permission.csv or use seeds and fixtures or only seeds - before make create roles and permissions
- create the appropriate migrations)
Second solution (and only using Seed) it not flexible, because enlarging the list of permissions and roles involves modifying the code as opposed to the first solution - loading them from the file.
Of course, there are other solutions. I think that the proposing by me has a simple logic, it is effective and flexible to changes.
Check this: https://itsolutionstuff.com/post/laravel-56-user-roles-and-permissions-acl-using-spatie-tutorialexample.html
or
https://www.google.pl/search?q=laravel+roles+and+permissions+tutorial&oq=laravel+roles+&aqs=chrome.4.69i57j69i60j69i65j0l3.11595j0j7&sourceid=chrome&ie=UTF-8
Here is how I go about this problem in Laravel, when having users with different access-types.
Lets say we have a model like you, called Post. Now what we will do is, add a scope to that model, which we'll define a bit further down here:
use App\Scopes\AdminScope;
class Post extends Model {
// Apply a global scope to this controller
protected static function boot(){
parent::boot();
static::addGlobalScope(new AdminScope);
}
In the router you define it as a regular resource route:
Route::resource('posts', 'PostsController');
In the Controller, you can fetch all Posts on the index method like normal. This will after we create our admin scope, return all the posts in the system for admin users, and those belonging to a specific user for regular users:
class PostsController extends Controller {
public function index(){
$posts = Post::all();
}
No comes the part where you differentiate wether returning all the posts in the system, or just the ones belonging to the current user, based on the user type that is logged in:
Create a new folder in your app-folder named Scopes. In this folder, create a new file called AdminScope.php which will look something like this:
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Auth;
class AdminScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
// IF THE CURRENT USER TYPE IS NOT ADMIN, ALTER THE QUERIES:
if( Auth::user()->type != "admin" ){
$builder->where('user_id', '=', Auth::user()->id)
}
}
}
Ofcourse, this last file you will need to alter to meet the requirements on how you differentiate between a normal user and an administrator.
The good thing about this approach, is that you now can apply this Scope to any model where you see it fit, and it will alter all the queries for non-admin users to only show the Models that they own where the scope is applied.
Note:
This is a global scope, that will apply to all the Eloquent Models where it has been added, and to all queries made for that model. If you want, you can also write conditional local scopes, which you can read more about here:
Local Scopes
Related
I am working on an app, in which each user is assigned a particular region. For example, user 1 belongs to a region 'Phoenix', user 2 to 'Scottsdale', and user 3 to 'Tempe'. The region of each user is defined in the region_id column.
The app has 3 separate guards for 3 different user models (it's a marketplace with regional managers, providers, and customers). Virtually every model in the app has a region_id column, identifying to which region the resource belongs to.
I would like to simplify how the resources are queried. Currently, I am filtering each model/resource directly in the controller, e.g.:
$customers = Customer::where('region_id', Auth::user()->region_id);
I would like to set such filters at the global level, to make sure that each authenticated user can see records only from their own region. It is important particularly for displaying the list of records in the current region, not checking whether the user can access/edit a particular single record.
I have looked into Query Scopes, but I am not able to access the currently authenticated user withing them. The only case when I can access the current user is when the global scope is defined in a closure, but this defeats the purpose of not having to define the same logic in each model (i.e. I cannot extract the filtering logic to a query scope class).
What would be a good alternative approach to consider? How can I set the global model filters based on a property of a currently authenticated user?
How about make a RegionalModel and extend all your Models with a region_id field? Then, in your RegionalModel create a scope like forUser(User $user) to apply your filter.
class RegionalModel extends Model {
public function scopeForUser($query, User $user) {
// Apply filter here
}
}
Then your Customer model might look like this
class Customer extends RegionalModel {
// Code here
}
Then when you want to get a list of customers
$user = Auth::user();
$customers = Customer::forUser($user)->get();
I posted a similar question as a possible bug (was not sure whether this behavior was something expected) on Laravel Framework issue tracker on GitHub and found the answer there.
Turns out, you cannot access the authenticated user in the global query scope constructor, but it is accessible in the apply() method.
So instead of:
public function __construct() {
$this->region_id = Auth::user()->region_id;
}
public function apply(Builder $builder, Model $model)
{
$builder->where('region_id', $this->region_id);
}
I had to do:
public function apply(Builder $builder, Model $model)
{
$region_id = Auth::user()->region_id;
$builder->where('region_id', $region_id);
}
Link to the issue on GitHub here.
I'm working on building a small CRUD app in Laravel 5. I have a few app wide settings such as "SiteTitle" and "BaseURL" I'd like to give my admins the ability to change from the UI. My whole app uses a base.blade.php template that gets extended to the different views+controllers, this is where these settings would most likely be used.
Something like:
<h1>{{ $setting->SiteName }}</h1>
I have the settings stored in a database table that are tied to a Setting.php model.
I'd rather not everyone of my controller methods query the database for these settings to just pass them up to the template base.blade.php.
What's the best way of creating some type of global setting variable I can reuse throughout the app?
Thanks in advance!
You could create a service provider, say SettingsServiceProvider, that loads all the settings from the database and then caches them. Then on subsequent page loads, it could return cached setting values rather than querying the database, which you should be rightfully concerned about.
Something as simple as:
class SettingsServiceProvider extends ServiceProvider
{
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->singleton('settings', function ($app) {
return $app['cache']->remember('site.settings', 60, function () {
return Setting::pluck('value', 'key')->toArray();
});
});
}
}
Assuming your settings model is called Setting as per Laravel’s naming conventions. You can then access settings like so:
<h1>{{ array_get(app('settings'), 'site.name') }}</h1>
If you wanted a prettier way of accessing settings, you could create a helper function:
function setting($key)
{
return array_get(app('settings'), $key);
}
Which would make usage like this:
<h1>{{ setting('site.name') }}</h1>
Almost emulating the config() helper function’s usage.
If your app uses multiple controllers then you could define a parent controller that gets extended by all your other controllers. Within the parent controller's constructor you can retrieve the settings data and store it in a class property which will be available to all of the child controller classes. Your child controllers can simply pass this data to the view.
I have a table full of Products. But, for each user is only allowed a group of Products to see. This is done by the table users_products_permissions. Each user has an instance of UsersPermissions table. In this table I set a range of restrictions to be applied on the user, restrictions to say each product a user is allowed to "see". This is done by a 'weak' (That's how we call in brasilian portuguese) table composed only by user_permission_id and product_id.
By the time of the requesition the requester user will be logged, of course.
The questions rests on: How to filter user's access to products based on it's restrictions?
Without checking in every requisition, manually, if the user model have access to this product. An automatic way, lets say...
In case any code is needed, just say it!
For 4.2:
Use a global scope to scope an entire model.
http://laravel.com/docs/4.2/eloquent#global-scopes
and
http://softonsofa.com/laravel-how-to-define-and-use-eloquent-global-scopes/
For 4.0:
http://www.laravel-tricks.com/tricks/global-scope-model and
Laravel Eloquent Query Builder Default Where Condition
<?php
class Product extends Eloquent {
public function newQuery()
{
return parent::newQuery()->where('permission', '=', 1);
}
}
You can use a Query Scope for this. Scopes allow you to easily re-use query logic in your models. To define a scope, simply prefix a model method with scope:
class User extends Eloquent {
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
public function scopeWomen($query)
{
return $query->whereGender('W');
}
}
Utilizing A Query Scope
$users = User::popular()->women()->orderBy('created_at')->get();
I am currently working on a web app that has been set up using the Repository/Service Layer Design Pattern, i.e. I have service layer that does any necessary business logic before running any methods within the repository. I have facades for each one of my models which access their respective service layers, and this has been fine for the most part. However, now that I am trying to set up Eloquent relationships, the facades seem to be causing a massive headache as I am not sure which direction I should be going.
Take the following code:
class Account extends Eloquent {
// Our table name
protected $table = "accounts";
// Our primary key
protected $primaryKey = "id";
/**
* Role Relationship
*
* Returns a list of roles associated with
* this account
*/
public function roles() {
return $this->hasMany('Role');
}
}
This will not work as is, because instead of using the entity class of Role, it is using the Role Facade. I have figured out a workaround for this, by setting an alias for the Entity with a slightly different name, such as RoleEntity so that
public function roles() {
return $this->hasMany('RoleEntity');
}
will work, however this doesn't seem like the most optimal solution.
My question is, is the practice ok? Or better yet, should this be happening at all? And if not, how do I fix it/where did I go wrong?
You have two classes with the same name in the same namespace. Use different namespaces so you can use the same class names.
I usually use \Models to locate my models classes.
At the top of each model file:
namespace Models;
In your controller or any part of your app:
\Models\Role::first();
Note that changing the namespace on your model will require you to add the namespaces of other classes i.e. Str, Eloquent, Url, Redirect, etc.
use Eloquent;
use URL;
In your model, you also have to pass the namespaces in the relationship functions, i.e.:
public function roles() {
return $this->hasMany('\Models\Role');
}
I have a controller/model for projects. so this controls the projects model, etc, etc. I have a homepage which is being controlled by the pages_controller. I want to show a list of projects on the homepage. Is it as easy as doing:
function index() {
$this->set('projects', $this->Project->find('all'));
}
I'm guessing not as I'm getting:
Undefined property: PagesController::$Project
Can someone steer me in the right direction please,
Jonesy
You must load every model in the controller class by variable $uses, for example:
var $uses = array('Project');
or in action use method
$this->loadModel('Project');
In my opinion the proper way to do this is add a function to your current model which instantiates the other model and returns the needed data.
Here's an example which returns data from the Project model in a model called Example and calls the data in the Example controller:
Using Project Model inside Example Model:
<?php
/* Example Model */
App::uses('Project', 'Model');
class Example extends AppModel {
public function allProjects() {
$projectModel = new Project();
$projects = $projectModel->find('all');
return $projects;
}
}
Returning that data in Example Controller
// once inside your correct view function just do:
$projects = $this->Example->allProjects();
$this->set('projects', $projects);
In the Example view
<?php
// Now assuming you're in the .ctp template associated with
// your view function which used: $projects = $this->Example->allProjects();
// you should be able to access the var: $projects
// For example:
print_r($projects['Project']);
Why is this "better" practice than loading both models into your controller? Well, the Project model is inherited by the Example model, so Project data now becomes part of the Example model scope. (What this means on the database side of things is the 2 tables are joined using SQL JOIN clauses).
Or as the manual says:
One of the most powerful features of CakePHP is the ability to link relational mapping provided by the model. In CakePHP, the links between models are handled through associations.
Defining relations between different objects in your application should be a natural process. For example: in a recipe database, a recipe may have many reviews, reviews have a single author, and authors may have many recipes. Defining the way these relations work allows you to access your data in an intuitive and powerful way. (source)
For me it's more reasonable to use requestAction. This way the logic is wrapped in the controller.
In example:
//in your controller Projects:
class ProjectsController extends AppController {
function dashboard(){
$this->set('projects', $this->Project->find('all'));
}
$this->render('dashboard');
}
Bear in mind that you need to create dashboard.ctp in /app/views/projects of course.
In the Page's dashboard view (probably /app/views/pages/dashboard.ctp) add:
echo $this->requestAction(array('controller'=>'projects', 'action'=>'dashboard'));
This way the logic will remain in the project's controller. Of course you can request /projects/index, but the handling of the pagination will be more complicated.
more about requestAction(). but bear in mind that you need to use it carefully. It could slow down your application.