How to reload/refresh model from database in Laravel? - php

In some of my tests, I have a user model I have created and I run some methods that need to save certain attributes. In rails, I would typically call something like user.reload which would repopulate the attributes from the database.
Is there a way in laravel to do that? I read through the api and couldn't find a method for it: http://laravel.com/api/4.1/Illuminate/Database/Eloquent/Model.html Any ideas on the "right" way to do this?

There was a commit submitted to the 4.0 branch made in August to add a reload() method, but so far it hasn't been merged with the newer Laravel branches.
But... Laravel 5 is providing a "fresh()" method that will return a new instance of the current model. Once you're using Laravel 5.0 or newer, you can reload a model like this:
$model = $model->fresh();
Note that fresh() doesn't directly update your existing $model, it just returns a new instance, so that's why we need to use "$model = ". It also accepts a parameter which is an array of relations that you want it to eager load.
If you aren't yet using Laravel 5 but you want the same functionality, you can add this method to your model(s):
public function fresh(array $with = array())
{
$key = $this->getKeyName();
return $this->exists ? static::with($with)->where($key, $this->getKey())->first() : null;
}
Update: If you're using Laravel 5.4.24 or newer, there is now a $model->refresh() method that you can use to refresh the object's attributes and relationships in place rather than fetching a new object like fresh() does. See Jeff Puckett answer for more specifics on that.

Thanks to PR#19174 available since 5.4.24 is the refresh method.
$model->refresh();
This way you don't have to deal with reassignment as is shown in other answers with the fresh method, which is generally not helpful if you want to refresh a model that's been passed into another method because the variable assignment will be out of scope for the calling contexts to use later.

refresh() is a mutable operation: It will reload the current model instance from the database.
fresh() is an immutable operation: It returns a new model instance from the database. It doesn't affect the current instance.
// Database state:
$user=User::create([
'name' => 'John',
]);
// Model (memory) state:
$user->name = 'Sarah';
$user2 = $user->fresh();
// $user->name => 'Sarah';
// $user2->name => 'John'
$user->refresh();
// $user->name => 'John'

I can't see it either. Looks like you'll have to:
$model = $model->find($model->id);
You can also create one yourself:
public function reload()
{
$instance = new static;
$instance = $instance->newQuery()->find($this->{$this->primaryKey});
$this->attributes = $instance->attributes;
$this->original = $instance->original;
}
Just tested it here and it looks it works, not sure how far this goes, though, Eloquen is a pretty big class.

I believe #Antonio' answer is the most correct, but depending on the use case, you could also use a combination of $model->setRawAttributes and $model->getAttributes.
$users = User::all();
foreach($users as $user)
{
$rawAttributes = $user->getAttributes();
// manipulate user as required
// ..
// Once done, return attribute state
$user->setRawAttributes($rawAttributes);
}
The primary downside to this is that you're only "reloading" the data attributes, not any relationships you've altered, etc. That might also be considered the plus side.
EDIT
As of L5 - fresh() is the way to go

Related

Class name must be a valid object or a string Laravel 5.7

I want to save a new record, but Laravel gives me this error Class name must be could object or a string. my code;
public function store(Request $request)
{
$question = new Question;
$question->question = $request->question;
$question->question_type_id = $request->question_type;
$question->user_id = $request->Auth::id();
$question->save();
}
Note: My PHP version is latest .i.e., 7.1.9
I assume you created an input field that has a name of the user’s id and you are trying t access it with
$request->Auth::id()
Which for a user with a user id of one, I assume you are asking to pass request item $request->1.
This won’t work as Auth::id() is being passed as a literal. Accessing the request object it’s complaining saying ‘hey, I can’t accept a class as a variable name, please stop!
If this is in fact what you are trying to do, and Auth::id() works (I never used that, I typically use Auth::user()->id), then you would have to pass the function within parentheses like this:
$request->(Auth::id())
right syntax for dynamic object key is :
$question->user_id = $request->{Auth::id()};
or if you just want auth id, then it is
$question->user_id = Auth::id();
I have fixed this issue, thanks to aynber for sorting out my issue. I have changed
$question->user_id = $request->Auth::id();
to
$question->user_id = Auth::id();

Laravel Auth User add custom data

