Caching Eloquent models in Laravel 5.1 - laravel

I've created an API using Laravel and I'm trying to find out how to cache Eloquent models. Lets take this example as one of the API endpoints /posts to get all the posts. Also within the method there are various filter options such as category and search and also gives the option to expand the user.
public function index()
{
$posts = Post::active()->ordered();
if (Input::get('category')) $posts = $posts->category(Input::get('category'));
if (Input::get('search')) $posts = $posts->search(Input::get('search'));
if ($this->isExpand('user')) $posts = $posts->with('user');
$posts = $posts->paginate($this->limit);
return $this->respondWithCollection($this->postTransformer->transformCollection($posts->all()), $posts);
}
I have been reading up and found in Laravel 4 you could cache a model like this
return Post::remember($minutes);
But I see this has been removed for Laravel 5.1 and now you have to cache using the Cache facade, but is only retrievable by a single key string.
$posts = Cache::remember('posts', $minutes, function()
{
return Post::paginate($this->limit);
});
As you can see, my controller method contains different options, so for the cache to be effective I would have to create a unique key for each option like posts_cagetory_5, posts_search_search_term, posts_category_5_search_search_term_page_5 and this will clearly get ridiculous.
So either I'm not coming across the right way to do this or the Laravel cache appears to have gone backwards. What's the best solution for caching this API call?

As the search is arbitrary, using a key based on the search options appears to be the only option here. I certainly don't see it as "ridiculous" to add a cache to for expensive DB search queries. I may be wrong as I came by this post looking for a solution to your exact problem. My code:
$itemId = 1;
$platform = Input::get('platform'); // (android|ios|web)
$cacheKey = 'item:' . $itemId . ':' . $platform;
$item = Item::find(1);
if( Cache::has($cacheKey) ) {
$result = Cache::get($cacheKey);
} else {
$result = $this->response->collection( $item, new ItemTransformer( $platform ) );
Cache::tags('items')->put($cacheKey, $result, 60); // Or whatever time or caching and tagged to be able to clear the lot in one go...
}
return $result;
I realise that my example has less complexity but it seems to cover all the bases for me. I then use an observer to clear the cache on update.

Related

Laravel Automatic resource CRUD

What patterns can I use for 'automatic' resource CRUD operations for given Models in Laravel?
Say I have two models SomeModel and SomeRelatedModel where some_related_model.some_model_id is an FK to SomeModel.
The standard method on the SomeModelController for handling the create POST /api/someModel might look like this:
public function store(Request $request)
{
$user = Auth::guard('api')->user();
$data = $request->get('data');
$data['user_id'] = $user->id;
$someModel = SomeModel::create($data);
// has this request been made with the data for the
// related model? If so create this too.
if($data['relatedModel']){
SomeRelatedModel::create(array_merge(
['some_model_id' => $someModel->id]
$data['relatedModel']
));
}
// has this request been made expecting to get related
// models back in the response? If so load these
if($request->has('with')){
$someModel->load($request->get('with'));
}
return (new PostResource($post))
->toResponse($request)
->setStatusCode(201);
}
This works but is very verbose and for models with a sub-sub relation would need changing further. Similar work will need to be done for the other endpoints for all resources.
Is there a more versatile (or tidy) pattern using out-of-the box classes to get a similar effect?
Have a look at Laravel Orion. Fits your use case.

beforeFind() in Yii ActiveRecord and cache

In some model classes I want to implement cache. And I wanted to do that like:
UsersModel::model()->findByAttributes([...])
In that class I wanted to override method beforeFind() to send request first to cache server, but it seems that method does not take any additional parameters, nor does have object with attributes.
Putting additional conditions/checks in top level code something like :
$response = Yii::app()->cache->get('userUserLogin');
if(empty($response) == true) {
//fetch data from db and set to cache
$userModel = UsersModel::model->findByAttributes([...])
Yii::app()->cache->set('user' . $userModel->username, $userModel->getAttributes());
}
is not nice and trivial, leading to many branches.
You should not use beforeFind() for that. Besides technical problems in implementation, you may get many side effects and hard to debug bugs because of that. That is because cache may be out of date and many internal Yii logic may rely on assumption, that findByAttributes() (and other methods) always fetches fresh data from database. You will also not be able to ignore cache and get model directly from database.
In general you have 2 options:
1. Use CActiveRecord::cache()
$model = UsersModel::model()->cache(60)->findByAttributes([...])
This will query cache results for 60 seconds.
2. Custom helpers
You may add custom methods, which will simplify using cached active records:
public static function findByAttributesFromCache($attributes = []) {
$result = Yii::app()->cache->get(json_encode($attributes));
if ($result === false) {
//fetch data from db and set to cache
$result = static::model()->findByAttributes($attributes);
Yii::app()->cache->set(json_encode($attributes), $result, 60);
}
return $result;
}
You can add such method to trait and reuse it in multiple models. Then all you need is:
$userModel = UsersModel::findByAttributesFromCache([...]);

Laravel 5.2 - How to access an App Presenter method in EventServiceProvider?

