Search code examples
cakephpcookiescakephp-3.7

How to use a CookieCollection in multiple functions


I'm setting up a web page using cookies to determine if the user already logged in, using a cookie containing his id. Problem is : The cookie is either not written or the cookie collection is not updated.

I've tried reading the documentation, but it does not define the usage of CookieCollection.

Here's the function where i write my cookie :

function displayData(){
        $id = $this->getRequest()->getSession()->read('id');
        $cookies = CookieCollection::createFromServerRequest($this->getRequest());
        if(!$cookies->has('id')){
            $cookie = (new Cookie('id'))
                ->withValue($id)
                ->withExpiry(new DateTime('+999 year'))
                ->withPath('/')
                ->withDomain('break-first.eu')
                ->withSecure(true)
                ->withHttpOnly(true);
            $cookies = $cookies->add($cookie);
        }
        // Other stuff
    }

And where I try reading it :

function index(){
        $cookies = $this->getRequest()->getCookieCollection();
        dd($cookies);
    }

I expect having a cookie named "id", but I don't have it. Only CAKEPHP and pll_language are showing up.


Solution

  • First things first, CakePHP provides authentication functionality with cookie authentication, you may want to have a look at that instead of driving a custom solution.

    That being said, what you're doing there will create a cookie collection object, which however is just that, a lone object somewhere in space, it won't affect the state of your application, in order for that to happen you have to actually modify the response object.

    However what you're trying to do there doesn't require cookie collections in the first place, you can simply read and write cookies directly via the methods provided by the request and response objects, like:

    // will be `null` in case the cookie doesn't exist
    $cookie = $this->getRequest()->getCookie('id');
    
    // responses are immutable, they need to be reassinged
    this->setResponse(
        $this->getResponse()->withCookie(
            (new Cookie('id'))
                ->withValue($id)
                ->withExpiry(new DateTime('+999 year'))
                ->withPath('/')
                ->withDomain('break-first.eu')
                ->withSecure(true)
                ->withHttpOnly(true)
        )
    );
    

    And if you where to use a cookie collection for whatever reason, then you'd use withCookieCollection() to pass it into the response:

    $this->setResponse($this->getResponse()->withCookieCollection($cookies));
    

    If you run into strict typing errors, you could for example create a custom reponse class with an overridden Response::convertCookieToArray() method and cast the string to an integer there (make sure that PHP_INT_MAX covers your target date timestamp, 32-Bit incompatibility is why the fix that landed in CakePHP 4.x, probably won't come to 3.x), something like:

    src/Http/Response.php

    namespace App\Http;
    
    use Cake\Http\Cookie\CookieInterface;
    use Cake\Http\Response as CakeResponse;
    
    class Response extends CakeResponse
    {
        protected function convertCookieToArray(CookieInterface $cookie)
        {
            $data = parent::convertCookieToArray($cookie);
            $data['expire'] = (int)$data['expire'];
    
            return $data;
        }
    }
    

    You can pass that into the app in your webroot/index.php file, as the second argument of the $server->run() call:

    // ...
    $server->emit($server->run(null, new \App\Http\Response()));
    

    See also