Laravel 5.5 - Prevent additional attribute to be used during model update - php

The users table, besides others, have these fields: username, first_name, last_name. Each user can decide whether to show the username or the full name (first + last). This choice is stored inside a "settings" table.
To not perform repeated queries and calls to a function any time I need to show the name, I add the name to display to the user object as it is created, like $user->display_name = ... according to the user's choice.
The problem is that when the user updates the profile, Laravel tries to save this name inside a display_name field into the users' table, which doesn't exist and returns a 500 error. That also happens when the user tries to logout.
Is it possible to avoid that Laravel tries to store that value inside the database?
As suggested in other discussions I have already tried to give a default value to the attribute inside the User model, I've tried to set the attribute as protected, but nothing did work.

This is where the get{...}Attribute() function of a Model comes in handy. Say you want to access $user->full_name without actually saving full_name to the database. Since you have first_name and last_name, you can declare on your User model:
public function getFullNameAttribute(){
return $this->first_name." ".$this->last_name;
}
Laravel will parse what's between get and Attribute into a property available on the model, in this case either $user->full_name or $user->fullName.
To translate this to your use case, you can use something like:
In your User.php model:
public function getDisplayNameAttribute(){
if($this->settings == "use_full_name"){
return $this->first_name." ".$this->last_name;
} else if($this->settings == "use_username"){
return $this->username;
}
return "Not Configured...";
}
Note: You'll have to configure your if statements to determine what to return based on your settings.
Somewhere in a controller or view, you can call $user->display_name to have one of 3 things (determined by the logic/return statements above) displayed:
public function example(){
$user = User::first();
dd($user->display_name);
// $user->first_name." "$user->last_name, $user->username or "Not Configured..."
}
By doing this, when you access your $user, it will have a display_name property available that doesn't actually exist on the model, so you won't run into issues should you call $user->save();

Related

Laravel 8 user table column names

I am using laravel 8 with an existing user table. All is working as expected except the password reset link functionality. This is because my table has the email column name as "Email" instead of "email." Other applications use this table, so the column name cannot be changed. I can get the password reset link functionality working if I manually set the column name within the framework itself (example below).
File: /vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php
public function retrieveByCredentials(array $credentials)
{
// framework code that retieves the user record for email address
if ($res) {
$res->email = $res->Email;
}
// rest of frame work code
}
This seems a little "hacky." Is there a better approach to this?
Laravel would benefit greatly from more customization regarding the user's table (custom user table name, column names, etc.).
Laravel has mutators and accessors. This does that you can change behavior of ->email access or assigning it. Add this snippet to your User.php model.
class User {
public function getEmailAttribute()
{
return $this->attributes['Email'];
}
}
You can read the docs about it. The naming convention for the function is get{PropertyName}Attribute, if you define your function like so, you can easily overwrite property logic in Laravel. Making it use the column Email.

RESTful API different responses based on user roles

