3

In CakePHP 3.8 the segment of my controller looks like this:

// ...  

public function beforeFilter(Event $event)
{
    // ...

    $this->Cookie->configKey('guestCookie', [
        'expires' => '+1 days',
        'httpOnly' => true,
        'encryption' => false
    ]);

    if(!$this->Cookie->check('guestCookie')) {
        $guestID = Text::uuid();
        $this->Cookie->write('guestCookie', $guestID);
    }
    
    $this->guestCookie = $this->Cookie->read('guestCookie');

    // ...
}


public function error()
{
    // ...
    $this->Cookie->delete('guestCookie');
    // ...
}

// ...

How to write the same thing in CakePHP4 version? My problem relates to defining Cookies. Cookie settings are described here: https://book.cakephp.org/4/en/controllers/request-response.html#cookie-collections , but unfortunately this didn't help me at all.

I tried to solve the problem on this way:

    public function beforeFilter(EventInterface $event)
    {
//...
    
        if(!$this->cookies->has('guestCookie')) { 
            $cookie = (new Cookie('guestCookie'))->withValue(Text::uuid())->withExpiry(new \DateTime('+20 days'))->withPath('/')->withSecure(false)->withHttpOnly(true); 
            $this->cookies = new CookieCollection([$cookie]);
        }

        $this->guestCookie = $this->cookies->get('guestCookie')->getValue();      
 //...       
    } 

In my case $ this->cookies->has('guestCookie') is always 'false'. The cookie value is never stored in the browser. Please help.

user3661042
  • 167
  • 1
  • 13

1 Answers1

7

There's rarely a need to touch cookie collections, most of the times simple reading of cookie values from the request object, and writing cookies to the response object is all you need, so I would suggest that you stick with that until the actual need for collections comes up.

The docs could probably do a better job here at explaining when to use what.

Reading, writing, and deleting cookies

As shown in the linked docs, cookie values can be read via:

$this->request->getCookie($cookieName)

and written via:

$this->response = $this->response->withCookie($cookieObject)

It's important to reassign the response object (unless you directly return it from the controller), as it is immutable, meaning withCookie() will return a new response object instead of modifying the current one.

Deleting cookies can be done by responding with an expired cookie, using withExpiredCookie() instead of withCookie(), or obtaining the expired version of a cookie via $cookie->withExpired() and passing it to withCookie().

Configuring cookie defaults

If you wanted to, cookie defaults can be set via Cookie::setDefaults():

\Cake\Cookie\Cookie::setDefaults([
    'expires' => new DateTime('+1 days'),
    'http' => true,
]);

However this will apply application wide to all cookie instances being created after this point, so you'd likely use it rather rarely, and if you do, do so with care!

Porting from the Cookie component

With the new API, your code could be written like this, with $this->guestCookie holding the cookie value, either the newly generated one, or the one obtained from the cookie received by your application:

use Cake\Http\Cookie\Cookie;

// ...  

public function beforeFilter(Event $event)
{
    // ...
    $guestID = $this->request->getCookie('guestCookie');
    if(!$guestID) {
        $guestID = Text::uuid();
        
        $cookie = Cookie::create('guestCookie', $guestID, [
            'expires' => new DateTime('+1 days'),
            'http' => true,
        ]);
        $this->response = $this->response->withCookie($cookie);
    }
    
    $this->guestCookie = $guestID;
    // ...
}

public function error()
{
    // ...
    $cookie = new Cookie('guestCookie');
    $this->response = $this->response->withExpiredCookie($cookie);
    // ...
}

// ...

See also

ndm
  • 59,784
  • 9
  • 71
  • 110
  • 1
    I have tried this but with no success. In a function I have a debug($this->request->getCookie('event')); $cookie = (new Cookie('event')) ->withValue('true') ->withExpiry(new DateTime('+1 month')); $this->response = $this->response->withCookie($cookie); when I reload the page, the debug always return null. What am I missing? Thanks. – Harris Dec 18 '20 at 15:37
  • 1
    @Harris Remove the `debug()` call, or make it conditional depending on the read cookie value not being empty. If you echo data before the response is being sent, then no (cookie) headers can be sent anymore, and therefore no cookies can be set. With that code you should actually get corresponding warnings about headers not being able to be sent. – ndm Dec 18 '20 at 19:12