How I stumbled onto the Reflection API
Tags: webdev, php
Published: 2022-01-21 09:52:23

Coding for fun can sometime lead you into unexpected neighbourhoods. This is what happened to me this week. I've written previously about the micro-framework [Framework-X](https://framework-x.org/) and this week I was building another project with it and was in need of hooking it up with a database. *Framework-X* supports async and non-blocking execution. For this reason, while it is possible, the project recommends going for non-blocking database access. So it is absolutely possible to use `Doctrine` but it is not the reactive way that *Framework-X* promotes. So this had me leave the known grounds of *Doctrine* to explore a more "hands-on" approach of communicating with the database.  
  
**So I though**, anyway.  
  
Being me, I like to explore new subjects and see how far I can go with my current proficiency. I come from a background where all I've ever done has almost exclusively been "*object oriented*", but now being left without `Doctrine` (a popular PHP [ORM](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping)) I saw an opportunity to build similar (but way more simplistic) functionality on my own.  
  
I didn't think I'd set out to build a mini-ORM in my project but that's where I've found myself. I have entity-classes, or what you could describe as "plain old PHP-objects" (POPOs). These POPOs describe their own properties with types. Theses types may describe a relation to another entity as well as a one-to-many relation, reflected as an array in the other class (many-to-one). I don't have a many-to-many relation in the project currently. So a POPO could look like this:  
  
```php
<?php 

namespace App\Entity;

class Category
{
    /**
     * @var string $uuid - UUID version 4 variant 1
     */
    private string $uuid = '';

    private string $name = '';

    /**
     * @var Category[] $childCategories
     */
    private array $childCategories = [];

    private ?Category $parentCategory = null;

    public function setUuid(string $uuid = ''): self
    {
        $this->uuid = $uuid;
        return $this;
    }

    public function getUuid(): string
    {
        return $this->uuid;
    }

    public function setName(string $name): self
    {
        $this->name = $name;
        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setChildCategories(array $childCategories): self
    {
        $this->childCategories = $childCategories;
        return $this;
    }

    public function getChildCategories(): array
    {
        return $this->childCategories;
    }

    public function addChildCategory(Category $childCategory): self
    {
        if (!$this->hasChildCategory($childCategory)){
            $this->childCategories[] = $childCategory;
        }
        return $this;
    }

    public function removeChildCategory(Category $childCategory): self
    {
        $this->childCategories = array_filter($this->childCategories, function ($category) use ($childCategory) {
            return $category->getUuid() !== $childCategory->getUuid();
        });

        return $this;
    }

    public function hasChildCategory(Category $childCategory): bool
    {
        return in_array($childCategory, $this->childCategories);
    }

    public function setParentCategory(Category $parentCategory): self
    {
        $parentCategory->addChildCategory($this);
        $this->parentCategory = $parentCategory;
        return $this;
    }

    public function getParentCategory(): ?Category
    {
        return $this->parentCategory;
    }
}
```
  
Having POPOs like this, I wanted to be able to store these objects in the database, as well as fetch results from the database and turn them back into objects. For this to work for me, I needed to know what types each property is. In the case of arrays, I needed to know the type of the objects inside the array. Normal properties simply has a stated type, for arrays I use the `@var` annotation:  
```php
    /**
     * @var Category[] $childCategories
     */
    private array $childCategories = [];
```
  
For each property I also have a getter and a setter. Knowing all this, I can get all relevant properties when I want to store them in the database. With enough insight into the class I can turn each property into a corresponding SQL-type: a string into `VARCHAR` for MySQL for example, and a reference into a `VARCHAR` (as I'm using UUID) along with a foreign-key.  

**But from where do I get the relevant insight?!**  
  
That would be the `ReflectionClass`! Without it I would have to parse each class-file line-by-line, but instead I can use `getMethods`, `getProperties`, `getType`, `getDocComment`. What I found most fun in this project was fetching the annotation type hint for the arrays. I built a method in a utility class to fetch those:  
```php

    // Inside a class called App\Utility\EntityReflection.php

    public static function getPropertyTypeFromComment(string $entityClass, string $property): ?string
    {
        $reflectionClass = new \ReflectionClass($entityClass);
        $reflectionProperty = $reflectionClass->getProperty($property);
        $propertyComment = $reflectionProperty->getDocComment();

        $refType = preg_grep('/([A-Z])\w+/', explode(" ", $propertyComment));

        if (count($refType) > 0) {
            $refType = array_pop($refType);
        } else {
            return null;
        }

        if ($refType === null) {
            return null;
        }

        if (strpos($refType, '[]') !== false) {
            return $refType;
        }

        return null;    
    }
```  
  
I could call this with `$commentType = EntityReflection::getPropertyTypeFromComment(Category::class, 'categories');`, which would return `Category[]`.  
  
And that's how I stumbled upon the Reflection API. I now use it successfully storing and fetching objects from a relational database. But that's another story. So have you used reflection in any of your objects, and how did you like it? As I may have hinted at, I find it pretty fun, magical and very useful!