0

In symfony3 I'm trying to save a cookie. But when the cookie is made the first time, it returns a 500 error.

This is the code to create the cookie:

protected function checkForCartId(Request $request)
{
    if(!empty($this->uniqueCartId)) {
        return $this->uniqueCartId;
    }

    $response = new Response();
    if(!$request->cookies->has('uniqueCartId')) {
        $this->uniqueCartId = uniqid();
        $response->headers->setCookie(new Cookie('uniqueCartId', $this->uniqueCartId));
        $response->send();

        return $this->uniqueCartId;
    }

    return $request->cookies->get('uniqueCartId');
}

When I remove the line $response->send() the error is gone, but the cookie is not saved.

Any ideas?

=========== EDIT =============

Log file (after getting error again)

[2018-04-22 14:47:26] request.INFO: Matched route "homepage". {"route":"homepage","route_parameters":{"_controller":"AppBundle\\Controller\\DefaultController::indexAction","_route":"homepage"},"request_uri":"http://127.0.0.1:8000/","method":"GET"} []
[2018-04-22 14:47:26] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} []
[2018-04-22 14:47:26] security.DEBUG: Calling getCredentials() on guard configurator. {"firewall_key":"main","authenticator":"AppBundle\\Security\\LoginFormAuthenticator"} []
[2018-04-22 14:47:26] security.INFO: Populated the TokenStorage with an anonymous Token. [] []
[2018-04-22 14:47:26] doctrine.DEBUG: SELECT t0.id AS id_1, t0.url AS url_2, t0.title AS title_3, t0.content AS content_4, t0.seo_title AS seo_title_5, t0.seo_description AS seo_description_6, t0.published AS published_7 FROM pages t0 WHERE t0.url = ? LIMIT 1 ["/"] []
[2018-04-22 14:47:26] doctrine.DEBUG: SELECT p0_.id AS id_0, p0_.slug AS slug_1, p0_.name AS name_2, p0_.is_published AS is_published_3, p0_.description AS description_4, p0_.price AS price_5, p0_.discount_price AS discount_price_6, p0_.page_title AS page_title_7, p0_.seo_title AS seo_title_8, p0_.seo_description AS seo_description_9 FROM product p0_ WHERE p0_.discount_price > 0 AND p0_.is_published = ? [1] []
[2018-04-22 14:47:26] doctrine.DEBUG: SELECT h0_.id AS id_0, h0_.title AS title_1, h0_.subtitle AS subtitle_2, h0_.image AS image_3, h0_.button_text AS button_text_4, h0_.button_link AS button_link_5, h0_.active AS active_6 FROM homepage_banner h0_ WHERE h0_.active = ? [1] []
[2018-04-22 14:47:26] doctrine.DEBUG: SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.slug AS slug_2, c0_.page_title AS page_title_3, c0_.description AS description_4, c0_.parent_id AS parent_id_5, c0_.created_by AS created_by_6 FROM category c0_ WHERE c0_.parent_id IS NULL [] []
[2018-04-22 14:47:26] doctrine.DEBUG: SELECT t0.id AS id_1, t0.name AS name_2, t0.slug AS slug_3, t0.page_title AS page_title_4, t0.description AS description_5, t0.parent_id AS parent_id_6, t0.created_by AS created_by_7 FROM category t0 WHERE t0.parent_id = ? [1] []
[2018-04-22 14:47:26] doctrine.DEBUG: SELECT t0.id AS id_1, t0.name AS name_2, t0.slug AS slug_3, t0.page_title AS page_title_4, t0.description AS description_5, t0.parent_id AS parent_id_6, t0.created_by AS created_by_7 FROM category t0 WHERE t0.parent_id = ? [2] []
[2018-04-22 14:47:26] doctrine.DEBUG: SELECT s0_.id AS id_0, s0_.cart_id AS cart_id_1, s0_.quantity AS quantity_2, s0_.price AS price_3, s0_.discount_price AS discount_price_4, s0_.ordered AS ordered_5, s0_.created_at AS created_at_6, s0_.product_id AS product_id_7, s0_.user_id AS user_id_8 FROM shopping_cart s0_ WHERE s0_.cart_id = ? ["5adc845e728a9"] []
[2018-04-22 14:47:26] request.CRITICAL: Uncaught PHP Exception UnexpectedValueException: "The Response content must be a string or object implementing __toString(), "boolean" given." at H:\Websites\CMS\vendor\symfony\symfony\src\Symfony\Component\HttpFoundation\Response.php line 406 {"exception":"[object] (UnexpectedValueException(code: 0): The Response content must be a string or object implementing __toString(), \"boolean\" given. at H:\\Websites\\CMS\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\HttpFoundation\\Response.php:406)"} []
[2018-04-22 14:47:27] request.INFO: Matched route "_wdt". {"route":"_wdt","route_parameters":{"_controller":"web_profiler.controller.profiler:toolbarAction","token":"49bddb","_route":"_wdt"},"request_uri":"http://127.0.0.1:8000/_wdt/49bddb","method":"GET"} []

