Zend framework - Where to Initialize session when the router needs access to it - session

I work in a project which uses the session a lot. We have a db handler (the standard one from Zend) and currently i have this initialization (db handler + session start) in a plugin for the preDispatchLoop. Previously it was in preDispatch but because of it being called for each action (included those in the 'forwarded' action, it caused me problems.
My problem is that i began working in internationalization and i began using the router to detect the language in the URI: we use the form /language/controller/action). The router would like to use the session to read/store the language. But as you may know the router comes first and then the (pre/post) dispatcher stuff.
So the question would be: why not move the session initialization to the bootstrapping ? it's because it was there before but i had to move it because i need to test that the db (remember that the session uses the db) is accessible to prevent errors. And if there's an error i simply redirect (request->setController/setAction error). If i move back the session initialization code to the bootstrapping i can't make the redirect if the db is not accessible.
I've read other question and i've found many people asking to access the request object from the bootstrapping. But they all say: you can but shouldn't. but then, how would i do in this case ? my last option would be to move back the sessions initialization to the bootstrapping and if it fails, manually send headers and read the view but the error code but that's a horrible hack.
My thoughts are that the session shouldn't be used that early. They shouldn't be called in the bootstrapping as they're not yet fully aware of the controller/action requested. I think that to get the language i could simply rely in cookies (manual) and get it from there (as well as from the URI). And if eventually some day the session info must be used in the bootstrapping i would use a global variable.
What do you think ? is there an error in the way i'm controlling the application ?
Some questions seen:
Zend Framework: Getting request object in bootstrap
Best way to deal with session handling in Zend Framework
(Zend version 1.9.6, not using Application nor Bootstrap)

