2

My question is very related to this question but not quite the same. It's a follow up question on my previous one if you have read it but it doesn't depend on it that much, so reading this question only should be enough. I have this method to save a 'touch' on a 'cabin' (I know it's odd). I (try to) do so be extracting information from a POST request, and test it using POSTMAN.


/**
 * @param Request $request
 * @throws \Doctrine\ORM\ORMException
 * @throws \Doctrine\ORM\OptimisticLockException
 * @throws \ErrorException
 * @return JsonResponse
 */
public function registerTouch(Request $request)
{
    $touchService = new TouchService($this->entityManager);

    $cabinet = $request->get('cabinet_id');

    /**
     * @var $touch Touch
     */
    $touch = new Touch(
        $request->get('time'),
        $request->get('toucher'),
        $request->get('cabinet_id'),
        $request->get('id')
    );

    if (empty($cabinet)) {
        return new JsonResponse(['error' => 'Touch not saved'], 200);
    } else {
        $touch->setCabinet($cabinet);
        $touchService->registerTouch($touch);
        return new JsonResponse(['success' => 'Touch saved'], 200);
    }

and the Touch class consists of the following:

<?php

namespace App\Entity;

use DateTime;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\TouchRepository")
 */
class Touch implements \JsonSerializable
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="datetime")
     */
    private $time;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $toucher;

    private $accountId;

    /**
     * @ORM\ManyToOne(targetEntity="Cabinet")
     */
    private $cabinet;

    /**
     * Touch constructor.
     * @param DateTime $time
     * @param string $toucher
     * @param Cabinet $cabinet
     * @param int $id
     */
    public function __construct(DateTime $time, string $toucher, Cabinet $cabinet = null, int $id = null)
    {
        $this->time = $time;
        $this->toucher = $toucher;
        $this->cabinet = $cabinet;
        $this->id = $id;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function setId(int $id): self
    {
        $this->id = $id;

        return $this;
    }

    public function getTime(): DateTime
    {
        return $this->time;
    }

    public function getToucher(): string
    {
        return $this->toucher;
    }

    public function getCabinet(): Cabinet
    {
        return $this->cabinet;
    }

    public function setCabinet(Cabinet $cabinet): self
    {
        $this->cabinet = $cabinet;
        $this->accountId = $cabinet->getId();
        return $this;
    }

    public function getAccountId(): int
    {
        return $this->accountId;
    }

    public function setAccountId(int $accountId): self
    {
        $this->accountId = $accountId;

        return $this;
    }

    public function jsonSerialize()
    {
        return get_object_vars($this);
    }
}

But when running this code I'm getting this error:

Argument 1 passed to App\Entity\Touch::__construct() must be an instance of DateTime, null given, called in /var/www/learningProject/src/Controller/APITouchController.php on line 89 (500 Internal Server Error)

I'm passing the data using POSTMAN:

[
    {
        "id": 666,
        "cabinet_id": 55,
        "time": {
            "date": "2018-06-18 11:51:22.000000",
            "timezone_type": 3,
            "timezone": "UTC"
        },
        "toucher": "person1",
    }
]

Which is the correct layout for a DateTime object so I'm not sure why this error is happening nor how to solve it. Any help would be appreciated!

Nicolai Fröhlich
  • 51,330
  • 11
  • 126
  • 130
  • Having the correct format does not make it a `DateTime` object, you'll have to convert it at some point. See [this question](https://stackoverflow.com/questions/42271865/json-array-to-php-datetime). – ehymel Jul 18 '19 at 15:06
  • @ehymel Wouldn't that result in me losing the format and possibly data like the timezone? How would this work? Because I need to store the 'touch' in a database. –  Jul 18 '19 at 15:08
  • @ehymel I also tried the above `time` with this `"time": "2018-06-18 11:51:22",` and I still get the same error. –  Jul 18 '19 at 15:12
  • Your `Touch::__construct()` function is requiring a php `DateTime` object. but when you call `$touch = new Touch(...)` you are effectively passing a string, regardless of whether the string is in some specific format. Before calling `new Touch(...)` you have to meet the type requirements of your own code. See my first comment. – ehymel Jul 18 '19 at 15:23
  • Try `dump($request)` and tell us what you have in. – ste Jul 18 '19 at 15:23

1 Answers1

2

The issue is you're passing a string to the constructor of Touch but you're type-hinting with DateTime $time so it expects a DateTime object.

To fix your issue convert the string to DateTime before passing it to the constructor.

/** @var $cabinet Cabinet|null */
$cabinet = $this->entityManager->getRepository(Cabinet::class)->findOneBy([
    'id' => $request->get('cabinet_id')
]);

if (null === $cabinet) {
    return new JsonResponse(['error' => 'Touch not saved'], 200);
}

/** @var $touch Touch */
$touch = new Touch(
    new \DateTime($request->get('time')),
    $request->get('toucher'),
    $cabinet,
    (int)$request->get('id')
);
$touchService->registerTouch($touch);

return new JsonResponse(['success' => 'Touch saved'], 200);

Tip: Consider using DateTimeImmutable instead of DateTime.

Nicolai Fröhlich
  • 51,330
  • 11
  • 126
  • 130
  • Thanks for your response! Two small question if you don't mind, 1) why DateTimeImmutable instead? 2) This indeed solved the problem but it still appears for the rest of the elements `Argument 2 passed to App\Entity\Touch::__construct() must be of the type integer, null given, called in /var/www/learningProject/src/Controller/APITouchController.php on line 89 (500 Internal Server Error)` Should I also convert that to integer then and similar stuff for the rest? and how would that work? Because `new \int` is not a thing iirc –  Jul 18 '19 at 15:29
  • Updated the answer. About the `DateTime` vs `DateTimeImmutable` recommendation. There are many articles on this topic around the interwebs (i.e. [this one](https://medium.com/@codebyjeff/whats-all-this-immutable-date-stuff-anyway-72d4130af8ce)). Just google it :) – Nicolai Fröhlich Jul 18 '19 at 15:33
  • Thanks once again! One last issue, I'm getting this when type hinting the Cabinet: `Method call uses 1 parameters, but method signature uses 0 parameters less...`. My Cabinet class is in this question: https://stackoverflow.com/questions/56612348/how-to-join-multiple-entities-on-a-foreign-id-in-symfony-4-using-a-query-builder Any ideas what could be done about it? –  Jul 18 '19 at 15:38
  • Oh, I see. you'll have to fetch your cabinet entity from database by given id then. Something like `$this->entityManager->getRepository(Cabinet::class)->findOneBy(['id' -> $request->get('cabinet_id')])`. Updated the answer. – Nicolai Fröhlich Jul 18 '19 at 15:41
  • A few days later, but would you by any chance have any idea why the JsonResponse is not returned no matter what? Everything is working and the touch is being registered and all fine, but POSTMAN giving this as a response: `{ "headers": {} }`. I have also tried returning a Response and other types but nothing is returned no matter what I try. –  Jul 23 '19 at 11:56
  • Please open a new question and add the relevant code parts and information (controller action, payload, etc. ). – Nicolai Fröhlich Jul 23 '19 at 12:45