xp-framework / rfc

One of the major deficiencies in the development of many projects is that there is no roadmap or strategy available other than in the developers' heads. The XP team publishes its decisions by documenting change requests in form of RFCs.
2 stars 1 forks source link

Extract reflection to its own library #338

Open thekid opened 3 years ago

thekid commented 3 years ago

Scope of Change

This RFC suggests extracting reflection to its own library, while the type system will continue to exist.

Rationale

Reflection is a big block of code with quite a bit of legacy, including an outdated concept of annotations which doesn't fit PHP 8 attributes well.

Functionality

This is the lang.reflect package, which will be replaced by https://github.com/xp-framework/reflection except for the Proxy class, which we might extract to its own library or simply move to the mocking library:

package lang.reflect {

  public interface lang.reflect.InvocationHandler

  public abstract class lang.reflect.Modifiers
  public class lang.reflect.ClassParser
  public class lang.reflect.Constructor
  public class lang.reflect.Field
  public class lang.reflect.Method
  public class lang.reflect.Module
  public class lang.reflect.Package
  public class lang.reflect.Parameter
  public class lang.reflect.Proxy
  public class lang.reflect.Routine
  public class lang.reflect.TargetInvocationException
}

The lang.XPClass class will be stripped down to its essentials, where it will serve the purpose of reflecting value types.

Security considerations

None.

Speed impact

Reduces the size of XP core:

$ cloc-1.82.pl src/main/php/lang/reflect/ src/main/php/lang/XPClass.class.php
      13 text files.
      13 unique files.
       1 file ignored.

github.com/AlDanial/cloc v 1.82  T=0.06 s (221.4 files/s, 58222.3 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
PHP                             13            298           1148           1972
-------------------------------------------------------------------------------
SUM:                            13            298           1148           1972
-------------------------------------------------------------------------------

Dependencies

Generics.

Related documents

https://github.com/xp-framework/unittest/pull/43

thekid commented 3 years ago

The xp-framework/reflection library comes in at around 1700 LOC:

$ cloc-1.82.pl src/main/
      43 text files.
      43 unique files.
       8 files ignored.

github.com/AlDanial/cloc v 1.82  T=0.15 s (280.1 files/s, 19744.7 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
PHP                             42            380            861           1720
-------------------------------------------------------------------------------
SUM:                            42            380            861           1720
-------------------------------------------------------------------------------

The code in the core library's lang.reflect package is around 1500 lines of code, additionally, more than 400 lines would be removed from lang.XPClass, making both libraries about equally heavy.

thekid commented 3 years ago

⚠️ Proxy removal

The lang.reflect.Proxy class would be dropped, there has so far only been one library using it, which could be rewritten to include this class (or a specialized version of it).

thekid commented 3 years ago

⚠️ Generics removal?

XP Framework's generic types, which heavily rely on reflection (especially on annotations), have only really been used inside xp-framework/collections, really, so the first idea would be to remove them from core. However, it's unclear how the type system can be extended from the outside.

$ cloc-1.82.pl src/main/php/lang/GenericTypes.class.php
       1 text file.
       1 unique file.
       0 files ignored.

github.com/AlDanial/cloc v 1.82  T=0.06 s (16.8 files/s, 5807.0 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
PHP                              1             16             30            299
-------------------------------------------------------------------------------

Basically, this infrastructure makes declaring reusable collection classes easier.

Currently possible

#[Generic('T')]
class Vector {
  private $elements;

  #[Generic(params: ['T'])]
  public function __construct(... $elements) {
    $this->elements= $elements;
  }
}

$v= create('new Vector<string>', ['hello', 'world']);

Without them, we would need to write code such as

class Vector {
  private $elements= [];

  public function __construct(Type $type, ... $elements) {
    $this->type= $type;
    foreach ($elements as $element) {
      if (!$type->isInstance($element)) throw ...
      $this->elements[]= $element;
    }
  }
}

$v= new Vector(Primitive::$STRING, 'hello', 'world');

The most useful class in the xp-framework/collections library is util.collections.HashTable, which can map arbitrary values to arbitray values. However, this can also be done using PHP's SplObjectStorage class.

The most useful feature is the type safety, which could be implemented by something along the lines of:

$v= Vectors::of('string')->new('hello', 'world');  // returns a Vector class

This library could come with something like a Generics class which would extend lang.Type and provide utility methods for verifying arguments. The only thing that wouldn't work this way would be using is() or Type::forName() from XP core.

Decision

It boils down to whether a language (or here: something that extends the PHP language) should have generics. GoLang has done without them for over a decade, and has put lots of thought into making it viable, see https://blog.golang.org/generics-next-step.

Removing generics would mean:

thekid commented 1 year ago

The following changes make the transition to xp-framework/reflection easier:

thekid commented 1 year ago

Before this can be implemented, XP core needs to be migrated to the new testing library, see #344. Otherwise, we'll simply see Call to undefined method lang\XPClass::getMethods()) when trying to run the framework's unittests.

thekid commented 1 year ago

The Package class contains the getClasses() method which is quite convenient; and needs to be replaced by the following if you don't want a dependency on xp-framework/reflection:

/**
 * Returns XPClass instances for all classes inside a given package
 *
 * @param  string $package
 * @return iterable
 */
private static function classesIn($package) {
  $offset= -strlen(\xp::CLASS_FILE_EXT);
  $cl= ClassLoader::getDefault();
  foreach ($cl->packageContents($package) as $item) {
    if (0 === substr_compare($item, \xp::CLASS_FILE_EXT, $offset)) {
      yield $cl->loadClass($package.'.'.substr($item, 0, $offset));
    }
  }
}

...which exposes quite a bit of internals. Maybe the ClassLoader API itself could provide a packageTypes() method?

thekid commented 5 months ago

Re the Proxy class - https://github.com/xp-framework/rfc/issues/338#issuecomment-774665217

The Proxy class will be removed in XP 12, see https://github.com/xp-framework/core/pull/341

thekid commented 5 months ago

The test library was still using XP reflection inside its FromDirectory source. This was changed in xp-framework/test@46fc70e06876c500d6b04d7561836010212cb377 and released in https://github.com/xp-framework/test/releases/tag/v2.1.0

thekid commented 5 months ago

The logging library was changed to use the reflection library in https://github.com/xp-framework/logging/releases/tag/v11.2.0