I would move the session initialization and db connection to the bootstrapping.
If you can't connect to your db during bootstrapp this should count as low-level error. It is not excepted to happen in production.
Simply wrap your bootstrapping process into a try catch block so you can output an error page.
// in your index.php
try {
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap()
->run();
} catch (Exception $e) {
header('Content-type: text/html; charset=utf-8');
header('HTTP/1.1 503 Service Unavailable');
header("Retry-After: 3600");
// Output some error page.
echo "<html><head><title>System Error</title></head><body>...</bod></html>";
?>

Related

laravel event dispatch from controller

Alright so I have been trying to set up a spreadsheet application with concurrent editing. I went down the laravel echo, redis, sockets route. (Any advice to just use pusher will be summarily dismissed). Now for the most part I have this working and I can call my event from tinker and see the data I want flow through redis to sockets and get picked up by my frontend. However the call simply does not work when placed in a controller; its like its just being ignored. For simplicity sake I removed all code from the controller function except for event(new SalesPricingReportEdit($request)); I can use this exact code in tinker storing the exact source of the request from dev tools {"change":[[0,"future contract price",null,750]],"customer_id":101443,"item_id":"MOFT0602-1-550"} and my ws responds on multiple instances of the page across different computers with the data attached to the WS. I can post any code anyone needs to help just let me know what you want to see.
EDIT(resolved): Alright so now that I am mostly finished with this project I have my write up I figured Id post it for posterity this is a process guide that lead me to successfully using laravel events with redis and socket-io via echo.
Register your new Event in laravel go to app/Providers/EventServiceProvider.php and put in the fully namespaced path for your Event that doesnt exist yet. Using the default namespace will save you a headache in implemntation but you can use a non-default
if you wish to gouge your own eyes out while you attempt to route the events on your frontend.
protected $listen = [ 'App\Events\EventName.php' => [
//you would put a handler here but for my case i was only dealing with an
//Event generated by server and handled by the client(browser)
//so it wasn't needed. KISS
],
Once you save that you just use the artisan generate event command and it will read the listener array and scaffold you up an event.
note: While we are in the providers folder lets just note the broadcastServiceProvider and its reference to the routes/channel file this isn't very important for me now but its good to remember
In your app/Events/EventName.php we will be configuring our event. Most of this is standard but there will be a few gotchas I dont want to forget about.
your class should by default implement ShouldBroadcast ... I changed this to shouldBroadcastNow as well as setting my default QUE_DRIVER=sync in global .env and config to sync .. this just avoids an extra overhead of queing the jobs..probably dumb in the long term but, for now queing is a bit overkill.
continue in app/Events/EventName.php
Next notice your construct function. To pass data through an event you need to declare a public variable in the class and set the value for that inside your constructor. By Default laravel will/should/suppposed to pass any public variable in the
class with the event.
public $change;
public function __construct($request){
$this->change = json_decode($request, true);
}
Next in your broadcastOn() function you want to return a new channel...essencially
return new [Type]Channel([channel-name]);
make sure you have all the facades you need in your header.
the broadcastWith() function will let you manipulate the data sent with the event.
I did not use the broadcastAs() function but, it is supposed to allow you to change the Event Name rather than using the default class name.
Now we are going to go to our controller file where we plan to actually fire the event from app/Http/Controllers/xxxxxxxxxController.php
use the syntax
event(new EventName($data))
when you want the event to get kicked off. this will not work as we have not set up redis or sockets but to test this is all in working order go to your .env and change the BROADCAST_DRIVER=log. once done wipe cache and do whatever it is you need to do to get the controller function to run(either by using tinker or by a frontend path you plan on using with the event). you should then be able to see the storage/app/logs/laravel.log contain the information from your event.
Go ahead and install redis on your server and use basic laravel configuration in .env and in config/database.php the only thing to change is to add in the .env BROADCAST_DRIVER=redis.
To avoid some complications go ahead and make sure your CACHE_DRIVER to file.
I have not yet set up multi connections with redis to let redis work both as broadcast and cache; ik its possible but I havent done it. so for now cache_driver can go away.
Also I tried to set this up using PHPredis but I admitted defeat and just used predis. Anyone that can figure this out I will venmo 50 bucks.
Finally to test that you have done this correctly go into
redis-cli > PING ...resp: PONG
Also you can use redis-cli monitor to verify that event data is indeed being pushed to redis.
Now we will start setup with npm we are going install laravel-echo-server, I will also get the pm2 package to act as my daemonizer for echo server.bash it up with
` #!/var/www/repoting/ bash
laravel-echo-server start`
then just run pm2 start socket.sh
when testing you might want to run laravel-echo-server start straight from the cli because the output stream will let you know when it observed an event broadcast in redis.
we will also need the
npm --save socket.io-client
or websockets don't work.
Lets go to resources/assets/js/bootstrap.js
import Echo from "laravel-echo";
window.io = require('socket.io-client');
if (typeof io !== 'undefined') {
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001',
});
}
This is initializing our websocket connection.
Now in our blade file we are going to subscribe to the channel and listen for events. Make sure to include the :6001/socket.io/socket.io.js file or your callbacks will fail
Why are we not doing this in the bootstrap.js you ask? because part of the channel name I am using is being passed by the controller with the view so to subscribe to the right channel I need something like
channel-name-{{$customer_id}}.
What you say I should have properly set up vue and done things the way they were designed to be done in vue....go home your drunk.
So now in resources/views/xxxxx.blade.php
I am using this in conjunction with handsontables so I am going to insert my listener in the afterRender hook.
Echo.channel('EventName-{{$customer_id}}')
.listen('EventName',function(data){
console.log(data);
});
any confusion here about what goes in the channel vs what goes in the listen clause can think of it as using the channelName and then the eventName by default the class name. There seems like in the past to have been some issues
with the class name being namespaced properly but I did not experience any problems.
this should all lead to seeing a console.log with your event data on any page subscribed to the the channel. But for prosperity sake here is the testing order to help verify each step along the way.
Troubleshooting steps
Is the event configured properly in laravel? Change the BROADCAST_DRIVER=log and verify you get a log entry when you run your controller.
Is the data actually being pushed to redis? with the right channel, etc? open redis-cli montior while you trigger an event and you should see feedback like "PUBLISH" "channel-name" "message"
Is echo server/socketio-server registering the event? look at your terminal that is running the laravel-echo-server start command you should see the event propagate from redis to echo there
Is your bootstrap.js file giving you an open web socket? go to dev-tools and check the network->ws if you see an entry click it and then on messages; make sure you see sent and recieved data. If you dont something is wrong in bootstrap.js
Is your clientside js retrieving data from the websocket event? if not you probably forgot to include you socket.io.js file.
Next Steps
-Implement whisper channels to notify the page of who is currently using the page as well as to unsubscribe the from the echo channel and remove any unneeded listeners
-pass handsontable selected data through the whisper channel and set those cells to readonly where the user is different from the current user
I ended up just adding the redis facade and calling Redis::publish(). My guess here is that I couldn't call an event without having a listener setup. I don't need a listener I just needed the event to fire and push it to redis. the real confusing part is why this works without a hitch in tinker and not in a controller is still a mystery but, I solved the immediate problem so this just gets chalked up to an edge case where I implemented something differently than it was probably designed to be.

