1

I've got an entity called Logs that has a ManyToOne relation to an HourlyRates entity. Both Logs and HourlyRates have date properties. When adding a log with a specific date, an hourlyRate is assigned to it if the log-date fits within the rate's time range. I'm using the Doctrine Extensions Bundle, so the data in each entity can be soft-deleted.

What needs to be done:
After soft-deleting an HourlyRate the related Log has to be updated, so that the nearest existing past HourlyRate takes the place of the deleted one.

I tried to use preSoftDelete, postSoftDelete, preRemove and postRemove methods inside an HourlyRate entity listener. The code was being executed and the setters were working properly, but the database hasn't been updated in any of said cases. An "EntityNotFoundException" was being thrown everytime.

My second approach was to use the preRemove event along with setting the cascade option to "all" by using annotations in the HourlyRate class. As a result, soft-deleting an hourlyRate caused soft-deleting of the related log.



The Log entity:

class Log
{
    use SoftDeleteableEntity;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\HourlyRate", inversedBy="logs")
     * @ORM\JoinColumn(nullable=false)
     */
    private $hourlyRate;

    public function setHourlyRate(?HourlyRate $hourlyRate): self
    {
        $this->hourlyRate = $hourlyRate;

        return $this;
    }
}


The HourlyRate entity:

class HourlyRate
{
    use SoftDeleteableEntity;

    //other code
        /**
     * @ORM\OneToMany(targetEntity="App\Entity\Log", mappedBy="hourlyRate", cascade={"all"})
     */
    private $logs;
}


The HourlyRate entity listener:

class HourlyRateEntityListener
{

     public function preRemove(HourlyRate $hourlyRate, LifecycleEventArgs $args)
     {
         $entityManager = $args->getObjectManager();

         /** @var HourlyRateRepository $HRrepo */
         $HRrepo = $entityManager->getRepository(HourlyRate::class);

         foreach ($hourlyRate->getLogs() as $log)
         {
             $rate = $HRrepo->findHourlyRateByDate($log->getDate(), $log->getUser(), $hourlyRate);
             $log->setHourlyRate($rate);
         }

     }
}


The repository method:

class HourlyRateRepository extends ServiceEntityRepository
{
    public function findHourlyRateByDate(?\DateTimeInterface $datetime, User $user, ?HourlyRate $ignore = null): ?HourlyRate
    {
        $qb = $this->createQueryBuilder('hr')
            ->where('hr.date <= :hr_date')
            ->andWhere('hr.user = :user')
            ->orderBy('hr.date', 'DESC')
            ->setMaxResults(1)
            ->setParameters(array('hr_date' => $datetime, 'user' => $user));

        //ignore the "deleted" hourlyRate
        if($ignore){
            $qb->andWhere('hr.id != :ignored')
            ->setParameter('ignored', $ignore->getId());
        }

        return $qb->getQuery()
            ->getOneOrNullResult()
        ;
    }
}

Thank you in advance for any of your help.



EDIT:

Okay so after a whole week of trials and errors i finally managed to achieve the result I wanted.

I removed the One-To-Many relation between the hourlyRates and the logs from the entities, but left the $hourlyRate property inside the Log class. Then I got rid of the HourlyRateEntityListener and the preRemove() method from the LogEntityListener. Instead, I implemented the postLoad() method:

class LogEntityListener
{
    public function postLoad(Log $log, LifeCycleEventArgs $args)
    {
        $entityManager = $args->getObjectManager();
        $HRrepo = $entityManager->getRepository(HourlyRate::class);

        /** @var HourlyRateRepository $HRrepo */
        $rate = $HRrepo->findHourlyRateByDate($log->getDate(), $log->getUser());

        $log->setHourlyRate($rate);
    }
}

This approach allows me to set the proper hourlyRate for each log without involving the database. Idk if this solution is acceptable though.

ephemerev
  • 31
  • 2
  • Why dont you just query the past `hourlyRate` and call `setHourlyRate` on your `Log`? – Code Spirit Jul 31 '19 at 22:05
  • This is exactly what I'm doing inside the listener. – ephemerev Jul 31 '19 at 22:30
  • Then you should find out why the `EntityNotFoundException` exception is thrown. It means that you try to query an non existent entity. So your query returns no result. Is there actually a next hourlyRate in the database which matches your condition? Also if you are using [Gedmos soft deletable](http://atlantic18.github.io/DoctrineExtensions/doc/softdeleteable.html) you shouldnt have to manually check for soft deleted entities but just register the filter. – Code Spirit Jul 31 '19 at 22:35
  • The filter is already registered in the config. When I was testing every approach, I made sure that there is an existing hourlyRate that should fit the conditions. The exception is thrown because the deleted rate is not being overwritten with a new one. The program tries to get the old rate, but it can't since it was soft-deleted. If the data was updated properly, that problem wouldn't occur (IMO). – ephemerev Jul 31 '19 at 23:12
  • Have you `flush()`ed your entity manager after deleting the `houerlyRate`? From the docs: `Just like persist, invoking remove on an entity does NOT cause an immediate SQL DELETE to be issued on the database. The entity will be deleted on the next invocation of EntityManager#flush() that involves that entity.` – Code Spirit Aug 01 '19 at 07:34
  • Using `flush()` inside the listener's methods was causing some kind of an infinite loop. I have dealt with the problem in a different (and probably messier) way. Thank you very much for your replies! – ephemerev Aug 05 '19 at 13:06

0 Answers0