================= EDIT =============== Controller action:

public function getCartItems(Request $request)
{
    $shoppingCartRepository = $this->em->getRepository(ShoppingCart::class);

    return $this->render('checkout/cart.html.twig', [
        'cartItems' => $shoppingCartRepository->findAllProductsInCart($this->getUser(), $this->checkForCartId($request))
    ]);
}

================= EDIT ===============

My New getCartItems funtion:

public function getCartItems(Request $request)
{
    $shoppingCartRepository = $this->em->getRepository(ShoppingCart::class);

    if(!$request->cookies->has('uniqueCartId')) {
        $this->checkForCartId($request);
    }

    $response = $this->render('checkout/cart.html.twig', [
        'cartItems' => $shoppingCartRepository->findAllProductsInCart($this->getUser(), $this->checkForCartId($request))
    ]);

    $response->headers->setCookie(new Cookie('uniqueCartId', $this->checkForCartId($request)));
    return $response;
}

============= SOLUTION =============

public function getCartItems(Request $request)
{
    $shoppingCartRepository = $this->em->getRepository(ShoppingCart::class);

    $response = new Response();
    if(!$request->cookies->has('uniqueCartId')) {
        $response->headers->setCookie(new Cookie('uniqueCartId', $this->checkForCartId($request)));
        $response->sendHeaders();
    }

    return $this->render('checkout/cart.html.twig', [
        'cartItems' => $shoppingCartRepository->findAllProductsInCart($this->getUser(), $this->checkForCartId($request))
    ], $response);
}
Refilon
  • 3,334
  • 1
  • 27
  • 51
  • 1
    There should be an additional info in your log file in var/log/, can you add it to the question? I suspect you will see "Cannot modify header information - headers already sent" somewhere. – Daniel Alexandrov Apr 22 '18 at 12:41
  • @DanielAlexandrov I inserted the log file. – Refilon Apr 22 '18 at 12:48
  • Try to find the error by putting `try...catch` block... – Yash Parekh Apr 22 '18 at 12:59
  • You sending a boolean as the response payload somewhere (the row before the last in your log). I can't deduct your application flow from this method only, but you shouldn't send the response from this function, because I guess later you are doing it again. As a general rule of thumb if you are expecting a result from a method/function, avoid side effects in it (like sending the request in your case), because it often leads to such errors. – Daniel Alexandrov Apr 22 '18 at 13:12
  • @DanielAlexandrov When I delete the $response->send() line, it does not throw the error. I just want to know how I can store a cookie without getting this error. I do not know how to set it on a different way. I can try it in javascript, but that's kind of dangerous? – Refilon Apr 22 '18 at 21:42
  • 1
    Where are you calling this method from? What happens after the check? If you can return the cookie value and set it on an existing request later, it will work fine. However, without seeing your controller code, it will be hard to give you more concrete information. – Daniel Alexandrov Apr 22 '18 at 21:53
  • Please update you question with the controller code. Since the Symfony Controller requires a response object to be returned, and implicitly calls the `send` method of the response object returned, you should avoid doing so in a service as the headers would have already been sent by it when Symfony attempts to do so. – Will B. Apr 23 '18 at 06:27
  • @fyrye updated with the controller code – Refilon Apr 24 '18 at 08:25
  • @DanielAlexandrov I'm calling this method from the controller (see updated post). And the `getCartItems()` I call directly in my twig template. – Refilon Apr 24 '18 at 08:25