Where to check if an User is logged in in a Laravel Application?

I've been using your advice and View::sharing all of my important data to all views. However, there is one issue I have encountered.
This code:
if(!Auth::guest()){
$user=Auth::user()->id;
}
else $user=0;
$temp=DB::select('query');
View::share('cartnumber', count($temp));
View::share('cartitems', $temp);
doesn't work when put in AppServiceProvider. Or better, it always sets $user=0, even if I am logged in. I thought it is because AppServiceProvider's boot function executes before the site checks if someone is logged in.
I then tried to use a BaseController with a construct function but that doesn't work either. The only solution that seems to work correctly is putting the code in every single Controller for every view! That actually works, which kind of confirms my theory.
But is there anywhere I can put this code without having to copy/paste it in every single Controller? Thanks in advance!
You'd likely want to put this code later in the request life cycle to guarantee an auth user because as others have mentioned middleware/session code has not occured during this part of the framework booting up. You could use a service class to call in all your controllers to avoid the copy pasting. Or If you'd like to achieve this using code in your service provider you could use a View Composer instead of a share this allows you to define a callback/or class that will be called right before the view is returned
view()->composer(['/uri-that-needs-data'], function ($view) {
if (Auth::check()) {
$cart = DB::query(...)->get();
$view->with('cartitems', $cart);
}
});
Check out https://laravel.com/docs/5.7/views#view-composers for more details.
Auth::user() will be empty until the session middleware has run.
The reason you can't access the user inside your service provider is because that code is run during the "bootstrapping" phase of the application lifecycle, when it's doing things like loading filesystem or cache drivers, long before the request is sent through response handlers (including middleware).
Once the application has been bootstrapped and all service providers
have been registered, the Request will be handed off to the router
for dispatching. The router will dispatch the request to a route or
controller, as well as run any route specific middleware.
Source: https://laravel.com/docs/5.7/lifecycle
If you don't want to copy/paste that code everywhere, then one place to put it is in custom route middleware. You can list it after the auth middleware to guarantee a logged-in user.
Edit: View composers are another really good option, as suggested by #surgiie. The reason these can be set up inside a service provider (unlike your example) is because the view composer registers a callback, but doesn't execute it until a much later stage in the application lifecycle.

Corrupt Kohana session after inserting a custom object

