With the new Entity field API, we've to unset all defined entity properties for the magic getters to apply. #1778178: Convert comments to the new Entity Field API moves that to a central init() function, but still it's ugly. So let's discuss how to improve things here.

One think das-peter came up with in #1818556: Convert nodes to the new Entity Field API was to use get_class_vars(get_class($this)); automatically unset all of them. Maybe we need a function helper so the code runs outside the entity scope and does not include protected properties. We could also use reflection, but I don't know whether that's performing well.

Another possibility would be to remove the defined properties, however I think they are a good DX improvement. A downside of them right now, is that right now we've entity field definitions that kind of duplicate the docs of the entity class properties. We could look into using annotations to parse entity base-field definitions out from the entity class property docs though.

Comments

fago’s picture

Issue tags: +Entity Field API

adding tag

das-peter’s picture

As far as I know the most IDE's unfortunately don't support the @property annotation introduced in phpDoc years ago.
So for good DX I don't think we can't get fully rid of the properties. :|

get_class_vars(get_class($this)); could be a solution, but as fago said we've to be careful because of the scope we use this. A helper function outside the class could work but seems a bit ugly.
If it is always true that properties that can be unset have a return from $this->getPropertyDefinition($property) we could use this as condition for unset properties returned by get_class_vars(get_class($this));. I think the patch I've posted in #1818556: Convert nodes to the new Entity Field API already uses this construct.

Reflection sounds interesting, but definitely would need a benchmark ;)

fago’s picture

Yes, just looping over property definitions would be the simplest fix and should totally work. I'm not sure it's feasible from a performance perspective though... Also, it feelds wrong to depend on that for being able to construct or unserialize the object, so I'd prefer to avoid that if possible.

das-peter’s picture

I've just figured out that, call_user_func('get_class_vars', get_class($this)) resets the scope the function is called in. That way we should be able to fetch just the public properties of a class within a method of the class itself.

das-peter’s picture

I did some benchmarks to compare using plain unset() with dynamic approaches.
It looks like a generic approach takes up to 3 times longer - but it heavily depends on the number of the calls.
In absolute numbers this means for 100000 calls that the fastest dynamic approach takes 0.4842 seconds longer (260%) than the usage of unset().
The question now is, if this gain of DX is worth this :)

Used code:

class A {
  public $foo = 'foo';
  public $foobar = 'foo';
  public $one = 'one';
  public $two = 'two';
  protected $bar = 'bar';

  public function foo() {
    foreach (call_user_func('get_object_vars', $this) as $var) {
      unset($this->{$var});
    }
  }

  public function bar() {
    $closure = function($instance) { return get_object_vars($instance); };
    foreach ($closure($this) as $var) {
      unset($this->{$var});
    }
  }

  public function foobar() {
    foreach (foobar($this) as $var) {
      unset($this->{$var});
    }
  }

  public function reference() {
    unset(
      $this->foo,
      $this->foobar,
      $this->one,
      $this->two
    );
  }
}
function foobar($instance) {
  return get_object_vars($instance);
}

foreach (array(100, 1000, 10000, 100000) as $repeats) {
  echo str_repeat('-', 10) . ' Repeats ' . $repeats . ' ' . str_repeat('-', 10) . '<br>';
  $results = array();
  $A = new A();
  $time = microtime (TRUE);
  for($i=0; $i < $repeats; $i++) {
    $A->foo();
  }
  $key = '<span style="color:#A5700D;">foo</span>';
  $results[$key] = microtime (TRUE) - $time;
  echo $key . ': ' . round($results[$key], 6) . ' Sec<br>';

  $A = new A();
  $time = microtime (TRUE);
  for($i=0; $i < $repeats; $i++) {
    $A->bar();
  }
  $key = '<span style="color:#780BB7;">bar</span>';
  $results[$key] = microtime (TRUE) - $time;
  echo $key . ': ' . round($results[$key], 6) . ' Sec<br>';

  $A = new A();
  $time = microtime (TRUE);
  for($i=0; $i < $repeats; $i++) {
    $A->foobar();
  }
  $key = '<span style="color:#872923;">foobar</span>';
  $results[$key] = microtime (TRUE) - $time;
  echo $key . ': ' . round($results[$key], 6) . ' Sec<br>';

  $A = new A();
  $time = microtime (TRUE);
  for($i=0; $i < $repeats; $i++) {
    $A->reference();
  }
  $reference = microtime (TRUE) - $time;
  $reference_name = '<span style="color:#105E2A;">Reference</span>';
  echo $reference_name . ': ' . round($reference, 6) . ' Sec<br><br>';

  $winner = min($results);
  $winner_name = array_search($winner, $results);
  unset($results[$winner_name]);
  echo $winner_name . ' wins by ' . (round((min($results) / $winner) * 100, 6) - 100) . '%<br>';
  echo (($winner < $reference) ? $winner_name . ' beats ' . $reference_name . ' by ' : $reference_name . ' beats ' . $winner_name . ' by ') . round(($winner / $reference) * 100, 6) . '% [' . round(abs($reference - $winner), 5) . ' Sec]<br><br>';
}
berdir’s picture

Status: Active » Closed (duplicate)

Closing as duplicate of #2095919: Kill ContentEntityBase::init() , we don't need them anymore as we have interfaces & methods now, just need to get rid of the remaining ones :)