I have a guitar lessons site where there is an exercises table. The original developers placed some functions in ExercisePresenter to retrieve other bits of data associated with an exercise, such as its url.
Here is a function in ExercisePresenter that returns url for an exercise:
public function url()
{
return '/guitar-lesson-ex/' . $this->urlName() . '/' . $this->id;
}
So now I am creating an event on new exercise created so I can use pusher notifications. In the EventServiceProvider I have this:
public function boot(DispatcherContract $events)
{
parent::boot($events);
Exercise::created(function ($exercise) {
// need to update lesson difficulty
$lesid = $exercise->lesson_id;
$les = \App\Lesson::find($lesid);
$dif = $les->difficulty();
DB::table('lessons')
->where('id', $lesid)
->update(['difficulty' => $dif]);
// lets trigger NewExerciseEvent to send pusher notifications
$url = $exercise->url;
event(new NewExerciseEvent($message));
});
}
I thought in above code $url = $exercise->url; would work since I see $exercise->url used successfully in exercise views. But it is returning $url as null. Now, there is no url column in the exercise database, so I figure somehow when $exercise->url; is used in a view, laravel is figuring out that the ExercisePresenter is able to return the url.
I am debugging through PHPStorm, but when I get to $url = $exercise->url; and step in, I am just taken through various bits of laravel code that looks for a method, etc. I am not savvy enough with laravel to figure out what it is doing here differently than in the view (too bad we can't debug views...), but each time I try this $url is returned as null.
Any idea how to get this to work properly in my EventServiceProvider?
Thanks!
Figured it out:
$url = $exercise->present()->url;
I had been searching for how to use presenters but having just read (Laravel View Presenters From Scratch), everything is clear!
Sorry for posting prematurely.

Laravel: two models in one controller method

Let me explain about my problem.
I am currently using Laravel 5.0. Here is my structure
Table: bgts, Model: Bgt, Controller: BgtController
Table: bgthistories, Model: BgtHistory
Now I want to do these:
Everytimes creating new item into bgts table, I want to make a copy and insert into bgthistories table. Then, everytimes that record is updated, i'll copy one more version, still insert into bgthistories.
Here is store() method.
public function store(Request $request) {
$bgt = new Bgt();
$history = $this->coppy($bgt);
$uploader = new UploadController('/data/uploads/bgt');
$bgt->name = $request['name'];
$bgt->avatar = $uploader->avatar($request);
$bgt->attachments($uploader->attachments($request));
//dd($bgt);
$bgt->save();
$history->save();
return redirect('bgt');
}
And this is the coping:
public function coppy($bgt) {
$array = $this->$bgt->toArray();
$version = new BgtHistory();
foreach($array as $key => $value) {
$version->$key = $value;
}
return $version;
}
I create migration tables already. Everything is ready. But, when I call
$bgt->save();
$history->save();
It did not work. If I remove $history->save();, it create new record ok. I think the save() method that built-in in Model provided by Laravel is problem. Can anyone tell me how to solve this.
I tried to build the raw query then executed it by DB:statement but it did not work too. Every try to execute anything with DB is failing.
Please research before re-inventing the wheel.
(Same stuff different sites in case one is down)
http://packalyst.com/packages/package/mpociot/versionable
https://packagist.org/packages/mpociot/versionable
https://github.com/mpociot/versionable
Cheers and good luck ;)

How to increment a column using Eloquent Model in Laravel 4

I am not sure how to increment the value in a column using Eloquent Model in Laravel 4?
This is what I currently have and I am not sure how correct is this.
$visitor = Visitor::where('token','=','sometoken')->first();
if(isset($visitor)){
$visitor->increment('totalvisits');
}else{
Visitor::create(array(
'token'=>'sometoken',
'totalvisits'=>0
));
}
With Query Builder we could do it using
DB::table('visitors')->increment('totalvisits');
Looks like the code that I posted worked after all
$visitor = Visitor::where('token','=','sometoken')->first();
if(isset($visitor)){
$visitor->increment('totalvisits');
}else{
Visitor::create(array(
'token'=>'sometoken',
'totalvisits'=>0
));
}
Prior to a fix a few weeks ago the increment method actually fell through to the query builder and would be called on the entire table, which was undesirable.
Now calling increment or decrement on a model instance will perform the operation only on that model instance.
Laravel 5 now has atomic increment:
public function increment($column, $amount = 1, array $extra = [])
{
if (! is_numeric($amount)) {
throw new InvalidArgumentException('Non-numeric value passed to increment method.');
}
$wrapped = $this->grammar->wrap($column);
$columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra);
return $this->update($columns);
}
which essentially works like:
Customer::query()
->where('id', $customer_id)
->update([
'loyalty_points' => DB::raw('loyalty_points + 1')
]);
Below is old answer for Laravel 4 where the built-in increment was a seperate select and then update which of course leads to bugs with multiple users:
If you'd like to accurately count your visitors by ensuring the update is atomic then try putting this in your Visitor model:
public function incrementTotalVisits(){
// increment regardless of the current value in this model.
$this->where('id', $this->id)->update(['totalVisits' => DB::raw('last_insert_id(totalVisits + 1)')]);
//update this model incase we would like to use it.
$this->totalVisits = DB::getPdo()->lastInsertId();
//remove from dirty list to prevent any saves overwriting the newer database value.
$this->syncOriginalAttribute('totalVisits');
//return it because why not
return $this->totalVisits;
}
I'm using it for a change tag system but might work for your needs too.
Does anyone know what to replace the "$this->where('id',$this->id)" with because since dealing with $this Visitor it should be redundant.

Resources