1 Answers1

2

You need to set the cookie on the Response object, which in your case is the rendered twig template. After a bit of refactoring your code can look like:

protected function getCardId(Request $request)
{
    if(!$request->cookies->has('uniqueCartId')) {
        $this->uniqueCartId = uniqid();
        return ['is_new' => true, 'unique_id' => $this->uniqueCartId];
    } 

    $this->uniqueId = $request->cookies->get('uniqueCartId');
    return ['is_new' => false, 'unique_id' => $request->cookies->get('uniqueCartId')];
}

...

public function getCartItems(Request $request)
{
    $shoppingCartRepository = $this->em->getRepository(ShoppingCart::class);

    $cartId = $this->getCardId($request);

    $response = $this->render('checkout/cart.html.twig', [
        'cartItems' => $shoppingCartRepository->findAllProductsInCart($this->getUser(), $cartId['uniqueId'])
    ]);

    if($cartId['is_new']) {
        $response->headers->setCookie(new Cookie('uniqueCartId', $this->uniqueCartId));
    }
    return $response;
}

The important thing to note is that the response is returned only once, you can't return two responses for a single request.

Daniel Alexandrov
  • 1,299
  • 8
  • 13
  • Hi Daniel, thank you for your answer. I already tried this, but doing it this way, the cookie is not created. – Refilon Apr 24 '18 at 11:28
  • Are you sure? Did you removed the $response->send(); from your checkForCartId method? Can you try with the code from my example, including the changed checkForCartId/getCardId? – Daniel Alexandrov Apr 24 '18 at 13:08
  • I did use your code, and this is the dump from `app.request.cookies` `ParameterBag {#71 ▼ #parameters: array:1 [▼ "PHPSESSID" => "nljafbnff634j9vl69cvd07bm3" ] }` As you can see, the cookie is not set. The error is gone tho. – Refilon Apr 24 '18 at 13:46
  • Oh wait, I see the uniqueCartId in Google Chrome, but not in Google Chrome Incognito mode. Is there a way to set cookies in Incognito mode? – Refilon Apr 24 '18 at 13:50
  • @Refilon chrome basically sets the cache to a temp directory that is emptied when the browser is closed and disables cookies and Flash cookies. So you can not save cookie data using Incognito mode. See: [What does Chrome's “Incognito Mode” do exactly?](https://stackoverflow.com/questions/33620706/what-does-chromes-incognito-mode-do-exactly) and https://en.wikipedia.org/wiki/Privacy_mode – Will B. Apr 24 '18 at 15:24
  • @fyrye That's not completely true. Because when I use the `$response->send()` method, it does set the cookie in incognito mode. Incognito mode does use cookies, and sets them also in the temp folder. So when you close the browser, it will delete the cookies from the temp folder as well. – Refilon Apr 25 '18 at 06:37
  • @Refilon Please read the articles I referenced that explains what I mentioned. The "saving" of cookie data, as opposed to using of cookie data, making it act functionally the same as a session. – Will B. Apr 25 '18 at 12:41
  • @fyrye Yes that is supposed to be how it works. But it does not save the cookie to the temp folder when I try this method. When I use `$response->send()` it does save the cookie in the temp folder. So I am wondering why it does this? – Refilon Apr 25 '18 at 14:41
  • @Refilon Show us the code you're using now so that we can reproduce the issue. On my dev apache server, the cookie state for the above answer is working fine in incognito mode, tested by commenting out the `setCookie` call and refreshing the page multiple times in Symfony 3.4.8. – Will B. Apr 25 '18 at 15:51
  • @fyrye I just added the new function. This does not work and does not set a cookie. Not in normal google chrome nor in google chrome incognito – Refilon Apr 27 '18 at 14:02
  • @Refilon in regards to your solution, keep in mind your twig response will NOT be able to send it's own headers, as the headers will have already been sent by your cookie response. See: https://github.com/symfony/http-foundation/blob/v3.4.8/Response.php#L325 You can test this by changing your `render` to `redirectToRoute`, though it usually also adds a meta refresh tag as well. – Will B. Apr 27 '18 at 15:50
  • @fyrye Thank you. It does send the headers I guess? Because it is saving the cookie. But it all works now – Refilon Apr 29 '18 at 11:16