src/Entity/Tuk.php line 16
<?phpnamespace App\Entity;use App\Repository\TukRepository;use Doctrine\ORM\Mapping as ORM;/*** A tuk-tuk. This is the entity that carries a live GPS position: the driver's* phone pings its coordinates here, and the passenger's map draws them.** @ORM\Entity(repositoryClass=TukRepository::class)* @ORM\Table(name="tuk")* @ORM\HasLifecycleCallbacks()*/class Tuk{public const STATUS_OFFLINE = 'offline'; // driver not workingpublic const STATUS_AVAILABLE = 'available'; // free, shown to passengerspublic const STATUS_BUSY = 'busy'; // on a tourpublic const STATUSES = [self::STATUS_OFFLINE,self::STATUS_AVAILABLE,self::STATUS_BUSY,];/*** A position older than this is stale: the driver closed the app or lost* signal, so the tuk must not be offered to passengers.*/public const PING_TIMEOUT_SECONDS = 60;/*** @ORM\Id* @ORM\GeneratedValue* @ORM\Column(type="integer")*/private ?int $id = null;/*** Owning side of the one-to-one. A tuk with no driver is parked.* @ORM\OneToOne(targetEntity=Driver::class, inversedBy="tuk")* @ORM\JoinColumn(nullable=true, unique=true, onDelete="SET NULL")*/private ?Driver $driver = null;/*** Fleet code shown to passengers, e.g. "TNT-01".* @ORM\Column(type="string", length=20, unique=true)*/private string $code;/*** @ORM\Column(type="string", length=20, nullable=true)*/private ?string $plateNumber = null;/*** Hex paint colour of the vehicle, used for its map marker.* @ORM\Column(type="string", length=7, options={"default": "#E8A020"})*/private string $color = '#E8A020';/*** @ORM\Column(type="integer", options={"default": 3})*/private int $seats = 3;/*** @ORM\Column(type="string", length=500, nullable=true)*/private ?string $photo = null;/*** One of self::STATUSES.* @ORM\Column(type="string", length=10, options={"default": "offline"})*/private string $status = self::STATUS_OFFLINE;/*** @ORM\Column(type="decimal", precision=10, scale=7, nullable=true)*/private ?float $latitude = null;/*** @ORM\Column(type="decimal", precision=10, scale=7, nullable=true)*/private ?float $longitude = null;/*** Direction of travel in degrees (0 = north), so the marker can point.* @ORM\Column(type="smallint", nullable=true)*/private ?int $heading = null;/*** When the driver's phone last reported a position.* @ORM\Column(type="datetime_immutable", nullable=true)*/private ?\DateTimeImmutable $lastPingAt = null;/*** @ORM\Column(type="boolean", options={"default": true})*/private bool $isActive = true;/*** @ORM\Column(type="datetime_immutable")*/private \DateTimeImmutable $createdAt;/*** @ORM\Column(type="datetime_immutable", nullable=true)*/private ?\DateTimeImmutable $updatedAt = null;public function __construct(){$this->createdAt = new \DateTimeImmutable();}/*** @ORM\PreUpdate()*/public function onPreUpdate(): void{$this->updatedAt = new \DateTimeImmutable();}public function getId(): ?int{return $this->id;}public function getDriver(): ?Driver{return $this->driver;}public function setDriver(?Driver $driver): self{$this->driver = $driver;// Keep the inverse side in sync so Driver::getTuk() works on objects// that were changed in this request but not yet reloaded.if ($driver !== null && $driver->getTuk() !== $this) {$driver->setTuk($this);}return $this;}public function getCode(): string{return $this->code;}public function setCode(string $code): self{$this->code = $code;return $this;}public function getPlateNumber(): ?string{return $this->plateNumber;}public function setPlateNumber(?string $plateNumber): self{$this->plateNumber = $plateNumber;return $this;}public function getColor(): string{return $this->color;}public function setColor(string $color): self{$this->color = $color;return $this;}public function getSeats(): int{return $this->seats;}public function setSeats(int $seats): self{$this->seats = $seats;return $this;}public function getPhoto(): ?string{return $this->photo;}public function setPhoto(?string $photo): self{$this->photo = $photo;return $this;}public function getStatus(): string{return $this->status;}public function setStatus(string $status): self{if (!in_array($status, self::STATUSES, true)) {throw new \InvalidArgumentException(sprintf('Invalid status "%s". Allowed: %s',$status,implode(', ', self::STATUSES)));}$this->status = $status;return $this;}public function getLatitude(): ?float{return $this->latitude;}public function setLatitude(?float $latitude): self{$this->latitude = $latitude;return $this;}public function getLongitude(): ?float{return $this->longitude;}public function setLongitude(?float $longitude): self{$this->longitude = $longitude;return $this;}public function getHeading(): ?int{return $this->heading;}public function setHeading(?int $heading): self{$this->heading = $heading === null ? null : (($heading % 360) + 360) % 360;return $this;}public function getLastPingAt(): ?\DateTimeImmutable{return $this->lastPingAt;}public function setLastPingAt(?\DateTimeImmutable $lastPingAt): self{$this->lastPingAt = $lastPingAt;return $this;}/*** Record a GPS fix from the driver's phone.*/public function ping(float $latitude, float $longitude, ?int $heading = null): self{$this->latitude = $latitude;$this->longitude = $longitude;$this->lastPingAt = new \DateTimeImmutable();if ($heading !== null) {$this->setHeading($heading);}return $this;}public function isActive(): bool{return $this->isActive;}public function setIsActive(bool $isActive): self{$this->isActive = $isActive;return $this;}public function getCreatedAt(): \DateTimeImmutable{return $this->createdAt;}public function getUpdatedAt(): ?\DateTimeImmutable{return $this->updatedAt;}// ── Derived state ────────────────────────────────────────────────────/*** True when the last GPS ping is recent enough to trust.*/public function isOnline(): bool{if ($this->status === self::STATUS_OFFLINE || $this->lastPingAt === null) {return false;}$age = (new \DateTimeImmutable())->getTimestamp() - $this->lastPingAt->getTimestamp();return $age <= self::PING_TIMEOUT_SECONDS;}/*** Can a passenger book this tuk right now?*/public function isBookable(): bool{return $this->isActive&& $this->status === self::STATUS_AVAILABLE&& $this->latitude !== null&& $this->longitude !== null&& $this->driver !== null&& $this->isOnline();}public function __toString(): string{return $this->code ?? '';}}