Description
I am trying to add a custom object called ReviewHolder to a session. This is being done in the "review" controller. The ReviewHolder class is included in the classes/controller/review.php file. I am using Kohana version 3.2.
The code
$session = Session::instance();
$reviewholder = $session->get('reviewholder');
if($reviewholder == null) {
$session->set('reviewholder', new ReviewHolder());
}
The problem
The problem is that after I execute the above code, the session gets corrupted and almost the entire website stops functioning correctly. This is the error message on every page:
Session_Exception [ 1 ]: Error reading session data.
The weird part
All pages that are loaded from the "review" controller are still all function correctly! It looks like the above code is messing up every other session in the application...
Debug
I have debugged the session to see if the object is set correctly with the following code:
echo Debug::vars($session->get('reviewholder'));
This shows the object correctly, so it has been set in the session.
Can someone help me out here? Thanks!
It doesn't really matter whats inside the session file.
All i know is that you can only store objects that are serialized in the session.
After serializing you can store it in an session and to make it an object again for later use. You can unserialize the serialized object.
Hope this helps!
I found the following worked for me when storing a custom object in the session:
Make sure your session directory is set correctly in PHP, ie. the directory exists, permissions allow your web server to write to the directory
Force the class for your object to be loaded in bootstrap.php by adding a line such as:
require_once( Kohana::find_file('classes','Library/MyClass') );
After those two steps I no longer saw the session corrupt exception

codeigniter php native sessions without using cookies or URL session id, but matching browserfingerprints in database

Because of european privacy law being harsly applied in the Netherlands and to keep my company's site user friendly without nagging the users with questions if it's okay to store a cookie on their computer that allows me to access their client data.
What I need is a way to "overwrite" the native php sessions class so that at the point where the native class requests the cookie that stores the phpsessid, that I can place my own code there that checks the browsers fingerprint and matches that to a session id which I can use to return the normal working of the native class.
My idea is:
table sess_fingerprints
Fields: fingerprint - phpsessid
function getsessionid()
{
$result = $this->db->query("SELECT phpsessid
FROM `sessiondatabase`.`sess_fingerprints`
WHERE `sess_fingerprints`.`fingerprint` = '$userfingerprint'");
if($result->num_rows() != 0)
{
return $result->row->phpsessid;
}
}
and at that point the native php session code just works as it would normally do.
So, my question is: is it possible to overwrite only the "cookie" part of the phpsession class? if so, how? because I haven't found that yet.
I'm aware of being able to pass along the session variable via urls etc, but that's not secure enough for my web applications.
PHP provides support for custom session handlers:
http://php.net/manual/en/session.customhandler.php
I think I have found the solution to my problem.
I'm going to override the functions related to cookies by using http://php.net/manual/en/function.override-function.php
Thank you all for thinking along.

Manually start session with specific id / transitioning session cookie between domains

My host requires me to use a different domain for SSL secured access (shared SSL), so I need to transition the user session between two domains. One part of the page lives at http://example.com, while the SSL'd part is at https://example.hosting.com. As such I can't set a domain-spanning cookie.
What I'm trying to do is to transition the session id over and re-set the cookie like this:
http://example.com/normal/page, user clicks link to secure area and goes to:
http://example.com/secure/page, which causes a redirect to:
https://example.hosting.com/secure/page?sess=ikub..., which resurrects the session and sets a new cookie valid for the domain, then redirects to:
https://example.hosting.com/secure/page
This works up to the point where the session should be resurrected. I'm doing:
function beforeFilter() {
...
$this->Session->id($_GET['sess']);
$this->Session->activate();
...
}
As far as I can tell this should start the session with the given ID. It actually generates a new session ID though and this session is empty, the data is not restored.
This is on CakePHP 1.2.4. Do I need to do something else, or is there a better way to do what I'm trying to do?
When Configure::write('Security.level') is set to medium or higher, session.referer_check is implicitly activated, which makes the whole thing fail. Setting the security level to low (or using a custom session configuration) makes everything work as it should.
There went about 5 hours of debugging... ( -_-;;)
My first thought is to use the Cake file sessions and copy the file over, and then perhaps try and start a new session with that phpsessid, although I'm not even sure if that would actually work or not :)
With Cake 2.6.1 -- This is what worked for me.
$this->Session->id("tfvjv43hjmsnjkh0v3ss539uq7"); // add session id you want to set
$this->Session->id();
$this->Session->read("key"); // hhoorray worked :)
with SessionComponent id() function needs to be called twice once with session id to set session_id(); and second time to start cake session.
First call does not really start the session ... I dont know how Cake Guys missed it .....
Upvote if this works for you.

Resources