We are currently working on an application with a Google Login with Laravel with Socialite. We have a Auth user who gets a permission number ex. 264. We have made a function which returns an array with all binary numbers this permission number is made off.
Because calling this function every single time a page loads may be kinda heavy, we thought of adding this once when the Auth::user() is created. We thought of adding a custom constructor in the Model, but we can't make it work.
function __construct($attributes = array()) {
parent::__construct($attributes);
$this->permissionsArray = PermissionHelper::permissionConverter($this->permissions);
}
But we can't get it to work, $this doesn't have values when calling this function.
TLDR;
Directly after making the Auth user I want to call the permissionConverter function and save the data to the user so we can use it more often. Any suggestions on how to do this?
EDIT: I checked all answers out today, succeeded with one of them, but I assumed Laravel put the authenticated user in the SESSION or something. I found out it doesn't and it gets all the data from the database every request. We couldn't do what we requested for unfortunately. So I just had to refactor the script and make it as efficient as possible (although it became a bit less readable for less experienced programmers).
Thanks for the help :D
Maybe you can use this solution ? https://stackoverflow.com/a/25949698/7065748
Create a on the User Eloquent model a boot method with
class User extends BaseModel {
public static function boot() {
static::creating(function($model) {
$model->permissionsArray = PermissionHelper::permissionConverter($model->permissions);
});
// do the same for update (updating) if necessary
}
}
Can't you just use this method ?
If new user:
$user = new User(); // or User:create(['...']) directly
$user->name = 'toto';
// and all other data
or
$user = Auth::user();
then
$user->permissionsArray = PermissionHelper::permissionConverter($user->permissions);
$user->save();

Non-public accessible function in CakePHP

