Does anybody know why I have an error when I'm using the Laravel pagination after filter results in a View?
It works perfectly in my simple view, but when I filter data using the form I've created for... the same pagination doesn't work!
MY CODE:
Controller:
public function filtro()
{
$keyword = Input::get('keyword');
$asignatura = Input::get('asignatura_id');
$nivel = Input::get('nivel_id');
$actividades = Actividad::where('actividad', 'LIKE', '%'.$keyword.'%');
if($asignatura){
$actividades->where('asignatura_id', $asignatura);
}
if($nivel){
$actividades->where('nivel_id', $nivel);
}
$actividads = $actividades->orderBy('created_at', 'DES')
->paginate(10);
$id_user = Sentry::getUser()->id;
$fichas = Ficha::where('user_id', $id_user)->orderBy('created_at', 'DESC')->get();
$asignatura_clave = Asignatura::where('id', $asignatura)->first();
$asignatura_id = Asignatura::orderBy('nombre', 'asc')->lists('nombre','id');
$nivel_id = Nivel::orderBy('edad', 'asc')->lists('edad','id');
$nivel_clave = Nivel::where('id', $nivel)->first();
$user = User::find($id_user);
// Devuelve todas las actividades que estan relacionadas con alguna ficha.
$actividadEnFicha = Actividad::has('fichas')
->get();
//$asignaturas = Asignatura::all();
// return var_dump($actividadEnFicha);
return View::make('actividads.index')
->with('fichas', $fichas)
->with('user', $user)
->with('palabra_clave', $keyword)
->with('keyword', $keyword)
->with('nivel_id', $nivel_id)
->with('nivel_clave', $nivel_clave)
->with('asignatura_clave', $asignatura_clave)
->with('asignatura_id', $asignatura_id)
->with('actividadEnFicha', $actividadEnFicha)
->with('actividads', $actividads);//->with('asignaturas', $asignaturas);
}
The pagination code in my View:
{{ $actividads->links(); }}
MESSAGE ERROR:
Symfony \ Component \ HttpKernel \ Exception \ MethodNotAllowedHttpException
* #param array $others
* #return void
*
* #throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
*/
protected function methodNotAllowed(array $others)
{
throw new MethodNotAllowedHttpException($others);
}
EDIT:
I've created these two routes
Route::post('filtro', array('as' => 'filtro', 'uses' => 'ActividadController#filtro')); and Route::get('filtro', array('as' => 'filtro', 'uses' => 'ActividadController#filtro')); , and pagination works.
The problem now is when I change to another page (using pagination) my filters are forgotten and it paginates all my activities angain... :S Any idea why? This is the first time I try it, and probably I'm doing it in a wrong way.
Any idea what's happening?
Thanks!
I have two remarques, the first is that inside the if you should assign the new result. Second is in the orderBy the second parameter should be 'DESC' and not 'DES'. And I think this second error is causing your issue.
if($asignatura){
$actividades = $actividades->where('asignatura_id', $asignatura);
}
if($nivel){
$actividades = $actividades->where('nivel_id', $nivel);
}
$actividads = $actividades->orderBy('created_at', 'DESC')
->paginate(10);
Hope this fixes your problem
Edit
The MethodNotAllowedHttpException is usualy caused by a route not allowing some type of requests. the paginate links sends the page number to the action using GET and I guess you're not allowing GET in your routes for this method.
I've solved my problem with pagination using session.
My problem was that, when I changed the page by pagination, everything was reloaded and my filters were forgotten. So I created a session for every filter and now I can move through my filtered data by pagination.
Related
I have laravel (7.x) application. I recently added the cache functionality for the performance boost. After implementing the cache functionality, I was having trouble with the pagination while loading the data in grid format, so I googled for the solution and found this Pagination with cache in Laravel.
Although, it did solve my problem. But, the case is that I have about 100 pages and due to the solution I found, each page has it's own cache. Now, if I create or update any record then it doesn't reflect in the grid because the data is loaded from the cache.
PostController.php:
...
$arraySearch = request()->all();
# calculating selected tab
$cache = (!empty(request()->inactive)) ? 'inactive' : 'active';
$cacheKey = strtoupper("{$this->controller}-index-{$cache}-{$arraySearch['page']}");
# caching the fetch data
$arrayModels = cache()->remember($cacheKey, 1440, function() use ($arraySearch) {
# models
$Post = new Post();
# returning
return [
'active' => $Post->_index(1, 'active', $arraySearch),
'inactive' => $Post->_index(0, 'inactive', $arraySearch),
];
});
...
Post.php:
public function _index($status = 1, $page = null, $arraySearch = null)
{
...
$Self = self::where('status', $status)
->orderBy('status', 'ASC')
->orderBy('title', 'ASC')
->paginate(10);
...
return $Self;
}
How do I clear all this cache to show the newly created or updated record to with the updated values.?
1. Store All pages under the same tag:
As seen on the documentation: https://laravel.com/docs/master/cache#storing-tagged-cache-items
You can use tags to group cached items.
$cacheTag = strtoupper("{$this->controller}-index-{$cache}");
$arrayModels = cache()->tags([$cacheTag])->remember($cacheKey, 1440, function() use ($arraySearch) {
...
2. Set an event listener on Post to clear the tag
You can run an Event listener on your Post update() or create() events.
https://laravel.com/docs/7.x/eloquent#events-using-closures
You can then clear the tag cache using
Cache::tags([$cacheTag])->flush();
I know this isn't the proper solution. But, until I find the proper way to do it, this is the option I am kind of stuck with.
PostController.php:
public function index()
{
...
$arraySearch = request()->all();
# calculating selected tab
$cache = (!empty(request()->inactive)) ? 'inactive' : 'active';
$cacheKey = strtoupper("{$this->controller}-index-{$cache}-{$arraySearch['page']}");
# caching the fetch data
$arrayModels = cache()->remember($cacheKey, 1440, function() use ($arraySearch) {
# models
$Post = new Post();
# returning
return [
'active' => $Post->_index(1, 'active', $arraySearch),
'inactive' => $Post->_index(0, 'inactive', $arraySearch),
];
});
...
}
public function store()
{
...
Artisan::call('cache:clear');
...
}
I'll post the proper solution when I find one. Till then I am using this one.
There is a method in Laravel Model class called booted (not boot, which is having a different purpose). This method runs every time something is "saved" (including "updated") or "deleted".
I have used this as following (in a Model; or a Trait, included in a Model):
protected static function booted(): void
{
$item = resolve(self::class);
static::saved(function () use ($item) {
$item->updateCaches();
});
static::deleted(function () use ($item) {
$item->updateCaches();
});
}
"updateCaches" is a method in the Trait (or in the Model), that can have the code to update the cache.
I'm building a search functionality that returns large collections which are paginated using a LengthAwarePaginator. I'm trying to cache results using a key called $searchFilter_$query_$offsetPages for a single page of returned results (10 items). It goes into the cache just fine. However, it times out when I try to check using Cache::has($key) or fetch using Cache::get($key).
The same problem occurs in the browser as well as in artisan Tinker. Strangely, when I put a random set of 10 items into the cache in Tinker and fetch them back, everything works fine. I'm using Redis as the cache driver.
Here is my controller method:
public function search($filter, $query, $layout, Request $request) {
if($layout == "list-map") {
return view("list-map")->with(['filter' => $filter, 'query' => $query, 'layout' => 'list-map']);
} else {
$offsetPages = $request->input('page', 1) - 1;
$cacheKey = $filter . "_" . $query . "_" . $offsetPages;
if(Cache::has($cacheKey)) {
\Log::info("fetching results from cache");
$data = Cache::get($cacheKey);
$totalCt = $data[0];
$results = $data[1];
} else {
$results = $this->getResults($filter, $query);
$totalCt = $results->count();
$results = $results->slice($offsetPages, $this->resultsPerPage);
\Log::info("caching results");
Cache::put($cacheKey, [$totalCt, $results], 5);
}
$results = new LengthAwarePaginator($results,
$totalCt,
$this->resultsPerPage,
$request->input('page', 1),
['path' => LengthAwarePaginator::resolveCurrentPath()]
);
return view($layout)->with(['filter' => $filter, 'query' => $query, 'layout' => $layout, 'results' => $results]);
}
}
So, the issue was that many of the models in the collection returned from my getResults() method were obtained via relationship queries. When I would dd($results) on the single page of 10 results, I could see that there was a "relations" field on each model. Inside that array were thousands of recursively related models based on the relationship I originally queried. I was unable to find any information about an option to not eager load these related models. Instead I came up with a bit of a hacky workaround to fetch the models directly:
$results = $results->slice($offsetPages, $this->resultsPerPage);
//load models directly so they don't include related models.
$temp = new \Illuminate\Database\Eloquent\Collection;
foreach($results as $result) {
if(get_class($result) == "App\Doctor") {
$result = Doctor::find($result->id);
} else if(get_class($result == "App\Organization")) {
$result = Organization::find($result->id);
}
$temp->push($result);
}
$results = $temp;
\Log::info("caching results");
Cache::put($cacheKey, [$totalCt, $results], 5);
If anyone knows the best practice in this situation, please let me know. Thanks!
Edit:
I've found a better solution instead of the above workaround. If I query my relationships like this: $taxonomy->doctors()->get() rather than $taxonomy->doctors, it does not load in the huge recusive relations.
I dont really see why your code doesn't work. The only potential problems I see are the cache keys, which could contain problematic characters, as well as the way you check for a cached value. As you are using Cache::has($key) before Cache::get($key), you could end up with a race condition where the first call returns true and the latter null because the cached value timed out just between the two calls.
I tried to address both issues in the following snippet:
public function search($filter, $query, $layout, Request $request)
{
if($layout == "list-map") {
return view("list-map")->with(['filter' => $filter, 'query' => $query, 'layout' => 'list-map']);
} else {
$offsetPages = $request->input('page', 1) - 1;
$cacheKey = md5("{$filter}_{$query}_{$offsetPages}");
$duration = 5; // todo: make this configurable or a constant
[$totalCount, $results] = Cache::remember($cacheKey, $duration, function () use ($filter, $query) {
$results = $this->getResults($filter, $query);
$totalCount = $results->count();
$filteredResults = $results->slice($offsetPages, $this->resultsPerPage);
return [$totalCount, $filteredResults];
});
$results = new LengthAwarePaginator($results,
$totalCount,
$this->resultsPerPage,
$request->input('page', 1),
['path' => LengthAwarePaginator::resolveCurrentPath()]
);
return view($layout)->with(compact('filter', 'query', 'layout', 'results'));
}
}
The inbuilt function Cache::remember() doesn't use Cache::has() under the hood. Instead, it will simply call Cache::get(). As this function will return null as default if no cache was hit, the function can easily determine if it has to execute the closure or not.
I also wrapped the $cacheKey in md5(), which gives a consistently valid key.
Looking at the following part of your code
$results = $this->getResults($filter, $query);
$totalCount = $results->count();
$filteredResults = $results->slice($offsetPages, $this->resultsPerPage);
I am quite sure the whole search could be improved (independently of the caching). Because it seems you are loading all results for a specific search into memory, even if you throw away most parts of it. There is certainly a better way to do this.
I am newbie in Oauth2 and laravel. I am trying to implement Authorization Server in laravel with authorization code grant. I followed all implementation instruction mentioned in https://github.com/lucadegasperi/oauth2-server-laravel.
Every thing is working fine except user need to approve/deny on each login to get access code. I want to show authorization-form only once when user first time ask for authorization and not every time, similar to how Oauth2 is implemented in google.
How can I do it, any pointers?
I have implemented a solution for your issue in one of my work, here is the sample code, hope that help:
Route::get('/oauth/authorize', array('before' => 'check-authorization-params|auth', function() {
// get the data from the check-authorization-params filter
$params = Session::get('authorize-params');
// get the user id
$params['user_id'] = Auth::user()->id;
if ($params['approval_prompt'] != 'force')
{
$session = DB::table('oauth_sessions')->where('client_id', '=', $params['client_id'])
->where('owner_type', '=', 'user')
->where('owner_id', '=', $params['user_id'])
->first();
if ($session)
{
$code = AuthorizationServer::newAuthorizeRequest('user', $params['user_id'], $params);
Session::forget('authorize-params');
return Redirect::to(AuthorizationServer::makeRedirectWithCode($code, $params));
}
}
// display the authorization form
return View::make('authorization-form', array('params' => $params));
}));
If the approval_prompt is not set to force, then I will check whether if there are any sessions belong to this user and show the authorization form only if there is no saved sessions.
Notice: This code is for the 1.0 version of the package, if you're using another version, there may be some different things.
This is modification of Hieu Le answer for Laravel 5.1 and lucadegasperi/oauth2-server-laravel with league/oauth2-server 4.1.2
Route::get('/oauth/authorize', ['as' => 'oauth.authorize.get','middleware' => ['check-authorization-params', 'auth'], function() {
// display a form where the user can authorize the client to access it's data
$authParams = Authorizer::getAuthCodeRequestParams();
$authParams['client_id'] = $authParams['client']->getId();
$formParams = array_except($authParams,'client');
$authParams['user_id'] = Auth::user()->id;
if (array_get($authParams, 'approval_prompt', null) != 'force')
{
$session = DB::table('oauth_sessions')->where('client_id', '=', $authParams['client_id'])
->where('owner_type', '=', 'user')
->where('owner_id', '=', $authParams['user_id'])
->first();
if ($session)
{
$redirectUri = Authorizer::issueAuthCode('user', $authParams['user_id'], $authParams);
return Redirect::to($redirectUri);
}
}
return View::make('oauth.authorization-form', ['params'=>$formParams,'client'=>$authParams['client']]);
}]);
How to use ActiveRecotd cache for Yii 2? I did't find any examples in official docs. In Google I found 2 examples, first is:
$db = self::getDb();
$object = $db->cache(function ($db) use($id) {
return self::findOne($id);
});
But it doesn't work for Model, I tested with updated framework. Other example is:
$data = \Yii::$app->cache->get('some_var_' . $id);
if ($data === false)
{
$data = self::findOne($id);
\Yii::$app->cache->set('some_var_' . $id, $data, 60);
}
It's working fine, but it's not ActiveRecord caching it's data caching, So we haven't got ActiveRecord caching in Yii 2?
1) Use cache like that:
$db = Yii::$app->db;// or Category::getDb()
$result = $db->cache(function ($db) use ($id) {
return Category::find()->where(['id' => $id])->all();
}, CACHE_TIMEOUT);
2) If you may use query dependency, use like that:
$db = Yii::$app->db;// or Category::getDb()
$dep = new DbDependency();
$dep->sql = 'SELECT count(*) FROM category';
$result = $db->cache(function ($db) use ($id) {
return Category::find()->where(['id' => $id])->all();
}, CACHE_TIMEOUT, $dep);
I too am having trouble with this. Here's my workaround for the time being for a hasOne() relationship.
public function getGroup()
{
if(isset(static::$_getGroup[$this->id])) {
return static::$_getGroup[$this->id];
}
$Group = $this->hasOne(BillChargesGroup::className(), ['id' => 'group_id'])->one();
static::$_getGroup[$this->id] = $Group;
return $Group;
}
I only want to cache data for the current request, so this works. However because I'm using ->one(); it does not return the ActiveQuery object if we call $model->getGroup() (which I found is good for extending queries)
Unfortunately if I do return the ActiveQuery object, Yii2 does some "magic" on it and always does a SELECT * which I can't control.
Since 2.0.14 you can use the following shortcuts:
(new Query())->cache(7200)->all();
// and
User::find()->cache(7200)->all();
Source: https://www.yiiframework.com/doc/guide/2.0/en/caching-data
I'm sure this is something I'm doing wrong, but I can't seem to figure it out. I'm using backbone.js to talk to my rest server (Philip Sturgeon's codeigniter restserver). I am running a normal model.destroy() on one of my backbone collections model.
//a basic example
tagCollection.at(5).destroy();
This creates a proper call to a url like:
DELETE http://mydomain.com/index.php/tags/tag/id/12
When I get inside my "tag_delete" php function, and do:
$this->delete('id');
This always returns nothing. I assume this has something to do with the way backbone.js sends it's requests, but nothing is jumping out at me. Details below.
Backbone is issuing a "DELETE" request.
Relevant code from my REST_Controller method:
function tag_delete () {
//delete the tag
$id = $this->delete('id'); //always empty
$result = $this->tag_model->delete($id);
if (! $result) {
$this->response(array('status' => 'failed'), 400);
}
$this->response(array('status' => 'success'), 200);
}
Any ideas? Any backbone.js experts run into this when using codeigniter and Philip Sturgeon's restserver?
This should be a cheap quick way to fix your delete request...
function tag_delete () {
$id = $this->uri->segment(4);
$result = $this->tag_model->delete($id);
if (! $result) {
$this->response(array('status' => 'failed'), 400);
}
$this->response(array('status' => 'success'), 200);
}
However, this is how I am structuring my requests using a combo of backbone and REST_Controller...
DELETE http://example.com/index.php/tags/12
(get rid of the /tag/id/ segment of the url... it's implied that you are deleting a 'tag' row from the 'tags' collection by id, appending /tag/id is unnecessary)
function tag_delete ($id) {
$result = $this->tag_model->delete($id);
if (! $result) {
$this->response(array('status' => 'failed'), 400);
}
$this->response(array('status' => 'success'), 200);
}
for the collection:
Backbone.Collection.extend({
url : '/tags'
});
tagCollection.at(5).destroy();
Then add something like this to your routes:
$route['tags/(:num)'] = 'tags/tag/$1';
which will set up the structure necessary for the restserver controller... it is just much more manageable that way if you are doing a lot of Backbone work.
As per tgriesser's suggestion, the best way to do this is to use the url property on the collection. I have used the following before and it works like charm (the following controller implemented using silex framework + paris library for data access):
// DELETE /{resource}/{id} Destroy
$app->delete('/api/todos/{id}', function ($id) use ($app) {
$todo = $app['paris']->getModel('Todo')->find_one($id);
$todo->delete();
return new Response('Todo deleted', 200);
});
In your backbone collection, add the following:
window.TodoList = Backbone.Collection.extend({
model: Todo,
url: "api/todos",
...
});
Recently, I have written a tutorial on how to do GET/POST/PUT/DELETE with Backbone.js and PHP http://cambridgesoftware.co.uk/blog/item/59-backbonejs-%20-php-with-silex-microframework-%20-mysql, might be helpful.