-3

how we can order an array of objects by the returned value of a specified getter ?

For example, We have an array of objects. Each object has a 'getCreatedAt' function, that returns a DateTime object. We need to order that array to get the last element by date.

Sᴀᴍ Onᴇᴌᴀ
  • 8,218
  • 8
  • 36
  • 58
Eloy
  • 67
  • 1
  • 7

1 Answers1

1

The solution reached, a php trait for internal object management. Now we can compare an array of objects among them by simply implementing this trait in our entities. This is the code.

trait ObjectManagement 
{
    private function orderObjects(array $objects, string $getter, string $ordering): array
    {
        usort(
            $objects,
            [new ObjectAscDescOrderingByGetter($getter, $ordering), 'order']
        );

        return $objects;
    }

}


class ObjectAscDescOrderingByGetter
{

    private $getter;
    private $ordering;

    public function __construct(string $getter, string $ordering)
    {
        if (!in_array($ordering, ['ASC', 'DESC'])) {
            throw new RuntimeException('Ordering type must be ASC or DESC.');
        }

        $this->getter = $getter;
        $this->ordering = $ordering;
    }

    public function order($objectA, $objectB)
    {
        $getter = $this->getter;

        $comparisonValueA = $objectA->$getter();
        $comparisonValueB = $objectB->$getter();

        if ($comparisonValueA === $comparisonValueB) {
            return 0;
        }

        if ($this->ordering == 'DESC') {
            return ($comparisonValueA > $comparisonValueB) ? -1 : 1;
        } else {
            return ($comparisonValueA > $comparisonValueB) ? 1 : -1;
        }
    }
}

We know that traits are not the best way in some cases, but in this case we found it very helpful since this ordering function are code that can be statically implemented in other classes. This is now our use case in entities:

class Car {

  private $inputDate;

  public function getInputDate() : DateTime 
  {
     return $this->inputDate;
  }
}

class CarPark {
   use ObjectManagement;
   private $cars;

   public function getLastCar() : Car {
     $result = $this->orderObjects($this->cars, 'getInputDate', 'DESC');
     return current($result);
   }
}

We leave here the tests also ....

class ObjectManagementTestCase extends TestCase
{

    use ObjectManagement;

    /**
     * @dataProvider getOrderingCases
     * @param array $datesToOrder
     * @param array $datesExpectedOrder
     * @param string $ordering
     */
    public function testOrdering(array $datesToOrder, array $datesExpectedOrder, string $ordering)
    {
        $objectsToOrder = [];
        foreach ($datesToOrder as $dateToOrder) {
            $objectsToOrder[] = $this->getOrderableMock($dateToOrder);
        }
        $objectsExpectedOrder = [];
        foreach ($datesExpectedOrder as $expectedDateOrder) {
            $objectsExpectedOrder[] = $this->getOrderableMock($expectedDateOrder);
        }

        $result = $this->orderObjects($objectsToOrder, 'getCreatedAt', $ordering);
        $this->assertEquals($objectsExpectedOrder, $result);
    }

    public function getOrderingCases()
    {
        return [
            'Must order this in descendant manner.' => [
                ['2016-01-01', '2017-01-01', '2017-01-03', '2017-01-05'],
                ['2017-01-05', '2017-01-03', '2017-01-01', '2016-01-01'],
                'DESC'
            ],
            'Must order this in ascendant manner.' => [
                ['2016-01-01', '2017-01-03', '2017-01-01', '2017-01-05'],
                ['2016-01-01', '2017-01-01', '2017-01-03', '2017-01-05'],
                'ASC'
            ],
        ];
    }

    private function getOrderableMock(string $date)
    {
        return new OrderableTestObject($date);
    }

    public function testThatInputArrayIsNotModified()
    {
        $objects = [
            $this->getOrderableMock('2016-01-01'),
            $this->getOrderableMock('2017-01-03'),
        ];

        $this->orderObjects($objects, 'getCreatedAt', 'DESC');

        $this->assertEquals(
            [
                $this->getOrderableMock('2016-01-01'),
                $this->getOrderableMock('2017-01-03'),
            ],
            $objects
        );

    }

    public function testExceptionOnNotRecognizedOrder()
    {
        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage('Ordering type must be ASC or DESC.');
        $this->orderObjects([], 'createdAt', '');
    }
}


class OrderableTestObject
{
    private $createdAt;

    public function __construct(string $date)
    {
        $this->createdAt = new DateTime($date);
    }

    public function getCreatedAt(): DateTime
    {
        return $this->createdAt;
    }
}
Eloy
  • 67
  • 1
  • 7