I have built a simple Notification system in my Cake app that I want to have a function that will create a new notification when I call a certain method. Because this is not something the user would actually access directly and is only database logic I have put it in the Notification model like so:
class Notification extends AppModel
{
public $name = 'Notification';
public function createNotification($userId, $content, $url)
{
$this->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
}
And then whenever I want to create a notification within my app I just do:
$this->Notification->createNotification($userId,'Test','Test');
However this doesn't work! The controller is talking to the model fine, but it doesn't create the row in the database... I'm not sure why... but it would seem I'm doing this wrong by just doing all the code in the model and then calling it across the app.
Edit: Based on answers and comments below, I have tried the following the code to create a protected method in my notifications controller:
protected function _createNotification($userId, $content, $url)
{
$this->Notification->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
Now the thing that is stumping me still (apologies if this is quite simple to others, but I have not used protected methods in CakePHP before) is how do I then call this from another controller? So for example If have a method in my PostsController and want to create a notification on successful save, how would I do this?
I thought about in my PostsController add method:
if($this->save($this->request-data){
$this->Notification->_createNotification($userId,'Test','Test');
}
But being protected I wouldn't be able to access the method from outside of the NotificationsController. Also I'm using the same syntax as if I was calling a function from a model so again it doesn't feel right.
Hopefully someone can help me out and get me back on track as this is a new area to me.
the controller should pass all data to the model
$this->createNotification($this->request->data);
the model then can use the data:
public function createNotification(array $data) {
$key = $data[$this->alias]['key'];
$data[...] = ...;
$this->create();
return $this->save($data);
}
you never ever try to access the controller (and/or its request object) from within a model.
you can also invoke the method from other models, of course:
public function otherModelsMethod() {
$this->Notification = ClassRegistry::init('Notification');
$data = array(
'Notification' => array(...)
);
$this->Notification->createNotification($data);
}
and you can make your methods verbose, but that usually makes it harder to read/understand/maintain with more and more arguments:
public function createNotification($userId, $content, $url) {
$data = array();
// assign the vars to $data
$data['user_id'] = $userId;
...
$this->create();
return $this->save($data);
}
so this is often not the cake way..
Methods in a model are not "publicly accessible" by definition. A user cannot call or invoke a method in a model. A user can only cause a controller action to be initiated, never anything in the model. If you don't call your model method from any controller, it's never going to be invoked. So forget about the "non-public" part of the question.
Your problem is that you're working in the model as if you were in a controller. There is no request object in a model. You just pass a data array into the model method and save that array. No need for $this->request. Just make a regular array(), put the data that was passed by the controller in there and save it.
The whole approach is totally wrong in the MVC context IMO and screams for the use of the CakePHP event system. Because what you want is in fact trigger some kind of event. Read http://book.cakephp.org/2.0/en/core-libraries/events.html
Trigger an Event and attach a global event listener that will listen for this kind of events and execute whatever it should do (save something to db) when an event happens. It's clean, flexible and extendible.
If you did a proper MVC stack for your app most, if not all, events aka notifications should be fired from within a model like when a post was saved successfully for example.
This is what I have ended up doing. While it certainly isn't glamorous. It works for what I want it to do and is a nice quick win as the notifications are only used in a few methods so I'm not creating a large amount of code that needs improving in the future.
First to create a notification I do the following:
$notificationContent = '<strong>'.$user['User']['username'].'</strong> has requested to be friends with you.';
$notificationUrl = Router::url(array('controller'=>'friends','action'=>'requests'));
$this->Notification->createNotification($friendId,$notificationContent,$notificationUrl);
Here I pass the content I want and the URL where the user can do something, in this case see the friend request they have been notified about. The url can be null if it's an information only notification.
The createNotification function is in the model only and looks like:
public function createNotification($userId, $content, $url = null)
{
$this->saveField('user_id',$userId);
$this->saveField('content',$content);
$this->saveField('url',$url);
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
This creates a new record in the table with the passed content, sets its status to 0 (which means unread) and the date it was created. The notification is then set as read when a user visits the notifications page.
Again this is most probably not an ideal solution to the problem outlined in this question... but it works and is easy to work with And may prove useful to others who are learning CakePHP who want to run functions from models when building prototype apps.
Remember nothing to stop you improving things in the future!
First of all, you can improve your last solution to do one save() (instead of 5) the following way:
public function createNotification($userId, $content, $url = null){
$data = array(
'user_id' => $userId,
'content' => $content,
'url' => $url,
'datetime' => date('Y-m-d H:i:s'),
'status' => 0
);
$this->create();
$this->save($data);
}
When I began programming CakePHP(1.3) more than a year ago I also had this problem.
(I wanted to use a function of a controller in any other controller.)
Because I didn't know/researched where to place code like this I've done it wrong for over a year in a very big project. Because the project is really really big I decided to leave it that way. This is what i do:
I add a function (without a view, underscored) to the app_controller.php:
class AppController extends Controller {
//........begin of controller..... skipped here
function _doSomething(){
//don't forget to load the used model
$this->loadModel('Notification');
//do ur magic (save or delete or find ;) )
$tadaaa = $this->Notification->find('first');
//return something
return $tadaaa;
}
}
This way you can access the function from your Notification controller and your Posts controller with:
$this->_doSomething();
I use this kind of functions to do things that have nothing to do with data submittance or reading, so i decided to keep them in the app_controller. In my project these functions are used to submit e-mails to users for example.. or post user actions to facebook from different controllers.
Hope I could make someone happy with this ;) but if you're planning to make a lot of these functions, it would be much better to place them in the model!

Passing two arrays from controller in cakePHP

I'm trying my best to learn MVC and cakePHP and I had a question about passing arrays to the view. Currently, I have some basic code below.
class AwarenesscampaignsController extends AppController {
public function view($id = null) {
$this->Awarenesscampaign->id = $id;
$this->set('data', $this->Awarenesscampaign->read());
}
This is what I "think" is currently happening.
AwarenesscampaignsController is set up. The view paramater requests id and matches it up with the Model, Awarenesscampaign. This matches up with the database and returns an array which is set to the variable "$data", and then the view is loaded.
My first question: is my understanding accurate?
What I would like to do is with this is to be able to pass another array, from a different model. For instance, I would like to query the table Posts (Controller: PostsController/ Model: Post).
For instance, my first attempt was to do the following inside the function:
$this->Post->find('all');
But this yields the error:
Indirect modification of overloaded property AwarenesscampaignsController::$Post has no effect [APP/Controller/AwarenesscampaignsController.php, line 20]
Additionally, I'm not sure how I would send both variables to the view.
To recap:
Was my understanding accurate?
How do I query a variable from another controller/model?
How do I sent this array to the appropriate view for that controller?
Thanks,
-M
You're on the right lines, and aren't doing it wrong per se. I would say your understanding is pretty good for a beginner.
By default Cake automatically loads a model that it thinks is directly related to the controller. So in AwarenesscampaignController, you can automatically access Awarenesscampaign (the model).
It doesn't know about any other model, though. One way you might solve this is by adding the following property to your controller:
// This has to contain ALL models you intend to use in the controller
public $uses = array('Awarenesscampaign', 'Post');
This goes at the top of the class, before you start declaring the functions. It tells Cake that you want to use other models except the 'default' one, but you have to add that one to the array too, or you'll lose access to it.
You can also use loadModel inside your action, if it's a one-off. It's then accessed the same way as you would access a model normally:
public function view($id = null) {
$this->loadModel('Post');
$posts = $this->Post->find('all');
...
}
To send this to your view, you can call set again, but you might want to change data to something more readable, and to prevent confusion:
public function view($id = null) {
...
$this->set('campaign', $this->Awarenesscampaign->read());
$this->set('posts', $this->Post->find('all'));
}
They'll be accessible as $campaign and $post respectively.
One tweak I would make, though, is to not use 'read' unless you intend to edit something. You can use findByColumnName to get the same data. Since you're using just an id, you can call findById:
$campaign = $this->Awarenesscampaign->findById($id);
There's quite a lot of magic going on there. It just means you can search for a particular value in a more short-hand format.
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
Finally, while you can access other models (as demonstrated), you can't, or generally shouldn't, try and access one controller from another. If you have code that you want to use in more than one controller, but can't go in the model, you can create Components.
http://book.cakephp.org/2.0/en/controllers/components.html#creating-a-component
The manual is fairly comprehensive. While sometimes hard to navigate, it will often have an answer to most of your questions.
http://book.cakephp.org/2.0/en/
1) Your understanding is good enough. What this is doing is basically mapping a row of database table with object. So after setting the Model id $this->Awarenesscampaign->id = $id, now Model is pointing to the row of database table that has id equals to what has been passed to view action.
2) you can query another table by calling the methods of that particular Model. If your model is somehow associated with the current Model that you are in, you can use chaining to call that Model's action. e.g. if your in Posts controller and Post Model is associated with Comment Model t get the data you can chain through.
$comments = $this->Post->Comment->find();
If however your Model of interest is not associated with current Model, there are couple of ways to perform operations of other Model. A good option is to use Class Registry. Say for example you want to use Customer Model which is not related to your current Model. In your controller you will do
$customer= ClassRegistry::init("Customer");
$customers= $customer->find();
3) to set multiple variables for the view you can set them via compact function or using associated row.
$posts = $this->Post->find();
$comments = $this->Post->Comment->find();
$this->set(compact('posts', 'comments'));
// or
$this->set('posts' => $posts, 'comments' => $comments);

Preventing unnecessary db hits (Kohana ORM)

Kohana's ORM, by default, is not as smart as I wanted when it comes to recognizing which objects it has already loaded. It saves objects loaded through a relationship, for example:
$obj = $foo->bar; // hits the DB for bar
/* ... Later ... */
$obj = $foo->bar; // had bar preloaded, so uses that instead
But if there's more than one way to find bar, it doesn't see that. Let's say both foo and thing (we need more meta-syntactic variables) have a relationship with the same bar:
$obj = $foo->bar; // hits DB
$obj = $thing->bar // hits DB again, even though it's the same object
I've attempted to fix this by having a preloaded array of objects keyed by model and id. It works, but the problem is that it only works if I know the ID ahead of time. My overloaded ORM functions look like this:
public function find($id = NULL)
{
$model = strtolower(get_class($this));
if ($id != NULL) // notice I don't have to hit the db if it's preloaded.
{
$key = $model . '_' . $id;
if (array_key_exists($key, self::$preloaded)) return self::$preloaded[$key];
}
$obj = parent::find($id);
$key = $model . '_' . $obj->pk();
// here, I have to hit the DB even if it was preloaded, just to find the ID!
if (array_key_exists($key, self::$preloaded)) return self::$preloaded[$key];
self::$preloaded[$key] = $obj;
return $obj;
}
The purpose of this is so that if I access the same object in two different places, and there's a chance they're the same object, it won't incorrectly half-update two different instances, but correctly update the one preloaded instance.
The above find method hits the DB needlessly in cases where I find an object by something other than primary key. Is there anything I can do to prevent that, short of keying the preloaded objects by every imaginable criterion? The core issue here seems so basic that I'm surprised it's not part of the original ORM library. Is there a reason for that, or something I overlooked? What do most people do to solve this or get around it? Whatever the solution is, I'll be applying it further when I integrate memcache into my code, so it might help to keep that in mind.
Turn on per-request DB caching in config/database.php ('caching' param, its FALSE by default). This will allow you to use cached results for the same queries.

Categories