I am trying to set a cookie on every page load. I did some research and found that event subscribers are the way to go.
I created a custom module with event. It starts the event on every page load but the problem is with the cookies.
The $event object has property called response but it is always null.
Therefore i cannot set any cookies.
class LanguageCookieSubscriber implements EventSubscriberInterface
{
protected $event;
protected $cookieValue;
public function init(GetResponseEvent $event) {
$this->event = $event;
$cookie = new Cookie("client_language_cookie", $this->cookieValue, 0, '/', NULL, FALSE);
$this->event->getResponse()->headers->setCookie($cookie);
}
}
I also tried to set the response object. Then I can set the cookie but the page will come blank.
$response = new Response();
$this->event->setResponse($response);
$cookie = new Cookie("client_language_cookie", $this->cookieValue, 0, '/', NULL, FALSE);
$this->event->getResponse()->headers->setCookie($cookie);
Any ideas how can i solve this? I need to display the page user has requested and only set cookie.
Thanks in advance.
Example:
public function onKernelResponse(ResponseEvent $event) {
$response = $event->getResponse();
$cookie = new Cookie('cookie name', 'my_cookie value', time() + 60 * 60 * 24 * 7);
$response->headers->setCookie($cookie);
$event->setResponse($response);
}
I've been using Slim Framework 2 for a while but want to switch to the newest version 3. When reading the upgrade guide, I was a bit bummed about them simply stating that "cookies has been removed from the core" and referring to the FIG Cookies github repo that contains code snippets that simply don't work with Slim.
Could anyone share some working code snippets that set and get some dummy cookies using Slim 3? Thanks.
If you don't want to use the tested PSR-7 library FIG Cookies you can use this:
namespace Your\App;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class Cookie
{
/**
* #param Response $response
* #param string $key
* #param string $value
* #return Response
*/
public function deleteCookie(Response $response, $key)
{
$cookie = urlencode($key).'='.
urlencode('deleted').'; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; httponly';
$response = $response->withAddedHeader('Set-Cookie', $cookie);
return $response;
}
/**
* #param Response $response
* #param string $cookieName
* #param string $cookieValue
* #return Response
*/
public function addCookie(Response $response, $cookieName, $cookieValue)
{
$expirationMinutes = 10;
$expiry = new \DateTimeImmutable('now + '.$expirationMinutes.'minutes');
$cookie = urlencode($cookieName).'='.
urlencode($cookieValue).'; expires='.$expiry->format(\DateTime::COOKIE).'; Max-Age=' .
$expirationMinutes * 60 . '; path=/; secure; httponly';
$response = $response->withAddedHeader('Set-Cookie', $cookie);
return $response;
}
/**
* #param Request $request
* #param string $cookieName
* #return string
*/
public function getCookieValue(Request $request, $cookieName)
{
$cookies = $request->getCookieParams();
return isset($cookies[$cookieName]) ? $cookies[$cookieName] : null;
}
}
Slim 3 has Cookies class. you are not forced to use external library for setting cookie:
$setcookies = new Slim\Http\Cookies();
$setcookies->set('auth',['value' => $jwt, 'expires' => time() + $expire, 'path' => '/','domain' => 'example.com','httponly' => true,'hostonly' => false,'secure' => true,'samesite' => 'lax']);
$setcookies->set('tracking', "$value");
$response = $response->withHeader('Set-Cookie', $setcookies->toHeaders());
And for getting cookie :
$jwt = $request->getCookieParam('auth');
I'was experiencing the same problem but, after a few tries, i figured out!
First you need to use :
$cookies = Dflydev\FigCookies\Cookies::fromRequest($request);
To get all the cookies sent by the client.
Or:
$cookie = FigRequestCookies::get($request, $cookiename);
To get a single cookie.
But the 'strange' part is how to set a cookie, so here's a little example:
function setCookie($response, $name, $value){
$response = FigResponseCookies::set($response, SetCookie::create($name)
->withValue($value)
->rememberForever()
);
return $response;
}
With :
$response = FigResponseCookies::set($response, SetCookie::create($name)
->withValue($value)
->rememberForever()
);
You'll add a new cookie to the request, this method returns a new request object with the new cookie in it.
So for all the other operations, you need to use the new request not the old one.
I hope this will help.
if you want post your code and we'll try to debug it.
I know I was asked to answer for cookies on slim 3, but since I could not find anywhere how to do this on slim 4, I was telling how to do it in case it could be useful to someone else.
If you are using Slim 4 with slim/psr7 you can set cookies from inside a middleware or a router, in this way:
To SET a cookie (put a cookie on a browser):
$cookies = new \Slim\Psr7\Cookies();
$cookies->setDefaults(['hostonly' => true, 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
$cookies->set('MyCookieName', ['value' => '', 'samesite' => 'Strict']); // this way I can ovveride the samesite attribute for this cookie only
$response=$response->withHeader('Set-Cookie', $cookies->toHeaders());
unset($cookies);
To unset a cookie (delete a cookie from a browser)
$cookies = new \Slim\Psr7\Cookies();
$cookies->setDefaults(['hostonly' => true, 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
$cookies->set('MyCookieName', ['value' => '', 'expires' => 1); // this way I can delete this cookie, setting it already expired the first sec in 1970
$response=$response->withHeader('Set-Cookie', $cookies->toHeaders());
unset($cookies);
To get the cookie (get the cookie sent from the browser)
$cookies = new \Slim\Psr7\Cookies($request->getCookieParams());
echo "cookie value is: " . $cookies->get('MyCookieName');
The easiest way I found to do this is as follows.
use Dflydev\FigCookies\SetCookie;
use Dflydev\FigCookies\FigResponseCookies;
$app->post('/login', function ($request, $response, $args) {
// Here you may check request data
$token = '123'; // Here you may use something like JWT::encode
return $this->response = FigResponseCookies::set($response, SetCookie::create('token')
->withValue($token)
->withDomain($_SERVER['HTTP_HOST'])
->withPath('/')
)->withJson([
'description' => 'Here goes your Json Body'
]);
});
Install vendor solution:
composer require sunrise/http-header-kit
Set the session cookie:
use Sunrise\Http\Header\HeaderSetCookie;
$header = new HeaderSetCookie('name', 'value', new \DateTime('+1 day'), [
'path' => '/',
'secure' => true,
'httponly' => true,
]);
$response = $header->addToMessage($response);
See the source code to learn more:
HTTP Header Kit for PHP 7.2+ based on PSR-7
I would recommend NOT using dflydev/dflydev-fig-cookies. It's broken and the documentation is either wrong or missing.
Based on LUXS's and Norman's answers, here's a working example with comments, for Slim 3:
/**
* Fig-cookies.
*
* Mind the required classes; mind the fact that (because PSR-7) this sets the
* cookie by modifying the response before returning it.
*
* Kudos to https://stackoverflow.com/a/55288070/1717535 and
* https://stackoverflow.com/a/42065030/1717535 for providing working examples.
*
* Memo about cookines in general:
* - if you don't set an expire date, it'll be a session cookie.
* - dot or no dot before the domain?
* - if no domain is specified, the cookie will be set without a dot.
* - if a domain is specified, it'll be the dotted version.
* - if both are set, seems the one set more recently will be used on read.
* - Seems the dotted/sub-domains version comes recommended.
* - But it forces to turn https://example.org into example.org, for all
* of local dev, ngrok, production, for no obvious benefit. So screw it.
* - Reconsider the day we'll use subdomains.
* - See https://stackoverflow.com/a/39708227/1717535
*
* Can use `->withDomain` to set the domain, without URL scheme and without
* trailing slash. e.g.: `->withDomain('clickandspeak.localhost')`
*/
use Dflydev\FigCookies\FigResponseCookies;
use Dflydev\FigCookies\SetCookie;
use Dflydev\FigCookies\FigRequestCookies;
$app->get('/tests/fig-cookies/set/', function ($request, $response, $args) {
$response = FigResponseCookies::set(
$response,
SetCookie::create('foo')
->withValue('bar')
->rememberForever()
->withPath('/')
);
// Do whatever else you need to do in your route…
// Then return the response.
return $response;
});
$app->get('/tests/fig-cookies/get/', function ($request, $response, $args) {
$cookie = FigRequestCookies::get($request, 'foo');
// but this is long and ugly and I would recommend Slim's (undocumented) built-in function instead:
// $request->getCookieParam('foo')
echo $cookie->getValue();
});
As for deleting/removing a cookie… Well:
$app->get('/tests/fig-cookies/delete/', function ($request, $response, $args) {
// $request = FigRequestCookies::remove($request, 'foo');
// $response = FigResponseCookies::expire($response, 'foo');
// Works. The cookie gets deleted.
// $response = FigResponseCookies::set(
// $response,
// SetCookie::create('foo')
// ->withExpires(new DateTime('-5 years'))
// ->withPath('/') // MUST use the same path as that used when creating the cookie.
// );
/**
* This is the example given in the README. But it does NOT work.
* It won't work because the path is missing; there's no option to set it!
* See https://github.com/dflydev/dflydev-fig-cookies/issues/23
*/
// $response = FigResponseCookies::expire($response, 'foo');
return $response;
});
I don't know if it will help anyone 4 years later but...
You can use the FIG Cookies library, turning it into a class with static functions. Like this:
namespace YourApp\Cookies;
use Dflydev\FigCookies\FigResponseCookies;
use Dflydev\FigCookies\SetCookie;
use Dflydev\FigCookies\FigRequestCookies;
use Dflydev\FigCookies\Modifier\SameSite;
use Carbon\Carbon; // I used Carbon as well
class Cookie
{
static function get(&$req, $key, $default = null)
{
$cookie = FigRequestCookies::get($req, $key, $default);
if (!$cookie->getValue()) {
return null;
}
return $cookie->getValue();
}
static function set(&$res, $key, $val, $expire_val, $expire_unit)
{
$now = Carbon::now();
$offset = Carbon::now()->add($expire_val, $expire_unit);
$res = FigResponseCookies::set($res, SetCookie::create($key)
->withValue($val)
->withExpires($offset->toCookieString())
->withMaxAge($offset->diffInSeconds($now))
->withPath('/')
->withDomain('appdomain.com')
->withSecure(false)
->withHttpOnly(true)
->withSameSite(SameSite::lax())
// You can learn more about the available options on the official GitHub page
);
return $res;
}
static function remove(&$res, $key)
{
$offset = Carbon::now()->sub(10, 'years');
$res = FigResponseCookies::set($res, SetCookie::create($key)
->withExpires($offset->toCookieString())
->withMaxAge(0)
->withPath('/')
->withDomain('appdomain.com')
);
return $res;
}
}
This way, it becomes really easy to deal with cookies.
You can use it like this:
use YourApp\Cookies\Cookie;
Cookie::set($response, 'name', 'value', 1, 'month') // To create a cookie
Cookie::get($request, 'name') // To get the value of a cookie
Cookie::remove($response, 'name') // To remove it
Or, if you prefer, like this:
\YourApp\Cookies\Cookie::set($response, 'name', 'value', 1, 'month')
\YourApp\Cookies\Cookie::get($request, 'name')
\YourApp\Cookies\Cookie::remove($response, 'name')
That's the way I use it.
I'm writing unit tests for my application. I wrote a function to login different user (to test user levels) and a function to generate valid or invalid form data (to test my form handling).
When the test submits a form, it throws an exception:
Uncaught PHP Exception LogicException: "Cannot set session ID after the session has started."
I'm using Symfony 2.6.4. I can't find any usefull information about this error message. The test worked perfectly a while ago.
class ControllerTest extends WebTestCase
{
public $client = null;
public $route = 'home/';
/**
* #var \Doctrine\ORM\EntityManager
*/
public $em;
public function setUp()
{
self::bootKernel();
$this->client = static::createClient();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager()
;
}
public function logIn($role = 'admin')
{
if ($role === 'admin') {
$userId = 20;
} elseif ($role === 'user') {
$userId = 29;
}
$user = $this->em->getRepository('Acme\DemoBundle\Entity\User')->find($userId);
$session = $this->client->getContainer()->get('session');
$firewall = 'main';
$token = new UsernamePasswordToken($user, $user->getPassword(), $firewall);
$session->set('_security_'.$firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
public function getFormData($valid = true)
{
//function to generate (in)valid formdata
}
public function getFormRequest($data, $url)
{
return $this->client->request(
'POST',
$url,
$data,
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_X-Requested-With' => 'XMLHttpRequest',
]
);
}
//works OK
public function testNewScenario()
{
$url = $this->baseurl . 'new';
$this->logIn('admin');
$crawler = $this->client->request('GET', $url);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET " . $url);
}
public function testValidNewScenario()
{
$this->logIn('admin');
$validData = $this->getFormData(true);
//this function throws the exception
$this->getFormRequest($validData, $this->baseurl);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode(), "Unexpected HTTP status code for POST " . $this->baseurl);
}
}
Here's the relevant part of my config_test.yml:
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
What's going on?
I don't know if this is still a problem for the OP as this is an old post, but the same issue had me running around in circles for best part of 3 hours trying to find a way out of it. And seeing as there doesnt seem to be a solution anywhere at all. Heres a possible one.
The problem exists in tests which are trying to create a full login.
Current symfony docs state that its preferred to use basic_http authentication in your tests, but if, like me, you need to be testing access levels youll need to be following this method.
The problem seems to occur when we try to set the cookieJar up. This (for me) always threw an error.
Cannot set session ID after the session has started
the solution as it turns out is reasonably simple. Wrap the cookie set code in a condition that checks for a current session id.
if( !$this->session->getId() ) {
$this->cookie = new Cookie( $this->session->getName(), $this->session->getId() );
$this->client->getCookieJar()->set( $this->cookie ); // <--- this is the problem line
}
its also worth noting that calling $this->session->invalidate() does not solve the issue.
I hope this helps someone and saves them some time.
This effected me on Symfony2.1 (no chance of upgrading), but Ive seen mentions of 2.6 getting it when combined with FOSFacebookBundle (where I believe the issue was fixed).
I can't seem to get cookies set from my controller.
code:
/**
* #Route("", name="wx_exchange_default_index")
* #Template("WXExchangeBundle:Default:index.html.twig")
* #Method({"GET"})
*/
public function indexAction(Request $request)
{
$returnArray['parents'] = self::getCategories();
$cookieLocationSession = $request->cookies->get('locationidsession', 0);
$cookieLocation = $request->cookies->get('locationid', 0);
$securityContext = $this->container->get('security.context');
$hashids = $this->get("wxexchange_hashids_service");
if ($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED'))
{
$user = $securityContext->getToken()->getUser();
$returnArray['user'] = $user;
$location = $user->getTblProfile()->getTblLocation();
if(!$cookieLocation || $cookieLocation != $hashids->encrypt($location->getId()))
{
$cookieLocation = $hashids->encrypt($location->getId());
//TODO: cookies are not being set figure out whys
$response = new Response();
$response->headers->setCookie(new Cookie("locationid", $cookieLocation, 365, '/', null, false, false));
$response->sendHeaders();
}
}
if (!$cookieLocation && !$cookieLocationSession)
{
return $returnArray;
}
if ($cookieLocationSession)
{
$cookieLocation = $cookieLocationSession;
}
if (!isset($location) || $cookieLocationSession)
{
$locationService = $this->get("wxexchange_location_service");
$locationId = $hashids->decrypt($cookieLocation);
if(count($locationId) >= 1)
$location = $locationService->getLocationById($locationId[0]);
}
if(isset($location))
return $this->redirect($this->generateUrl('wx_exchange_location', array('slug' => $location->getSlug(), 'locationid' => $cookieLocation)));
return $returnArray;
}
Do I have to return the response? If I do how do I keep processing (I have a redirect further down in my code)?
Edit: Interestingly enough if the same cookie is already set (via JQuery) running the code above deletes it, but it won't set it.
Edit: Action code posted.
The cookie object has httpOnly set to true by default, http://api.symfony.com/2.0/Symfony/Component/HttpFoundation/Cookie.html
This means that the browser should not make the cookie visible to client-side scripts. If you need to see the cookie in your scripts you can pass the 7th parameter as false when you create the cookie.
$response->headers->setCookie(new Cookie('foo', 'bar',time() + 60, '/', null, false, false));
If you just need to view the cookie for debugging purposes you can use Chrome Dev tools. They are available under the 'Resources' tab.
Edit : Try $response->sendHeaders();
Oops, Sorry everyone.
It's my error.
In my javascript I'm using a Jquery cookie plugin and when you set a new cookie you tell it the number of days before expiry:
$.cookie('locationid', value, { expires: 365, path: '/' });
Unfortunately I used a part of this syntax in my controller:
$cookie = new Cookie("locationid", $cookieLocation, 365, '/', null, false, false);
The problem is the third parameter is supposed to be a DateTime so while I thought I was telling it to expire in 365 days I probably created a cookie that expired almost instantly after creation.
Thanks for all the answers and time spent on your part, this is why I love SO.
As the title suggests,
Here's the code...
public function index(Request $request, Application $app)
{
$cookies = $request->cookies;
print_r($request->cookies);
if(!$cookies->has("recordsPerPage"))
{
$response = new Response();
$cookie = new Cookie("recordsPerPage", $app['defaultRecordsPerPage']);
$response->headers->setCookie($cookie);
}
print_r($request->cookies);exit; //prints nothing here !!
}
I also tried to set it in a $app->after() but failed. do you have any other ways to set cookies other than in controller.
Thank you.
Cookies are set with response and available on next request. So you have to return the response with this cookie, and if you want it to be available on the request, make it a redirect response so the browser will set cookie and issue next request with this newly created cookie:
$cookies = $request->cookies;
if(!$cookies->has("recordsPerPage"))
{
$cookie = new Cookie("recordsPerPage", $app['defaultRecordsPerPage']);
$response = Response::create('', 302, array("Location" => "http://127.0.0.1/whatever/"));
$response->headers->setCookie($cookie);
return $response;
}else{
return print_r($cookies, 1);
}
Other possibility is to set this cookie directly in the request ($request->cookies->set('recordsPerPage', $app['defaultRecordsPerPage']);) but you still have to return response with this cookie to set it in the browser.