Laravel5.5: save Provider data in session to avoid overload - laravel

I have made a ServiceProvider to load data on several views. Like this:
View::composer(['components.navigation.main.search','search.*','page-parts.cats','page-parts.categories_menu','page-parts.categories_more','page-parts.cats_top','components.modals.off-category'],function ($view) {
$view->with([
'toplevel_categories' => Category::topLevel()->orderBy('name')->get(),
]);
});
But on several html pages he needs to load multiple of these views and I don't want to load the topLevel categories each time to avoid overload and less runtime.
Can I store the loaded data (toplevel_categories) in a session or what is the most efficient way to handle this problem?

You could simply cache the variable and use it in the callback like:
$topLevelCategories = Category::topLevel()->orderBy('name')->get();
View::composer([], function($view) use ($topLevelCategories) {
$view->with([
'toplevel_categories' => $topLevelCategories
}
You could even use the cache mechanic from laravel itself to save an additional query, like caching it for 30 minutes (assuming the database hasnt changed in the meantime):
// Save the categories in the cache or retrieve them from it.
$topLevelCategories = Cache::remember('topLevelCategories', 30, function() {
return Category::topLevel()->orderBy('name')->get();
});
Note that for Laravel 5.8 the second parameter is in SECONDS, for 5.7 and below it is in MINUTES.
Since your service provider is only loaded once per request/lifecycle this should do the trick.

Related

how update cached data when main data is change in laravel

i want to use a cache of laravel for ex: index method on a specific controllers!
i use Cache::rememberForever method of laravel cache.
i dont use Cache::remember with ttl time for caching data!
my question: i dont no when and how i update data in cache
imaging: i cached user profile with all relations! now user change avatar or personal data! now i should be renew (update) cache data in redis! (update cache data for get in next call) i want to know the best solution for updating cache data when update main data
To update a cache you can use such. event function in your User model:
protected static function boot()
{
parent::boot();
$removeCacheFunc = function ($model) {
$key = self::USER_CACHE_KEY . $model->id; //compile cache key in your way
\Cache::delete($key);
};
static::saved($removeCacheFunc);
static::deleting($removeCacheFunc);
}
Next time you call Cache::rememberForever() will not find this entity by key and will make it on the fly

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([...]);

view composers using wildcard * to load a variable inside all the views laravel 5

How will it affect the performance? if I loaded a variable inside all the views using view composers even if I'm not using this variable inside all the views.
is it recommended to do that? please provide your answer with an article.
using this service provider.
View::composer(
[
'*',
],
function ($view) {
$masterLayout = ;//get variable from database
}
);
Well, obviously there will be a negative impact on the performance of the site if you're querying for data that is possibly not used, but, you could always use caching to alleviate the effect:
View::composer(['*'], function ($view) {
$masterLayout = Cache::rememberForever('master_layout', function() {
return DB::table('layouts')->where('name', 'master')->first();
});
});

Laravel 5: Sessions not working the way they should

On top of every controller and routes.php I used:
use Illuminate\Support\Facades\Session;
In routes.php I set the session using:
Session::put('key', 'value');
In a controller I want to call the session value of key using:
echo Session::get('key');
But once I set a new value to key in routes.php and call it in a controller, I still get the first value and not the new one. If I echo the the session using Session::all() in routes.php after setting it, I see the new value, but in a controller it flips back to the first value. I even tried using below in routes.php before setting the new value, but without success.
Session::forget('key');
Am I forgetting something here?
Using regular PHP $_SESSION my routes.php looks like this:
$slug = $_SERVER['REQUEST_URI'];
$slug = explode('/', $slug[0]);
if(in_array($slug[1], Language::all()->lists('iso'))) {
$_SESSION['language'] = $slug[1];
if(!$slug[2]) {
$_SESSION['slug'] = 'home';
Route::any('/{slug}', ['as' => 'pages.page', 'uses' => 'PagesController#page']);
} else {
if($slug[2] != 'dashboard' && $slug[2] != 'migrate' && $slug[2] != 'form-send') {
if (in_array($slug[2], ElementValue::where('element_field_id', 2)->lists('value_char')) && !isset($slug[3])) {
$_SESSION['slug'] = $slug[2];
Route::any('/{slug}', ['as' => 'pages.page', 'uses' => 'PagesController#page']);
} else {
$_SESSION['slug'] = 'home';
Route::any('/{slug}', ['as' => 'pages.page', 'uses' => 'PagesController#page']);
}
}
}
}
Where in routes.php are you setting the session value? It sounds like you're doing something like this:
Session::put('key', 'value');
Route::get('my-route', 'MyController#doSomething');
and then doing this:
class MyController {
public function doSomething()
{
Session::get('key');
}
}
Is that correct? If so, read on...
I'm no expert on the Laravel request lifecycle (for more, see the documentation), but it doesn't surprise me that this doesn't work. The way I think about it is this: the routes.php file is loaded and executed early in the life cycle - probably first - since it tells the application what code to execute next (ie. what do when a particular request is received). And when I say "early in the life cycle", I mean early - like before sessions are initialized. I believe that the Session::put call is simply being ignored, since at the time when you're setting the value, the session does not exist.
You may want expand your question with a little more detail about what you're trying to accomplish - there has got to be a better way to do it.
EDIT - in response to the comments below...
I am not saying you should touch the $_SESSION superglobal - that's a bad idea because I'm not even sure that Laravel uses the native PHP session facility and you have no guarantee that whatever you do will continue to work in the future.
It's not clear what you're trying to do, but to me this sounds like a value that does not belong in the session.
By placing the Session::put in the routes.php file, it sounds like you have some value that's important and should be set for every session and every request
If that's the case, and it's a static value, then it's not a session value, it's a configuration value.
If, instead, it's a dynamic value and/or it changes depending on which user is associated with a session, then you can set it in one of several places:
if you're using controller-based routing, you could set this in the controller constructor, although I wouldn't recommend it, because you will probably have to do it for several controllers, leading to code duplication
if you're using closures in your routes, set it there. E.g.
Route::get('some/route', function () {
Session::put('key', 'value');
// this works, because the closure isn't executed until after
// the application is initialized
});
you could also do it in middleware
or in a service provider (although I'm not certain that sessions would be available when the service providers are executed).
The best option is probably middleware - this would allow you to set (or calculate) the session value in one place in your code and also associate it with particular routes, if you don't need it for all routes.
Don't use $_SESSION in laravel. Uses the laravel Session class. See the following post How to access the globals $_SESSION and $_COOKIE from a laravel app?
Also, all your if logic should not be living in routes.php. You should add that to middleware to filter your routes.
Also, you are really making this hard for yourself. Laravel provides most of what you need in convenient helper classes e.g. Request::url(), Request::getHost(), Request::getLocale(). Have a read through the docs and get familiar with "The Laravel Way" it will be much easier and things will then work as you expect.
I moved the logic to the controller and now my routes are this simple:
Route::pattern('slug', '[a-zA-Z0-9\-_\/]+');
$slug = Request::path();
if(isset($slug)) {
Route::any('/{slug}', 'PagesController#index')->where('slug', '[a-zA-Z0-9\-_\/]+');
}
The session is stored in the PagesController and used further in the application. Thanks for your help guys.

Maintaining Session through Angular.js

I am working a project using the AngularJS framework. I am pretty new to using this framework; in the past I have only worked with pure JavaScript and jQuery. The project is a kind of web designer application for a niche market.
As the user moves between pages while designing I want to maintain a session of all the changes they are making.
Now if the user signs in we load the session using data from the database. When the user clicks on save button we update the database with the session data. Someone told me that I can maintain session in Angular similar to backbone. Is this possible? If yes, can you please direct me to a tutorial that does not focus on directives or UI? If this is not possible are there other viable options?
Here is a kind of snippet for you:
app.factory('Session', function($http) {
var Session = {
data: {},
saveSession: function() { /* save session data to db */ },
updateSession: function() {
/* load data from db */
$http.get('session.json').then(function(r) { return Session.data = r.data;});
}
};
Session.updateSession();
return Session;
});
Here is Plunker example how you can use that:
http://plnkr.co/edit/Fg3uF4ukl5p88Z0AeQqU?p=preview
Because the answer is no longer valid with a more stable version of angular, I am posting a newer solution.
PHP Page: session.php
if (!isset($_SESSION))
{
session_start();
}
$_SESSION['variable'] = "hello world";
$sessions = array();
$sessions['variable'] = $_SESSION['variable'];
header('Content-Type: application/json');
echo json_encode($sessions);
Send back only the session variables you want in Angular not all of them don't want to expose more than what is needed.
JS All Together
var app = angular.module('StarterApp', []);
app.controller("AppCtrl", ['$rootScope', 'Session', function($rootScope, Session) {
Session.then(function(response){
$rootScope.session = response;
});
}]);
app.factory('Session', function($http) {
return $http.get('/session.php').then(function(result) {
return result.data;
});
});
Do a simple get to get sessions using a factory.
If you want to make it post to make the page not visible when you just go to it in the browser you can, I'm just simplifying it
Add the factory to the controller
I use rootScope because it is a session variable that I use throughout all my code.
HTML
Inside your html you can reference your session
<html ng-app="StarterApp">
<body ng-controller="AppCtrl">
{{ session.variable }}
</body>
You can also try to make service based on window.sessionStorage or window.localStorage to keep state information between page reloads. I use it in the web app which is partially made in AngularJS and page URL is changed in "the old way" for some parts of workflow. Web storage is supported even by IE8. Here is angular-webstorage for convenience.
You would use a service for that in Angular. A service is a function you register with Angular, and that functions job is to return an object which will live until the browser is closed/refreshed. So it's a good place to store state in, and to synchronize that state with the server asynchronously as that state changes.
Typically for a use case which involves a sequence of pages and in the final stage or page we post the data to the server. In this scenario we need to maintain the state. In the below snippet we maintain the state on the client side
As mentioned in the above post. The session is created using the factory recipe.
Client side session can be maintained using the value provider recipe as well.
Please refer to my post for the complete details.
session-tracking-in-angularjs
Let's take an example of a shopping cart which we need to maintain across various pages / angularjs controller.
In typical shopping cart we buy products on various product / category pages and keep updating the cart. Here are the steps.
Here we create the custom injectable service having a cart inside using the "value provider recipe".
'use strict';
function Cart() {
return {
'cartId': '',
'cartItem': []
};
}
// custom service maintains the cart along with its behavior to clear itself , create new , delete Item or update cart
app.value('sessionService', {
cart: new Cart(),
clear: function () {
this.cart = new Cart();
// mechanism to create the cart id
this.cart.cartId = 1;
},
save: function (session) {
this.cart = session.cart;
},
updateCart: function (productId, productQty) {
this.cart.cartItem.push({
'productId': productId,
'productQty': productQty
});
},
//deleteItem and other cart operations function goes here...
});

Resources