I'm currently reading up on proper MVC, and I'm refactoring some code of my current project. It's written in Laravel. I'll have simplified the problem to this:
I have a domain object 'User' and 'Log'. A User has many Logs.
User domain object:
class User extends Authenticatable implements UserInterface
{
protected $fillable = ['surname', 'name','email'];
protected $hidden = [
'password', 'remember_token',
];
public function logs()
{
return $this->hasMany('App\Models\Log');
}
}
Log domain object:
class Log extends Model implements LogInterface
{
public $timestamps = false;
protected $fillable = [
'log_date',
'log_hours',
'log_minutes'
];
protected $dates = ['log_date'];
public function user()
{
return $this->belongsTo('App\Models\User');
}
}
In my view, I want to display the total hours logged of a certain day. I'm currently injecting a class in my controller which has a method calcHoursPerDay($user_id) which injects the LogRepository to get the Logs of that user on a given date and return the calculated value to the view.
Reading up on proper MVC, I'm inclined to drop the class and create a method for my User domain object called HoursOnDay($date). So keeping the bussiness logic in the Model.
Is this the way of using MVC, and if not, what would the proper approach?
Yes, that's the proper way to do things in Laravel and MVC. calcHoursPerDay() should be part of User or Log model. Also, look at scopes doc, I guess that's what you want to use:
https://laravel.com/docs/5.1/eloquent#query-scopes
Related
I'm new in Laravel, I want to know where is the correct place where define functions that query table from DB. In Model or in Controller?
Example:
public function insertUser($firstname, $lastname, $email) {
$user = new User();
$user->firstname = $firstname;
$user->lastname = $lastname;
$user->email = $email;
$user->save();
return $user;
}
The function above where I should declare? Models or Controllers?
Edit:
For example: I need to create a function that return male authors that live in USA and their books. I define AuthorController that use Author (Model). What's the right way to define this function? I write a function in my controller that accept gender and nation as arguments, like:
public function getAuthoursByGenderAndNation($gender, $nation) {
$authors = Author::with("books")->where("gender", "=", $gender)->where("nation", "=", $nation)->get();
return $authors;
}
Or I define a generic function that returns all authors with their books and then apply where clause on function that call this generic function? Like:
public function showAuthors(Request $request) {
$gender = $request->get("gender");
$nation = $request->get("nation");
$authors = $this->getAuthors()->where("gender", "=", $gender)->where("nation", "=", $nation)->get();
return view("authors", ["authors" => $authors]);
}
public function getAuthors() {
$authors = Author::with("books");
return $authors;
}
keep in mind that all application logics should be in controller, and all data operations should be in model. in your question insert user is a application logic, so you should place that on controller, but if you want to define how data is managed, place that method in model. For example, you want a model has ability to retrieve a collection with some condition, may be a user with female gender only so you can Access it via Modell::getFemale()
The function you mention, should be used within a controller. I would recommend that you get a grasp on how MVC works before you dive in Laravel.
Reading that may be useful to you
MVC Concept
Laravel Docs
PHP MVC Tutorial
As according to MCV recommendations.
M (model) should be fat and C (controller) should be thin.
you should write your all database transaction related code in model. Even you can create repositories for database queries.
Your controller should be thin, so you should write only logical code there, like calling model function.
Example:
UserController.php
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\Request;
class UserController extent Controller {
use App\User;
protected $_user;
public function __construct(User $user) {
$this->_user= $user;
}
function saveUser(Request $request) {
$user->fill($request->all());
$user->save();
// or you can directly save by $user->create($request->all());
}
}
This is how you can directly fill data to your User model with $fillable attribute defined there as
$fillable= ['name','email','password'];
If you define your model under the conventions of Eloquent you can simply use the built in Eloquent methods to insert your user as demonstrated in the documentation.
https://laravel.com/docs/5.3/eloquent#inserting-and-updating-models
In the wider scope of your question: 'where to define functions that query the DB table'.
I would suggest typically defining these on the model and looking to make use of the structures provided by Eloquent, for example defining scoped queries on your model.
The code in your controller would then call methods on your model eg.
Model::create();
It also appears you are trying to insert users. I would strongly suggest you look into using Laravel's built in Authentication structures. You'll find these very powerful.
https://laravel.com/docs/5.3/authentication
Hope this helps get you started.
I'm creating a system in which there are users, and user have many user roles. The user roles also contain permissions. Some fields are protected, so that they cannot be overwritten without the user possessing a specific permission.
For example, a user may have the attribute "email" which cannot be changed by the user, unless the user has the permission "update-email-address".
I originally intended to implement this concept as a trait or an abstract class, but I can't figure a way of doing this which doesn't involve either overloading the Eloquent Model constructor method, or else completely overloading another method.
What I'm hoping to do, is to be able to specify an array in a model like below, and by using a tract or extention, somehow prevent updating a model attribute:
/**
* The attributes that should only be updatable by given user with the
* specified permission
*
*/
public $update_only_by_permission = [
'email' => ['update-email-address'],
];
Is there a way to achieve this?
I stumbled across a way to provide a boot method for a trait extending a model, and was able to achieve this through the following:
Trait used on many Eloquent Models:
use Auth;
trait AttributeVisibleByPermissionTrait {
/**
* Stops updating of the object attributes if the authenticated
* user does not have the given permission provided in the
* $object->update_only_by_permission array.
*/
public static function bootAttributeVisibleByPermissionTrait(){
static::updating(function($object){
foreach ($object->update_only_by_permission as $attribute => $permissions) {
foreach ($permissions as $permission) {
if(!Auth::User()->can($permission)) {
unset($object->$attribute);
}
}
}
});
}
}
User Model:
class User extends Authenticatable
{
use AttributeVisibleByPermissionTrait;
/**
* The attributes that should only be updated by given user auth permissions
*
* #var array
*/
public $update_only_by_permission = [
'email' => ['update-email-address'],
];
}
I have a problem, I can not use policies in laravel 5.2.
I have 2 tables, students and tasks.
I try to apply a policy to prevent editing of a task by changing the url, but I always get the message This action is unauthorized although the task is the correct user.
Policy Code:
<?php
namespace App\Policies;
use App\Models\Student;
use App\Models\Task;
class TasksPolicy
{
public function edit(Student $student, Task $tasks)
{
return $student->id === $tasks->student_id;
}
}
Code in AuthServiceProvider.php
<?php
namespace App\Providers;
use App\Models\Task;
use App\Policies\TasksPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Task::class => TasksPolicy::class
];
And then the call in the TaskController.php file:
public function edit($id)
{
$tasks = Task::findOrFail($id);
$this->authorize('edit', $tasks);
return view('tasks.edit', compact('tasks'));
}
I think the code is good because I've revised several times, but as I said earlier I always get the message This action is unauthorized although the task is to edit the user.
http://i.imgur.com/2q6WFb3.jpg
What am I doing wrong? As I can use the policy correctly?
you are using "===" which means that both side data and datatype will match.May be your data are matched,not datatype,you may try using "=="
public function edit(Student $student, Task $tasks)
{
return $student->id == $tasks->student_id;
}
Two things: one is the name of the method and the other is the order of parameters. The method name should be 'update', not 'edit' - these are predefined, at least in later versions of Laravel. You might be getting the authorization error because the name 'edit' is not recognized by Laravel, so the policy for update is never defined.
The order of arguments also matters. When there are parameters passed to policy methods, the User model has to be the first parameter, followed by all the others.
public function update(User $user, [... other objects...])
So, you'd have
update(User $user, Student $student, Task $tasks)
Laravel will inject the Authenticated User Model but other objects have to be passed directly.
$this->authorize('edit', $student, $tasks);
Hopefully that will work.
If your Student class extends User Class, you may be thinking that you can substitute Student for User in the method prototype. You can't do that - that's a different method altogether.
I have several models, all of which need the one before it to be accessed. The examples below describe a similar situation
<?php namespace backend\Models;
use Illuminate\Database\Eloquent\Model;
class Geo_Locations extends Model {
protected $table = 'geo_locations';
protected $fillable = ['id', 'name', 'population', 'square_miles', 'comments'];
public function state(){
return $this->hasMany('backend\Models\Geo_State', 'id', 'state_id');
}
}
The "Geo_State" model
<?php namespace backend\Models;
use Illuminate\Database\Eloquent\Model;
class Geo_State extends Model {
protected $table = 'geo_states';
protected $fillable = ['id', 'name', 'political_obligation', 'comments'];
public function city(){
return $this->hasMany('backend\Models\Geo_City', 'id', 'city_id');
}
}
And then the "Geo_City" model would be
<?php namespace backend\Models;
use Illuminate\Database\Eloquent\Model;
class Geo_City extends Model {
protected $table = 'geo_cities';
protected $fillable = ['id', 'name', 'population', 'comments'];
public function miles(){
return $this->hasMany('backend\Models\Geo_Mile', 'id', 'mile_marker');
}
}
and it can continues but for the sake of ease, I'll stop there. What I would like to accomplish, and I'm sure many others would as well. Would be a way to retrieve all data related to one of the main models. For example, I want to get all the cities and their respective states within a location. What I would like for a result is something like
print_r(json_encode($a->getChildren(), JSON_PRETTY_PRINT));
should print out
{
"Geo_Location":{
"name":"United States",
"population":"300 Some Million",
"comments":"they have a lot of issues",
"Geo_State":{
"name":"Texas",
"population":"Some big number",
"comments":"they have a lot of republicans",
"Geo_City":{
"name":"Dallas",
"population":"A lot",
"comments":"Their food is awesome"
}
}
}
}
I have some of the code in a gist, while it may be poorly documented it's all I've been working with and it's my third attempt at doing this.
Individual calls to the route /api/v1/locations/1 would return all the info for United States but will leave out the Geo_State and below. A call to /api/v1/state/1 will return Texas's information but will leave out the Geo_Location and the Geo_City.
The issue in what I have is that it's not recursive. If I go to the route /api/v1/all it's suppose to return a json array similar to the desired one above, but it's not getting the children.
It sounds like what you want is to load the relationships the model has when you call that model. There are two ways I know you can do that in laravel using eager loading.
First way is when you retrieve the model using the with parameter, you can add the relationship and nested relationships like below.
$geoLocationCollectionObject =
Geo_Location::with('state', 'state.city', 'state.city.miles', 'etc..')
->where('id', $id)
->get();
This will return a collection object - which has a function to return the json (toJson). I'm not sure how it handles the relationships (what your calling children) so you may need to make a custom function to parse the collection object and format it how you've specified.
toJson Function
Second option is similar to the first but instead you add the with parameter (as protected $with array) to the model so that the relationships are loaded everytime you call that model.
Geo_Location class
protected $with = array('state');
Geo_State class
protected $with = array('city');
Geo_City class
protected $with = array('miles');
etc...
Now when you retrieve a Geo_Location collection object (or any of the others) the relationship will already be loaded into the object, I believe it should be recursive so that the city miles will also be called when you get a state model for instance - but haven't tested this myself so you may want to verify that.
I use ChromeLogger to output to the console and check this myself.
If you do end up needing a custom json_encode function you can access the relationship by doing $geoLocation->state to retreive all states and then for each state $state->city etc... See the docs for more info.
Laravel Docs on Eager Loading
I wasn't really sure what was going on in the code you linked, but hopefully this helps some.
So after a bit of fiddling around, turns out that what I am looking for would be
Geo_Locations::with('state.city')->get();
Where Geo_Locations is the base model. The "state" and the "city" would be the relations to the base model. If I were to return the query above, I would end up getting an json array of what I am looking for. Thank you for your help Anoua
(To make this more dynamic I'm going to try to find a way to get all of the names of the functions to automate it all to keep on track with what my goal is. )
Everytime I'm writing a Laravel model it just gives me a feeling of messy code. I have relationships and other model functions specially when using domain driven design. So I though about separating relationships and functions.
Example I have a User class that extends Eloqeunt:
class User extends Eloquent{}
and inside this class I have register functions and password hashing functions etc. Also, we can declare the relationships so:
class User extends Eloquent{
function post(){
return $this->hasMany('POST');
}
}
For some reason this smells funky to me. My solution was to create a Entities folder and inside create a User folder which will hold 2 files one would be UserRelationship which would hold of the the relationships for this class:
class UserRelationship extends Eloquent{
function post(){
return $this->hasMany('POST');
}
}
and a second which would be the actual User class where I would write all of the functions and this class would extend the UserRelationship class instead of Eloquent:
class User extends UserRelationship{
public static function register($email, $password, $activate_token)
{
$user = new static(compact('email', 'password', 'activate_token'));
$user->raise(new UserWasRegistered($user));
return $user;
}
}
What do you guys think of this approach I am relatively new to all this so I don't know if this is bad practice or to much work for little reward. What do you guys recommend?
For a user model, it is too much work. The easiest way and still a better approach is to define the relationship in the user model. If for example it is a post model where you have relationships for post to "user, comment, reply etc" then you can attempt splitting your relationships