Reflection

Inspecting Classes and Members at Runtime

class_exists(), method_exists(), property_exists(), get_parent_class(), is_a() and $obj::class (PHP 8.0) answer quick questions; get_object_vars() respects the calling scope. The Reflection API answers the rest, and containers (Dependency Injection) and attributes (Custom Attributes) build on it.

Class functions versus ReflectionClassPHP
<?php
abstract class Product {
  public function __construct(public string $title, protected int $priceCents) {}
  public function vars(): array { return get_object_vars($this); }
}
final class Book extends Product { private ?string $isbn = null; }
$b = new Book('Dune', 1999);
echo $b::class, ' < ', get_parent_class($b), ' ', json_encode([method_exists($b, 'vars'),
  property_exists(Book::class, 'isbn'), is_a($b, 'Product')]), "\n";
echo json_encode(get_object_vars($b)), ' vs ', json_encode($b->vars()), "\n";
foreach (new ReflectionClass($b)->getProperties() as $p) {
  printf("%-10s %-9s %-7s %s\n", $p->getName(), implode(' ', Reflection::getModifierNames(
    $p->getModifiers())), $p->getType(), $p->getDeclaringClass()->getName());
}
Output
Book < Product [true,true,true]
{"title":"Dune"} vs {"title":"Dune","priceCents":1999}
isbn       private   ?string Book
title      public    string  Product
priceCents protected int     Product

vars() runs in Product's scope: it sees priceCents but not Book's private $isbn. Reflection reads compiled metadata and is cheap: 100,000 new ReflectionClass() took 7.6 ms here.