src/Entity/Measurement.php line 26

Open in your IDE?
  1. <?php
  2. /**
  3.  * @license Silk Software House Sp. Z O.O.
  4.  */
  5. declare(strict_types=1);
  6. namespace App\Entity;
  7. use App\Entity\Traits\IdTrait;
  8. use App\Entity\Traits\TimePreUpdateTrait;
  9. use App\Entity\Traits\TimeTrait;
  10. use Doctrine\Common\Collections\ArrayCollection;
  11. use Doctrine\Common\Collections\Collection;
  12. use Doctrine\ORM\Mapping as ORM;
  13. use DateTime;
  14. use Exception;
  15. /**
  16.  * Class Measurement.
  17.  *
  18.  * @ORM\Table("measurements")
  19.  * @ORM\Entity(repositoryClass="App\Repository\MeasurementRepository")
  20.  * @ORM\HasLifecycleCallbacks()
  21.  */
  22. class Measurement
  23. {
  24.     use IdTrait;
  25.     use TimeTrait;
  26.     use TimePreUpdateTrait;
  27.     public const END_REASON_NORMAL_STOP 'NORMAL_STOP';
  28.     public const END_REASON_UNCLEAN_STOP 'UNCLEAN_STOP';
  29.     /**
  30.      * Device.
  31.      *
  32.      * @var Device|null
  33.      *
  34.      * @ORM\ManyToOne(targetEntity="App\Entity\Device", cascade={"persist"}, inversedBy="measurements")
  35.      * @ORM\JoinColumn(name="device_id", nullable=false)
  36.      */
  37.     private ?Device $device null;
  38.     /**
  39.      * Customer.
  40.      *
  41.      * @var Customer|null
  42.      *
  43.      * @ORM\ManyToOne(targetEntity="App\Entity\Customer")
  44.      * @ORM\JoinColumn(name="customer_id", nullable=true)
  45.      */
  46.     private ?Customer $customer null;
  47.     /**
  48.      * @ORM\OneToMany(targetEntity="App\Entity\Channel", mappedBy="measurement")
  49.      * @ORM\OrderBy({"number": "ASC"})
  50.      */
  51.     private $channels;
  52.     /**
  53.      * @ORM\Column(type="decimal", precision=5, scale=1)
  54.      */
  55.     private ?string $step null;
  56.     /**
  57.      * When was this measurement marked as finished (a device sent STOP flag).
  58.      *
  59.      * @var DateTime|null
  60.      *
  61.      * @ORM\Column(name="started_at", nullable=true, type="datetime", options={
  62.      *     "comment": "When was this measurement marked as finished (device sent START flag)."
  63.      * })
  64.      */
  65.     private ?DateTime $startedAt null;
  66.     /**
  67.      * When was this measurement marked as finished (device sent STOP flag).
  68.      *
  69.      * @var DateTime|null
  70.      *
  71.      * @ORM\Column(name="finished_at", nullable=true, type="datetime", options={
  72.      *     "comment": "When was this measurement marked as finished (device sent STOP flag)."
  73.      * })
  74.      */
  75.     private ?DateTime $finishedAt null;
  76.     /**
  77.      * Source period from the device.
  78.      *
  79.      * @var int|null
  80.      *
  81.      * @ORM\Column(name="source_period", type="integer", nullable=true, options={"comment": "Source period from device."})
  82.      */
  83.     private ?int $sourcePeriod null;
  84.     /**
  85.      * The last sample timestamp received for this measurement.
  86.      *
  87.      * @var DateTime|null
  88.      *
  89.      * @ORM\Column(name="last_sample_at", nullable=true, type="datetime", options={"comment": "Last sample timestamp received for this measurement."})
  90.      */
  91.     private ?DateTime $lastSampleAt null;
  92.     /**
  93.      * End reason.
  94.      *
  95.      * @var string|null
  96.      *
  97.      * @ORM\Column(name="end_reason", type="string", length=32, nullable=true, options={"comment": "End reason: NORMAL_STOP or UNCLEAN_STOP."})
  98.      */
  99.     private ?string $endReason null;
  100.     /**
  101.      * Name assigned to this measurement.
  102.      *
  103.      * @var string|null
  104.      *
  105.      * @ORM\Column(name="name", type="text", nullable=true, options={"comment": "Name assigned to this measurement."})
  106.      */
  107.     private ?string $name null;
  108.     /**
  109.      * @ORM\OneToMany(targetEntity="App\Entity\ProjectInformation", mappedBy="measurement", cascade={"persist", "remove"})
  110.      */
  111.     private $projectInformation;
  112.     /**
  113.      * Measurement settings.
  114.      *
  115.      * @var Collection
  116.      *
  117.      * @ORM\OneToMany(targetEntity="App\Entity\MeasurementSetting", mappedBy="measurement", cascade={"persist"})
  118.      */
  119.     private $settings;
  120.     /**
  121.      * @var ?string
  122.      */
  123.     private ?string $cachedNameWithRelativeNumberPerDevice null;
  124.     /**
  125.      * Measurement constructor.
  126.      */
  127.     public function __construct()
  128.     {
  129.         $this->channels = new ArrayCollection();
  130.         $this->projectInformation = new ArrayCollection();
  131.         $this->settings = new ArrayCollection();
  132.     }
  133.     /**
  134.      * Get device.
  135.      *
  136.      * @return Device
  137.      */
  138.     public function getDevice(): Device
  139.     {
  140.         if (!isset($this->device)) {
  141.             throw new \LogicException('The Device is not set, not expected.');
  142.         }
  143.         return $this->device;
  144.     }
  145.     /**
  146.      * Set device.
  147.      *
  148.      * @param Device $device
  149.      *
  150.      * @return Measurement
  151.      */
  152.     public function setDevice(Device $device): self
  153.     {
  154.         $this->device $device;
  155.         return $this;
  156.     }
  157.     /**
  158.      * Get customer.
  159.      *
  160.      * @return Customer|null
  161.      */
  162.     public function getCustomer(): ?Customer
  163.     {
  164.         return $this->customer;
  165.     }
  166.     /**
  167.      * Set customer.
  168.      *
  169.      * @param Customer|null $customer
  170.      *
  171.      * @return Measurement
  172.      */
  173.     public function setCustomer(?Customer $customer): self
  174.     {
  175.         $this->customer $customer;
  176.         return $this;
  177.     }
  178.     /**
  179.      * @return Collection|Channel[]
  180.      */
  181.     public function getChannels(): Collection
  182.     {
  183.         return $this->channels;
  184.     }
  185.     /**
  186.      * @param Channel $channel
  187.      */
  188.     public function addChannel(Channel $channel): void
  189.     {
  190.         if (!$this->channels->contains($channel)) {
  191.             $this->channels->add($channel);
  192.         }
  193.     }
  194.     /**
  195.      * @return string|null
  196.      */
  197.     public function getStep(): ?string
  198.     {
  199.         return $this->step;
  200.     }
  201.     /**
  202.      * @param string $step
  203.      *
  204.      * @return $this
  205.      */
  206.     public function setStep(string $step): self
  207.     {
  208.         $this->step $step;
  209.         return $this;
  210.     }
  211.     /**
  212.      * Get is finished.
  213.      *
  214.      * @return bool
  215.      */
  216.     public function getIsFinished(): bool
  217.     {
  218.         return null !== $this->finishedAt;
  219.     }
  220.     /**
  221.      * Get name.
  222.      *
  223.      * @return string|null
  224.      */
  225.     public function getName(): ?string
  226.     {
  227.         return $this->name;
  228.     }
  229.     /**
  230.      * Get name with fallback.
  231.      *
  232.      * @return string|null
  233.      */
  234.     public function getNameWithFallback(): ?string
  235.     {
  236.         return $this->name ?? ('{{Measurement}} ' $this->getId());
  237.     }
  238.     /**
  239.      * Get a name with heavy fallback.
  240.      *
  241.      * This function may be slow.
  242.      *
  243.      * @return string|null
  244.      */
  245.     public function getNameWithHeavyFallback(): ?string
  246.     {
  247.         if (null === $this->name) {
  248.             return $this->getNameWithRelativeNumberPerDevice();
  249.         }
  250.         return $this->name;
  251.     }
  252.     /**
  253.      * Get a name with a relative number per device.
  254.      *
  255.      * This function may be slow.
  256.      *
  257.      * @return string|null
  258.      */
  259.     public function getNameWithRelativeNumberPerDevice(): ?string
  260.     {
  261.         if (!$this->cachedNameWithRelativeNumberPerDevice) {
  262.             $measurementIds $this->getDevice()->getSortedMeasurmentsIdsArray();
  263.             if (empty($measurementIds)) {
  264.                 /* This scenario should never happen, but... */
  265.                 return '{{Measurement}}';
  266.             }
  267.             $relativePosition array_flip($measurementIds)[$this->getId()];
  268.             ++$relativePosition;
  269.             $this->cachedNameWithRelativeNumberPerDevice '{{Measurement}} ' $relativePosition;
  270.         }
  271.         return $this->cachedNameWithRelativeNumberPerDevice;
  272.     }
  273.     public function getNameWithHeavyFallbackAndDates(): ?string
  274.     {
  275.         $startAtString $this->getStartedAt() ? $this->getStartedAt()->format('Y-m-d H:i:s') : '?';
  276.         $endsAtString $this->getFinishedAt() ? $this->getFinishedAt()->format('Y-m-d H:i:s') : 'now';
  277.         return $this->getNameWithHeavyFallback().' ('.$startAtString.' - '.$endsAtString.')';
  278.     }
  279.     /**
  280.      * Set name.
  281.      *
  282.      * @param string|null $name
  283.      *
  284.      * @return Measurement
  285.      */
  286.     public function setName(?string $name): self
  287.     {
  288.         $this->name $name;
  289.         return $this;
  290.     }
  291.     /**
  292.      * Get started at.
  293.      *
  294.      * @return DateTime|null
  295.      */
  296.     public function getStartedAt(): ?DateTime
  297.     {
  298.         return $this->startedAt;
  299.     }
  300.     /**
  301.      * Set started at.
  302.      *
  303.      * @param DateTime|null $startedAt
  304.      *
  305.      * @return Measurement
  306.      */
  307.     public function setStartedAt(?DateTime $startedAt): self
  308.     {
  309.         $this->startedAt $startedAt;
  310.         return $this;
  311.     }
  312.     /**
  313.      * Get started at string.
  314.      *
  315.      * @param string|null $default
  316.      *
  317.      * @return string|null
  318.      */
  319.     public function getStartedAtString(?string $default null): ?string
  320.     {
  321.         return $this->startedAt $this->startedAt->format('Y-m-d H:i:s') : $default;
  322.     }
  323.     /**
  324.      * Get finished at.
  325.      *
  326.      * @return DateTime|null
  327.      */
  328.     public function getFinishedAt(): ?DateTime
  329.     {
  330.         return $this->finishedAt;
  331.     }
  332.     /**
  333.      * Set finished at.
  334.      *
  335.      * @param DateTime|null $finishedAt
  336.      *
  337.      * @return Measurement
  338.      */
  339.     public function setFinishedAt(?DateTime $finishedAt): self
  340.     {
  341.         $this->finishedAt $finishedAt;
  342.         return $this;
  343.     }
  344.     /**
  345.      * @return int|null
  346.      */
  347.     public function getSourcePeriod(): ?int
  348.     {
  349.         return $this->sourcePeriod;
  350.     }
  351.     /**
  352.      * @param int|null $sourcePeriod
  353.      *
  354.      * @return Measurement
  355.      */
  356.     public function setSourcePeriod(?int $sourcePeriod): self
  357.     {
  358.         $this->sourcePeriod $sourcePeriod;
  359.         return $this;
  360.     }
  361.     /**
  362.      * @return DateTime|null
  363.      */
  364.     public function getLastSampleAt(): ?DateTime
  365.     {
  366.         return $this->lastSampleAt;
  367.     }
  368.     /**
  369.      * @param DateTime|null $lastSampleAt
  370.      *
  371.      * @return Measurement
  372.      */
  373.     public function setLastSampleAt(?DateTime $lastSampleAt): self
  374.     {
  375.         $this->lastSampleAt $lastSampleAt;
  376.         return $this;
  377.     }
  378.     /**
  379.      * Returns the latest timestamp actually received from the device.
  380.      *
  381.      * Falls back to probe data for older/imported measurements where lastSampleAt
  382.      * was not populated yet.
  383.      */
  384.     public function getLastReceivedAt(): ?DateTime
  385.     {
  386.         if ($this->lastSampleAt) {
  387.             return $this->lastSampleAt;
  388.         }
  389.         $lastProbeAt null;
  390.         foreach ($this->channels as $channel) {
  391.             $probe $channel->getProbes()->last();
  392.             if (!$probe) {
  393.                 continue;
  394.             }
  395.             if (null === $lastProbeAt || $probe->getTime() > $lastProbeAt) {
  396.                 $lastProbeAt $probe->getTime();
  397.             }
  398.         }
  399.         return $lastProbeAt ?: $this->finishedAt;
  400.     }
  401.     /**
  402.      * @return string|null
  403.      */
  404.     public function getEndReason(): ?string
  405.     {
  406.         return $this->endReason;
  407.     }
  408.     /**
  409.      * @param string|null $endReason
  410.      *
  411.      * @return Measurement
  412.      */
  413.     public function setEndReason(?string $endReason): self
  414.     {
  415.         $this->endReason $endReason;
  416.         return $this;
  417.     }
  418.     /**
  419.      * Get finished at string.
  420.      *
  421.      * @param string|null $default
  422.      *
  423.      * @return string|null
  424.      */
  425.     public function getFinishedAtString(?string $default null): ?string
  426.     {
  427.         return $this->finishedAt $this->finishedAt->format('Y-m-d H:i:s') : $default;
  428.     }
  429.     /**
  430.      * @return Collection|ProjectInformation[]
  431.      */
  432.     public function getProjectInformation(): Collection
  433.     {
  434.         return $this->projectInformation;
  435.     }
  436.     /**
  437.      * Add project information.
  438.      *
  439.      * @param ProjectInformation $projectInformation
  440.      *
  441.      * @return Measurement
  442.      */
  443.     public function addProjectInformation(ProjectInformation $projectInformation): self
  444.     {
  445.         if (!$this->projectInformation->contains($projectInformation)) {
  446.             $this->projectInformation->add($projectInformation->setMeasurement($this));
  447.         }
  448.         return $this;
  449.     }
  450.     /**
  451.      * Remove project information.
  452.      *
  453.      * @param ProjectInformation $projectInformation
  454.      *
  455.      * @return Measurement
  456.      */
  457.     public function removeProjectInformation(ProjectInformation $projectInformation): self
  458.     {
  459.         if ($this->projectInformation->contains($projectInformation)) {
  460.             $this->projectInformation->removeElement($projectInformation);
  461.             // set the owning side to null (unless already changed)
  462.             if ($projectInformation->getMeasurement()->getId() === $this->getId()) {
  463.                 $projectInformation->setMeasurement(null);
  464.             }
  465.         }
  466.         return $this;
  467.     }
  468.     /**
  469.      * @return false|int
  470.      *
  471.      * @throws Exception
  472.      */
  473.     public function getActiveDays()
  474.     {
  475.         $until $this->getFinishedAt() ?? new DateTime();
  476.         $diff date_diff($this->getStartedAt() ?? $this->createdAt$until);
  477.         return $diff->days;
  478.     }
  479.     /**
  480.      * Get a standard recipe.
  481.      *
  482.      * @return Recipe|null
  483.      */
  484.     public function getStandardRecipe(): ?Recipe
  485.     {
  486.         return $this->getSetting() ? $this->getSetting()->getStandardRecipe() : null;
  487.     }
  488.     /**
  489.      * Set a standard recipe.
  490.      *
  491.      * @param Recipe|null $standardRecipe
  492.      *
  493.      * @return Measurement
  494.      */
  495.     public function setStandardRecipe(?Recipe $standardRecipe): self
  496.     {
  497.         $this->getSetting()->setStandardRecipe($standardRecipe);
  498.         return $this;
  499.     }
  500.     /**
  501.      * Get setting.
  502.      *
  503.      * @return MeasurementSetting|null
  504.      */
  505.     public function getSetting(): ?MeasurementSetting
  506.     {
  507.         return $this->settings->first() ?: null;
  508.     }
  509.     /**
  510.      * To string.
  511.      *
  512.      * @return string
  513.      */
  514.     public function __toString(): string
  515.     {
  516.         $startAtString $this->getStartedAt() ? $this->getStartedAt()->format('Y-m-d H:i:s') : '?';
  517.         $endsAtString $this->getFinishedAt() ? $this->getFinishedAt()->format('Y-m-d H:i:s') : 'now';
  518.         return $this->getNameWithFallback().' ('.$startAtString.' - '.$endsAtString.')';
  519.     }
  520.     /**
  521.      * @return string
  522.      */
  523.     public function getChannelDate(): string
  524.     {
  525.         $startAtString $this->getStartedAt() ? $this->getStartedAt()->format('Y-m-d H:i:s') : '?';
  526.         $endsAtString $this->getFinishedAt() ? $this->getFinishedAt()->format('Y-m-d H:i:s') : 'now';
  527.         return ' ('.$startAtString.' - '.$endsAtString.')';
  528.     }
  529.     /**
  530.      * Get nth channel.
  531.      *
  532.      * @param int $index
  533.      *
  534.      * @return ChannelSetting|null
  535.      */
  536.     public function getNthChannelSetting(int $index): ?ChannelSetting
  537.     {
  538.         if (!$this->getSetting()) {
  539.             return null;
  540.         }
  541.         return $this->getSetting()->getChannelSettings()[$index] ?? null;
  542.     }
  543.     /**
  544.      * Has channel limit exceeded.
  545.      *
  546.      * @return bool
  547.      */
  548.     public function hasChannelsLimitExceeded(): bool
  549.     {
  550.         return null !== $this->getChannelsLeft() && $this->getChannelsLeft() <= 0;
  551.     }
  552.     /**
  553.      * Channels left.
  554.      *
  555.      * @return int|null
  556.      */
  557.     public function getChannelsLeft(): ?int
  558.     {
  559.         if (!$this->device || !$this->device->getChannelsLimit()) {
  560.             return null;
  561.         }
  562.         if (!$this->settings->count()) {
  563.             return null;
  564.         }
  565.         return $this->device->getChannelsLimit() - $this->settings->first()->getChannelSettings()->count();
  566.     }
  567.     /**
  568.      * Add setting.
  569.      *
  570.      * @param MeasurementSetting $setting
  571.      *
  572.      * @return Measurement
  573.      */
  574.     public function setSetting(MeasurementSetting $setting): self
  575.     {
  576.         if (=== $this->settings->count()) {
  577.             $this->settings->add($setting);
  578.         }
  579.         return $this;
  580.     }
  581.     /**
  582.      * Get line type group.
  583.      *
  584.      * @return LineTypeGroup|null
  585.      */
  586.     public function getLineTypeGroup(): ?LineTypeGroup
  587.     {
  588.         return $this->getSetting() ? $this->getSetting()->getLineTypeGroup() : null;
  589.     }
  590.     /**
  591.      * Set a line type group.
  592.      *
  593.      * @param LineTypeGroup|null $lineTypeGroup
  594.      *
  595.      * @return Measurement
  596.      */
  597.     public function setLineTypeGroup(?LineTypeGroup $lineTypeGroup): self
  598.     {
  599.         if ($this->getSetting()) {
  600.             $this->getSetting()->setLineTypeGroup($lineTypeGroup);
  601.         }
  602.         return $this;
  603.     }
  604. }