I found how to get a session container like this:
$session = new \Zend\Session\Container('base');
But what if I need to access the session in many places during processing a HTTP request.
Let's say in the Application module's indexAction in the IndexController, then I redirect it to the User\Controller\IndexController and need to access the session again, and then in a view helper or two, and who knows how often more.
When constructing the session container every time anew, that is a waste of processing time. Yes, I debugged it to see what's going on in the constructor, and yes, there is some code executed behind the scenes. It is not as if the constructor would just return a global variable or something else which would be immutable and doesn't need a construction process.
So what to do?
Should I create a service for it?
a controller plugin?
a view helper?
a service and a controller plugin and a view helper, with the latter calling the service?
I'm sure it is something that many people must have come across and have dealt with, but I can't find any information on this.
Any hint is dearly appreciated.
Many thanks in advance! :-)
Here's a more refined and improved version.
It consists of the service "SessionService", a ViewHelper (which calls the SessionService), a ControllerPlugin (which also calls the SessionService), and shows how to set them up in the configuration file "module.config.php".
Make sure you set "use" paths or use absolute class paths in config.
SessionService.php:
class SessionService
{
protected $sessionContainer;
public function setSessionContainer(
$sessionContainer
) {
$this->sessionContainer = $sessionContainer;
}
public function __invoke() {
return $this->sessionContainer;
}
}
SessionHelper.php:
class SessionHelper extends \Zend\View\Helper\AbstractHelper
{
protected $sessionService;
public function setSessionService(
$sessionService
) {
$this->sessionService = $sessionService;
}
public function __invoke() {
return $this->sessionService;
}
}
SessionPlugin.php:
class SessionPlugin extends AbstractPlugin
{
protected $sessionService;
public function setSessionService(
$sessionService
) {
$this->sessionService = $sessionService;
}
public function __invoke() {
return $this->sessionService;
}
}
module.config.php:
'service_manager' => array(
'factories' => array(
'sessionService' => function(
ServiceLocatorInterface $serviceLocator
) {
$sessionContainer = new \Zend\Session\Container('base');
$sessionService = new SessionService();
$sessionService->setSessionContainer($sessionContainer);
return $sessionService;
},
),
),
'controller_plugins' => array(
'factories' => array(
'sessionPlugin' => function(
AbstractPluginManager $pluginManager
) {
$sessionService = $pluginManager->getServiceLocator()->get('sessionService');
$sessionPlugin = new SessionPlugin();
$sessionPlugin->setSessionService($sessionService);
return $sessionPlugin;
},
),
),
'view_helpers' => array(
'factories' => array(
'sessionHelper' => function (
AbstractPluginManager $helperPluginManager
) {
$sessionService = $helperPluginManager->getServiceLocator()->get('sessionService');
$sessionHelper = new SessionHelper();
$sessionHelper->setSessionService($sessionService);
return $sessionHelper;
},
),
),
In your Controller write:-
use Zend\Session\Container;
Make Session variable
$user_session = new Container('user');
'user' is Your Session Name To put Value in Your Session write:
$user_session->username = 'xyz';
After Storing You can Access Your Session By:
$user_session-> username
To destroy Session Variable Use:
$session = new Container('user');
$session->getManager()->getStorage()->clear('user');
it is Just Like : -
unset($_SESSION['user']);
http://wownewcode.blogspot.in/2013/12/set-session-in-zend-framework-2.html
Once you've initialized a session Container you can just re-use it with $_SESSION['container_name'];
Basically $session = new \Zend\Session\Container('base'); will create an ArrayObject named base inside $_SESSION. One advantage of initializing by creating a Container is that you can specify a TTL or expiration after x-hops.
$_SESSION['base']['key'] = "store this value";
I think you need to use session Manager and service with the container storage to achieve your goal.
you can set it in your application Module
Application\Module.php
use Zend\Session\Config\SessionConfig;
public function onBootstrap(EventInterface $e)
{
//Your other code here
//configure session
$sessionConfig = new SessionConfig();
$sessionConfig->setOptions($config['session']);
}
and in module.config.php
'session' => array(
'save_path' => realpath(ZF2PROJECT_ROOT . '/data/session'),
'name' => 'ZF2PROJECT_SESSION',
),
and in your services you can use like this. Forexample in Authentication service.
class AuthenticationService
{
protected $storage = null;
public function getStorage()
{
if (null === $this->storage) {
$this->setStorage(new Storage\Session());
}
return $this->storage;
}
}
Here is my current provisional solution or workaround, consisting of:
- a service for storing the session container object.
- a controller plugin for easy access to the session container, without having to inject a dependency for it in every controller.
The session service:
class Session
{
private static $container;
public function getContainer() {
if (!isset(self::$container)) {
self::$container = new \Zend\Session\Container('base');
}
return self::$container;
}
public function __invoke() {
return $this->getContainer();
}
}
The controller plugin:
class Session extends AbstractPlugin
{
protected $sessionService;
public function __construct(
SessionService $sessionService
) {
$this->sessionService = $sessionService;
}
public function getContainer() {
return $this->sessionService->getContainer();
}
public function __invoke() {
return $this->getContainer();
}
}
Configuration in module.config.php:
'service_manager' => array(
'factories' => array(
'sessionService' => function($sm) {
return new Application\Service\Session\Session();
},
),
),
'controller_plugins' => array(
'factories' => array(
'session' => function($serviceLocator) {
$sessionService = $serviceLocator->get('sessionService');
return new Application\Service\Mvc\Controller\Plugin\Session($sessionService);
},
),
),
Usage example in any controller or controller plugin method:
$sessionContainer = $this->session->getContainer();
or short form (because session service and controller plugin both implement __invoke):
$sessionContainer = $this->session();
and then can use the session container to store any variables in it, like this:
$sessionContainer->foo = 'bar';
Because the session service is created by a factory function through module.config.php, it is only created once.
The actual session container is a static variable in the session service and only created once, i.e. if it doesn't exist.
In subsequent calls to the getSessionContainer function, this only once created static session container is returned.
This is just a provisional workaround solution, works for me for now, but for making it re-usable for other applications also, it should provide functions to customize the session container name and the storage place and strategy, those parts are missing in this simple workaround solution.
Note: A view helper should not be necessary for it. Session variables should not be set in a view, and if a view needs read access to them, the data should be passed via a view model from controller to view.
Related
I made a view helper that checks if an external URL exists before outputting it. Some of those URLs are in my main layout, so that check is quite slowing down my site by calling all those urls all the times, to check if they exist. I would like to save the output of that function so that it only checks an URL if the same one hasn't been checked already in less than an hour, or a day. I believe I should use Zend Cache to do that? But I have no idea where to start, do you have any suggestions, easy solutions or some basic tutorial to learn? Thanks!
Add global config for cache service, like here:
config/autoload/global.php
'service_manager' => array(
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
)
),
config/autoload/cache.global.php
return array(
'caches' => array(
// Cache config
)
)
Use factory to create your View Helper:
Application/Module.php::getViewHelperConfig()
'LinkHelper' => function ($sm) {
$locator = $sm->getServiceLocator();
return new LinkHelper($locator->get('memcached'))
}
Use cache service in your View Helper:
LinkHelper.php
protected $cache;
public function __construct($cache)
{
$this->cache = $cache;
}
public function __invoke($url)
{
$cacheKey = md5($url);
if ($this->cache->hasItem($cacheKey) {
return $this->cache->getItem($cacheKey);
}
$link = ''; // Parse link
$this->cache->setItem($cacheKey, $link);
return $link;
}
I added this in routes.php, expected it will check the authentication session for the page, however it is not working.
Route::resource('ticket', 'TicketController', array('before' => 'auth') );
Then I go to the controller, work in another way. It's work.
class TicketController extends BaseController {
public function __construct()
{
$this->beforeFilter('auth');
}
May I know where can get more documentation regarding the Route::resource(), what type of argument it able to accept?
OK... I found the answer.
in
\vendor\laravel\framework\src\Illuminate\Routing\Router.php
public function resource($resource, $controller, array $options = array())
{
// If the resource name contains a slash, we will assume the developer wishes to
// register these resource routes with a prefix so we will set that up out of
// the box so they don't have to mess with it. Otherwise, we will continue.
if (str_contains($resource, '/'))
{
$this->prefixedResource($resource, $controller, $options);
return;
}
// We need to extract the base resource from the resource name. Nested resources
// are supported in the framework, but we need to know what name to use for a
// place-holder on the route wildcards, which should be the base resources.
$base = $this->getBaseResource($resource);
$defaults = $this->resourceDefaults;
foreach ($this->getResourceMethods($defaults, $options) as $method)
{
$this->{'addResource'.ucfirst($method)}($resource, $base, $controller);
}
}
protected function getResourceMethods($defaults, $options)
{
if (isset($options['only']))
{
return array_intersect($defaults, $options['only']);
}
elseif (isset($options['except']))
{
return array_diff($defaults, $options['except']);
}
return $defaults;
}
as you can see, it only accept only and except arguement only.
If you want to archive the same result in route.php, it can be done as below
Route::group(array('before'=>'auth'), function() {
Route::resource('ticket', 'TicketController');
});
I'm planning to use ZF2 in a future project, so I'm trying Zend Framework 2 RC1 now. I started with authentication step, and noticed that when i chose a different name than 'Zend_Auth' for session storage namespace, i can't access to object stored in session (AuthenticationService class' hasIdentity method returned false, despite User object data set in session).
<?php
namespace Application\Controller;
use Zend\Authentication\Adapter\DbTable as AuthAdapter;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Storage\Session as SessionStorage;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Application\Model\User;
use Application\Form\LoginForm;
class LoginController extends AbstractActionController
{
public function indexAction()
{
$auth = new AuthenticationService();
if ($auth->hasIdentity()) {
return $this->redirect()->toRoute('application');
}
$form = new LoginForm();
return array('form' => $form);
}
public function loginAction()
{
$auth = new AuthenticationService();
$form = new LoginForm();
$form->get('submit')->setAttribute('value', 'Add');
$request = $this->getRequest();
if ($request->isPost()) {
$user = new User();
$form->setInputFilter($user->getInputFilter('login'));
$form->setData($request->getPost());
if ($form->isValid()) {
$data = $form->getData();
// Configure the instance with constructor parameters...
$sm = $this->getServiceLocator();
$dbAdapter = $sm->get('db-adapter');
$authAdapter = new AuthAdapter($dbAdapter, 'users', 'username', 'password');
$authAdapter
->setIdentity($data['username'])
->setCredential(sha1($data['password']));
// Use 'users' instead of 'Zend_Auth'
$auth->setStorage(new SessionStorage('users'));
$result = $auth->authenticate($authAdapter);
if ($result->isValid()) {
// store the identity as an object where only the username and
// real_name have been returned
$storage = $auth->getStorage();
// store the identity as an object where the password column has
// been omitted
$storage->write($authAdapter->getResultRowObject(
null,
'password'
));
// Redirect to list of application
return $this->redirect()->toRoute('application');
}
}
}
// processed if form is not valid
return array('form' => $form);
}
}
In this code, when i changed the below line,
$auth->setStorage(new SessionStorage('users'));
like this:
$auth->setStorage(new SessionStorage());
hasIdentity method returned true.
I checked two classes Zend\Authentication\AuthenticationService and Zend\Authentication\Storage\Session, and didn't see a way to access session data which has different session namespace other than default.
What i need to understand is how can i access session data which has a different namespace and if there is no way to do it for now, should we define this as a bug?
I can update the question if any other information needed.
We are kinda missing one part of your code, the one where you try and receive the user identity. im guessing that you have forgotten to pass the the SessionStorage Object with the same namespace.
Also the configuration of the Authentication object should be moved to a factory so these kind of issues to not arrise.
Thats my five cents atleast :)
I'm working on testing a shopping cart, checkout, payment process on Zend Framework with phpunit. I'm testing ShoppingCartController by adding products to cart, a ShoppingCart Model handles product additions by storing product id's in a Zend Session Namespace, and then in another test I want to test that the products were added. The same ShoppingCart Model retrieves a list of added products from the same Zend Session namespace variable.
The add product test looks like this and works well, and the var_dump($_SESSION) was added to debug and shows the products correctly:
public function testCanAddProductsToShoppingCart() {
$testProducts = array(
array(
"product_id" => "1",
"product_quantity" => "5"
),
array(
"product_id" => "1",
"product_quantity" => "3"
),
array(
"product_id" => "2",
"product_quantity" => "1"
)
);
Ecommerce_Model_Shoppingcart::clean();
foreach ($testProducts as $product) {
$this->request->setMethod('POST')
->setPost(array(
'product_id' => $product["product_id"],
'quantity' => $product["product_quantity"]
));
$this->dispatch($this->getRouteUrl("add_to_shopping_cart"));
$this->assertResponseCode('200');
}
$products = Ecommerce_Model_Shoppingcart::getData();
$this->assertTrue($products[2][0]["product"] instanceof Ecommerce_Model_Product);
$this->assertEquals($products[2][0]["quantity"],
"8");
$this->assertTrue($products[2][1]["product"] instanceof Ecommerce_Model_Product);
$this->assertEquals($products[2][1]["quantity"],
"1");
var_dump($_SESSION);
}
The second test attempts to retrieve the products by asking the model to do so, the var_dump($_SESSION) is null already at the beginning of the test. The session variables were reset, I want to find a way to preserve them, can anyone help?
public function testCanDisplayShoppingCartWidget() {
var_dump($_SESSION);
$this->dispatch($this->getRouteUrl("view_shopping_mini_cart"));
$this->assertResponseCode('200');
}
Sorry for pointing you in the wrong direction. Here is a way better way of achieving this, suggested by ashawley from #phpunit channel of irc.freenode.net:
<?php
# running from the cli doesn't set $_SESSION here on phpunit trunk
if ( !isset( $_SESSION ) ) $_SESSION = array( );
class FooTest extends PHPUnit_Framework_TestCase {
protected $backupGlobalsBlacklist = array( '_SESSION' );
public function testOne( ) {
$_SESSION['foo'] = 'bar';
}
public function testTwo( ) {
$this->assertEquals( 'bar', $_SESSION['foo'] );
}
}
?>
== END UPDATE
In function tearDown(): copy $_SESSION to a class attribute and
In function setUp(): copy the class attribute to $_SESSION
For example, this test fails when you remove the functions setUp() and tearDown() methods:
<?php
# Usage: save this to test.php and run phpunit test.php
# running from the cli doesn't set $_SESSION here on phpunit trunk
if ( !isset( $_SESSION ) ) $_SESSION = array( );
class FooTest extends PHPUnit_Framework_TestCase {
public static $shared_session = array( );
public function setUp() {
$_SESSION = FooTest::$shared_session;
}
public function tearDown() {
FooTest::$shared_session = $_SESSION;
}
public function testOne( ) {
$_SESSION['foo'] = 'bar';
}
public function testTwo( ) {
$this->assertEquals( 'bar', $_SESSION['foo'] );
}
}
Also there is a backupGlobals feature but it doesn't work for me. You should try it thought, maybe it works on stable PHPUnit.
that's a very ugly of doing that. The right way should be using dependency injection.
That implies changing your source code to use this class instead of sessions directly:
class Session
{
private $adapter;
public static function init(SessionAdapter $adapter)
{
self::$adapter = $adapter;
}
public static function get($var)
{
return self::$adapter->get($var);
}
public static function set($var, $value)
{
return self::$adapter->set($var, $value);
}
}
interface SessionAdapter
{
public function get($var);
public function set($var, $value);
}
Additional information:
http://community.sitepoint.com/t/phpunit-testing-cookies-and-sessions/36557/2
Using PHPUnit to test cookies and sessions, how?
You can also just create a random session id for your PHPUnit test, and then make sure you pass this session id in a cookie in every further call you make. With Curl, you would use the CURLOPT_COOKIE option and set it to 'PHPSESSID=thesessionidofyourunittest' as such:
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_COOKIE, 'PHPSESSID=thesessionidofyourunittest');
I explained more in detail and with an example in this stackoverflow answer.
<?php
class Home extends CI_Controller
{
public function __construct()
{
// load libraries //
$this->load->library('session');
$this->load->library('database');
$this->load->library('captcha');
// alternative
$this->load->library(array('session', 'database', 'captcha'));
// load models //
$this->load->model('menu_model', 'mmodel');
$this->load->model('user_model', 'umodel');
$this->load->model('admin_model', 'amodel');
// alternative
$this->load->model(array(?));
}
}
?>
How can i load all models in array? is it possible?
For models, you can do this:
$models = array(
'menu_model' => 'mmodel',
'user_model' => 'umodel',
'admin_model' => 'amodel',
);
foreach ($models as $file => $object_name)
{
$this->load->model($file, $object_name);
}
But as mentioned, you can create file application/core/MY_Loader.php and write your own method for loading models. I think this might work (not tested):
class MY_Loader extends CI_Loader {
function model($model, $name = '', $db_conn = FALSE)
{
if (is_array($model))
{
foreach ($model as $file => $object_name)
{
// Linear array was passed, be backwards compatible.
// CI already allows loading models as arrays, but does
// not accept the model name param, just the file name
if ( ! is_string($file))
{
$file = $object_name;
$object_name = NULL;
}
parent::model($file, $object_name);
}
return;
}
// Call the default method otherwise
parent::model($model, $name, $db_conn);
}
}
Usage with our variable from above:
$this->load->model($models);
You could also allow a separate DB connection to be passed in an array, but then you'd need to have a multidimensional array, and not the simple one we used. It's not too often you'll need to do that anyways.
I don't have any idea about the CodeIgniter 2.x but in CodeIgniter 3.x, this will also works :
$models = array(
'menu_model' => 'mmodel',
'user_model' => 'umodel',
'admin_model' => 'amodel',
);
$this->load->model($models);
Not natively, but you can easily extend Loader->model() to support that logic.
This work fine for me:
$this->load->model(array('menu_model'=>'menu','user_model'=>'user','admin_model'=>'admin'));