i'm using Laravel as my PHP framework. its a convention to put index show store ... functions in controllers.
i have 2 types of users(Admin & normal user). lets assume there is an Order(in restaurant) model and i want to implement index function for its controller.
a user can have more than one Order.
what i need is that this function:
if admin is calling this API: returns all Orders
if normal user is calling this API: returns just Orders owned by this user
i searched but i couldn't find anything(tbh i didn't know what to search).
once i did this as below which i didn't like because it looks two different functions gathered in one:
if ($user->role == admin) {
// fetch all orders
} else if ($user->role == normal_user) {
// just find user orders
}
so my question is what's best approach to achieve what i want?
Such a REST API endpoint is typically a search allowing multiple filters, sorting and pagination. If so it is completly fine to apply different defaults for filters and also restrict filters to roles.
I would auto apply a filter user=currentUser for missing admin role and return a forbidden if a user without the admin role tries to apply a user filter for a different user.
With this approach you give admins also the functionality to search for offers of a specific user and you only need one search api to be used by the controller.
Why don't use an if statement?
You could make a scope on the model but then you'll still have an if.
What about this?
if ($user->role == admin) {
Order::all();
} else if ($user->role == normal_user) {
$user->orders()->get();
}
Or make it an inline if
$user->role == admin ? Order::all() : $user->orders()->get();
IMO the best practice here is to make a different Admin/OrderController.php
Then with middleware check wat, the role of the user is, and then redirect them to the admin controllers.
Since you'll probably also want an update and delete, or other functions only accesible by an Admin
I had a similar question myself a while ago and ended up with this strange solution to avoid that if/else block.
Assumptions
I assumed the existence of an helper method in the User model called isNot($role) to verify the if the user's role matches or not the given one.
This is just an example to give the idea of the check, but you should implement the condition as you like.
Second assumption I made is that each order has a user_id field which will reference the owner of that order though his id (FK of 1:N among user and order).
Implementation
public function index(Request $request)
{
$orders = Order::query()
->when($request->user()->isNot('admin'), function ($query) use ($request) {
return $request->user()->orders();
// Or return $query->where('user_id', $request->user()->id);
})
->paginate();
return OrderResource::collection($orders);
}
The when method is the key here. Basically you call it like: when($value, $callback) and if $value is false the callback won't be executed, otherwise it will.
So for example, if the user is not an admin, you will end up executing this query:
Order::paginate();
that would fetch all the order with pagination (note that you could swap paginate with get.
Otherwise, the callback is gonna be executed and you will execute the paginate method on $request->user()->orders(); (orders called like a method is still a query builder object, so you can call paginate on it).
The query would be:
$request->user()->orders()->paginate();
If you instead opted for the second solution in the callback you would basically add a where condition (filtering on the user_id of the orders) to the main scope to get only the user's orders.
The query would be:
Order::query()->where('user_id', $request->user()->id)->paginate();
Finally, to better control what's sent back as response I use Laravel's API Resource (and I really suggest you to do so as well if you need to customize the responses).
NOTE: The code might have syntax and/or logical errors as it was just an on the fly edit from production code, and it hasn't been tested, but it should give an overall idea for a correct implementation.
it would be better to include the if/else in your order modal like this:
class Order extends Model {
....
static function fetchFor (User $user) : Collection
{
return $user->isAdmin() ? self::all() : self::where("user_id",$user->id);
}
}
then you can call this method on your controller
public function index()
{
return view('your-view')->with('orders',Order::fetchFor(Auth::user())->get())
}
You can create scope in Order class...
For example you have field user_id in Order, for detect user
class Order
{
...
public function scopeByRole($query)
{
if (!Auth::user()->isAdmin())
$query = $query->where('user_id', Auth::user()->id);
return $query;
}
}
in you controller just get all Orders with scope:
$orders = Order::byRole()->get();
it return you orders by you role
Also you need have in class User function for detect role, example
class User
{
public function isAdmin()
{
// you logic which return true or false
}
}

Pass the data in the many to many relation ship in laravel

I have create a form that will show the user the event title, event description. And the code
public function show($id){
$showevents = Events::findOrFail($id);
return view('events.show',compact('showevents'));
}
This data i pass dont have the user data, but it will give me the specific event data. My question is how to pass the user data along with this?
Because my event table dont have the user information, it all in the user table.
I try {{$showevents->user->name}} in the view form, but it doesnt give me the information of the user.
You will be needing the user-id too if you want to retrieve infomation about the user and send it to the view file..
One thing you can do for getting the user-id anywhere is setting the user-id in Session variable when the user logs-in as:
\Session::set('user_id',$userID);
Then you can get the user-id anywhere in any controller using
$id = \Session::get('user_id');
For example in your case
public function show($id){
$user_id = \Session::get('user_id');
$user = User::findOrFail($user_id);
$showevents = Events::findOrFail($id);
return view('events.show',compact('showevents','user));
}
Now where you will set the Session variable depends entirely upon your code..I used to set after a user has logged in successfully and also rembember to remove session data when logging out as..
\Session::flush();
First of all {{$showevents->user->name}} of course will fail because you don't have any connection between events and user.
If you want to make connection between it you going to need user_id inside event table.
If you want to get user data but don't have any connection, you need to do in seperate query.
$user = User::findOrFail($user_id);
return view('events.show',compact('showevents', 'user'));
You should use Eloquent's relationship functions or wrap them inside you Models relationship functions and use them.
check the Laravel documentation

Update Specific Attributes or Fields

PROBLEM 1:
When I try to save() any Yii Model, it updates all fields in the row.
The problem is: When I try to save model users, even if has no PASSWORD to update, it get the database value(already hashed) and hash again.
How can I do to YII only update fields that I want?
Code:
$user = Users::model()->findByAttributes(array('username'=>$this->username));
$user->ip = $_SERVER['REMOTE_ADDR'];
$user->save();
Users.php (Model):
public function beforeSave() {
if (!empty($this->password))
$this->password=$this->hashPassword($this->password);
return true;
}
PROBLEM 2:
I have an API that can create USERS.
API Tutorial: http://www.yiiframework.com/wiki/175/how-to-create-a-rest-api/
When I have crypter_password in the database, instead password, I got the error: Parameter password is not allowed for model Users, because the API validate parameters using $model->hasAttribute().
How can I fix the API actionCreate to allow custom parameters?
According to Yii's doc: http://www.yiiframework.com/doc/api/1.1/CActiveRecord#save-detail
public boolean save(boolean $runValidation=true, array $attributes=NULL)
$attributes - array - list of attributes that need to be saved. Defaults to null, meaning all attributes that are loaded from DB will be saved.
You can pass in an array of fields that you want to save.
Eventhough the other answers listed here are not wrong, they are definitely not really developer friendly and it's extremely easy to forget to add the attributes to the save line.
Here is a developer friendly way of working.
In your model, add the following attribute:
private $_aAttributesBackup;
In this variable, we will store an exact copy of the current model. To do this, the following afterFind method needs to be added:
public function afterFind()
{
$this->_aAttributesBackup = $this->attributes;
}
Almost there. At this point, the model will store all of his attributes in the attributesBackup field which makes it easier to compare. To make it easier, we also need a method that will check if the specified attribute has a backup value. We do this by adding the following code into our model:
public function getOriginalAttribute($sAttribute)
{
if ($this->_aAttributesBackup)
{
return $this->_aAttributesBackup[$sAttribute];
}
return NULL;
}
Now, how about checking if the password has been changed? Simple, by adding the following beforeSave code:
public function beforeSave()
{
if ($this->getOriginalAttribute('password') != $this->password)
{
$this->password = sha1($this->password);
}
return parent::beforeSave();
}
Et voila. Now everytime you execute the code $Model->save(); the system will check if the password has been changed, If the password is changed, it will hash it again, if it is not changed, it won't be hashed again.
Save () inserts a row into the database table if its isNewRecord property is true. Otherwise, it will update the corresponding row in the table (usually the case if the record is obtained using one of those 'find' methods.)
What you have to do is update specific field so you can use SaveAttributes and it accepts the array of string values that have been updated for example demo code is as follow
$user = Users::model()->findByAttributes(array('username'=>$this->username));
$user->ip = $_SERVER['REMOTE_ADDR'];
$user->SaveAttributes(array('ip'));

Custom object returned by join in Codeigniter Active record

I have these two database tables:
locations
id
name
users
id
location_id
last_name
first_name
I also have User and Location class, they both extends Model and contain some custom methods. For example, there is a get_full_name() method in the User class.
I am using the following codes to load the data,
$this->db->select('users.id, locations.name as location_name, users.last_name, users.first_name');
$this->db->from('users');
$this->db->join('locations', 'users.location_id = location.id');
$query = $this->db->get();
$users= $query->custom_result_object('User'); //now $users is an array of User object
The custom_result_object is a built-in but undocumented function in Codeigniter. It accepts a string of class name and will use it to create objects of that class.
With the above codes, I can access the data like this,
foreach($users as $user)
{
echo $user->id;
echo $user->get_full_name();
echo $user->location_name;
}
But as you can see, location_name is now a field of the User object, what I want is the User object has a field of Location object that allows me to use it like this,
foreach($users as $user)
{
echo $user->id;
echo $user->get_full_name();
echo $user->location->name; // Location is an object
}
I don't want to use DataMapper ORM or any other ORMs, and would also like to avoid the N+1 query performance issue.
How can I do this with minimal amount of codes?
Note: I made up this example just for the demonstration purpose, in my real case, there are quite a lot of tables and classes (which has custom methods defined on them).
Many thanks to you all.
Have you considered the following?
class User {
var location;
public function location()
{
if ( empty($this->location) )
{
$ci =& get_instance();
$this->location = $ci->db->get_where('locations', array('id' => $this->location_id))->row();
}
return $this->location;
}
}
This way, you completely avoid the overhead of loading the location's table unless you need the data, in which case you cache it inside the object for future use.
I had questioned the same for myself but found the simplest way was to iterate through each result (in your case, user) and select the corresponding child (location) and set to the field in the parent object (user->location).
The only alternative way I can think of would involve editing (or extending and creating your own db set) the db_result class to use getters/setters instead of direct fields - perhaps with call_user_func_array(). At that point, you could code your setId() function in the user model to set the $location field upon receiving the userId/foreign key.
get location id from table user, use it's number to grab from table locations by id a object. and put in it.

Categories