0

I have re write my code from old php 7.4 scripts. And i see my code be more slower than old version My old models export data from method toArray And his representation was like that

public function toArray()
{
   return [
      "id" => $this->id;
      "name" => $this->name;
      //And many other values
   ]
}

But now, with PHP 8, i have use reflection object to serialize my object. That take something like :

class MyModel extends Model
{
   #[AttributeExport]
   private int $id;

   #[AttributeExport]
   private string $name;
}

//In Model

abstract Model implement JsonSerialize
{
   public function jsonSerialize(): array
    {
        $data = [];

        $reflection = new \ReflectionClass(get_called_class());
        $elements = array_merge($reflection->getProperties(), $reflection->getMethods());

        foreach ($elements as $element) {
            $exportableAttributes = $element->getAttributes(
                AttributeExport::class,
                \ReflectionAttribute::IS_INSTANCEOF
            );
            foreach ($exportableAttributes as $exportableAttribute) {
                /** @var AttributeExport $exportableInstance */
                $exportableInstance = $exportableAttribute->newInstance();

                $exportName = $exportableInstance->hasName() ? $exportableInstance->getName() : $element->getName();
                $method = $element->getName();
                if ($element instanceof \ReflectionProperty) {
                    $method = "get" . ucfirst($method);
                }

                $data[$exportName] = $this->{$method}();
            }
        }
        return $data;
    }
}

I may be mistakenly thinking that the problem may come from there. But do you think that on a large volume of data, this strategy can have an impact?

I used more abstract class compared to the old version to avoid code duplication. Dont know if that can impact too

Rocket
  • 93
  • 2
  • 10
  • Can you please extend your question by providing the implementation of `jsonSerialize`? Also the test setup how you measured the performance could help. – thehennyy Aug 29 '23 at 10:05
  • I have edit my post with full jsonSerialize implementation. For measured my performances i use postman and show my headers. I run a $_GLOBAL timer and on my reponse object i check the start timer versus current time and i send it on my header in debug mode to see how many milliseconds the request taken – Rocket Aug 29 '23 at 10:30

1 Answers1

0

There are two things to consider, you changed from direct property access to a reflection based getter method approach:

  • Calling methods is usually slower then direct access
  • Reflection operations are expensive but can usually be cached quite well

Have a look at this modification, i added a simple cache, now the runtime of the reflection approach is much faster, i think the performace hit should be acceptable in the most settings:

string(29) "reflection: 0.036892890930176"
string(33) "direct acccess: 0.015763998031616"
string(33) "direct methods: 0.018218040466309"
<?php
#[Attribute]
class AttributeExport
{

    public function __construct()
    {
    }

    public function hasName()
    {
        return false;
    }
}

abstract class Model implements JsonSerializable
{
    private static $cache = [];

    public function jsonSerialize():array
    {
        $data = [];

        if (!isset(self::$cache[self::class]))
        {
            self::$cache[self::class ] = [];
            $reflection = new \ReflectionClass(get_called_class());
            $elements = array_merge($reflection->getProperties() , $reflection->getMethods());

            foreach ($elements as $element)
            {
                $exportableAttributes = $element->getAttributes(AttributeExport::class , \ReflectionAttribute::IS_INSTANCEOF);
                foreach ($exportableAttributes as $exportableAttribute)
                {
                    /** @var AttributeExport $exportableInstance */
                    $exportableInstance = $exportableAttribute->newInstance();

                    $exportName = $exportableInstance->hasName() ? $exportableInstance->getName() : $element->getName();
                    $method = $element->getName();
                    if ($element instanceof \ReflectionProperty)
                    {
                        $method = "get" . ucfirst($method);
                    }

                    self::$cache[self::class][$exportName] = $method;
                }
            }
        }

        foreach (self::$cache[self::class] as $exportName => $method)
        {
            $data[$exportName] = $this->{$method}();
        }
        return $data;
    }

}

class MyModel extends Model
{

    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    #[AttributeExport]
    private int $id;

    #[AttributeExport]
    private string $name;

    public function getId()
    {
        return $this->id;
    }
    public function getName()
    {
        return $this->name;
    }

    public function toArray()
    {
        return [
            "id" => $this->id,
            "name" => $this->name
            //And many other values
        ];
    }

    public function toArrayMethods()
    {
        return [
            "id" => $this->getId(),
            "name" => $this->getName()
            //And many other values
        ];
    }

}

$z = new MyModel(1, "a");
$zr = $z->jsonSerialize();
$zr2 = $z->toArray();

$time1 = microtime(true);
for ($a = 0;$a < 100000;$a++)
{
    $x = new MyModel(1, "a");
    $r = $x->jsonSerialize();
}
$timea = microtime(true) - $time1;

$time2 = microtime(true);
for ($b = 1;$b < 100000;$b++)
{
    $x2 = new MyModel(1, "a");
    $r2 = $x2->toArray();
}
$timeb = microtime(true) - $time2;

$time3 = microtime(true);
for ($b = 1;$b < 100000;$b++)
{
    $x2 = new MyModel(1, "a");
    $r2 = $x2->toArrayMethods();
}
$timec = microtime(true) - $time3;

var_dump("reflection: " . $timea);
var_dump("direct acccess: " . $timeb);
var_dump("direct methods: " . $timec);

Using reflection to access the properties without getter could also be measured.

thehennyy
  • 4,020
  • 1
  • 22
  • 31
  • Thanks, it's a little bit better (i save 2seconds on my big test exemple), not perfect but a start of solution. I will continue to check if i can do better with direct access to property. – Rocket Aug 29 '23 at 14:56