8

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.

skizzo
  • 393
  • 1
  • 3
  • 13
  • Did you install `dflydev/dflydev-fig-cookies` library with composer to use it? Because **FIG Cookies** is standalone library supporting PSR-7 standard to managing cookies, not part of Slim Framework. – Grzegorz Gajda Feb 07 '16 at 19:19

8 Answers8

8

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;
    }

}
jens.klose
  • 351
  • 2
  • 8
  • 3
    Good answer. However, when deleting cookie, use empty text as value for deleted cookie. Your own method `getCookieValue` will return null when trying to get a cookie deleted by `deleteCookie`. More consistent (for me at least). – Georgy Ivanov Apr 14 '17 at 11:15
7

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');
Ehsan Chavoshi
  • 681
  • 6
  • 10
6

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');
dAm2K
  • 9,923
  • 5
  • 44
  • 47
5

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.

Norman
  • 435
  • 6
  • 10
  • When I use the get function and get the cookies from the request, it shows as if the cookie always exists where it does not :( –  Nov 09 '17 at 12:20
  • How you find out that the cookie does not exist? – Norman Nov 10 '17 at 15:23
  • You do a $cookie->getValue() === null ? 'I don't exist': 'I exist'; https://stackoverflow.com/questions/47201908/cant-get-cookies-in-slim3-using-dflydev-library?noredirect=1#comment81353899_47201908 –  Nov 10 '17 at 16:08
1

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'
  ]);
});
LUXS
  • 343
  • 1
  • 2
  • 15
0

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

0

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;
});
Fabien Snauwaert
  • 4,995
  • 5
  • 52
  • 70
  • "It's broken and the documentation is either wrong or missing" - can you elaborate? Also I'm puzzled as to why your given working example appears to use dflydev/dflydev-fig-cookies anyway. – Jonathon Hill Oct 31 '20 at 16:52
  • @JonathonHill The verbose syntax and the fact [the expire function has been broken for over 3 years](https://github.com/dflydev/dflydev-fig-cookies/issues/23) are the reason behind it. I'd added that note after fixing my own issue and working some more with dflydev-fig-cookies (getting something to work ≠ from liking the solution.) While we're on the topic of Slim... I loved Slim 2 for its simplicity and being super practical, but hate Slim 4 for its verbosity, needless complexity and lack of working examples. If I were to do it again I'd migrate from Slim 2 to a different framework. – Fabien Snauwaert Nov 01 '20 at 17:41
  • Good to know. Is there an alternative library you'd recommend for dealing with cookies in a PSR-7 request/response? dflydev/dflydev-fig-cookies seems to be the one that is used most. – Jonathon Hill Nov 03 '20 at 14:58
0

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135