Page MenuHomeDevCentral

D3860.diff
No OneTemporary

D3860.diff

diff --git a/omnitools/src/Reflection/CodeClass.php b/omnitools/src/Reflection/CodeClass.php
--- a/omnitools/src/Reflection/CodeClass.php
+++ b/omnitools/src/Reflection/CodeClass.php
@@ -63,6 +63,22 @@
};
}
+ ///
+ /// Properties helper methods
+ ///
+
+ /**
+ * @throws ReflectionException
+ */
+ public function hasProperty (string $propertyName) : bool {
+ $class = new ReflectionClass($this->className);
+ return $class->hasProperty($propertyName);
+ }
+
+ public function getProperty (string $propertyName) : CodeProperty {
+ return new CodeProperty($this, $propertyName);
+ }
+
///
/// Represented class constructor helper methods
///
diff --git a/omnitools/src/Reflection/CodeProperty.php b/omnitools/src/Reflection/CodeProperty.php
new file mode 100644
--- /dev/null
+++ b/omnitools/src/Reflection/CodeProperty.php
@@ -0,0 +1,223 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Reflection;
+
+use Keruald\OmniTools\Collections\Vector;
+
+use InvalidArgumentException;
+use LogicException;
+use ReflectionException;
+use ReflectionIntersectionType;
+use ReflectionNamedType;
+use ReflectionProperty;
+use ReflectionType;
+use ReflectionUnionType;
+
+class CodeProperty {
+
+ private ?ReflectionProperty $reflection = null;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (
+ private readonly CodeClass $class,
+ private readonly string $property,
+ ) {
+ $className = $class->getClassName();
+
+ try {
+ $this->reflection = new ReflectionProperty($className, $property);
+ } catch (ReflectionException) {
+ // Do nothing
+ }
+ }
+
+ public static function from (object $object, string $property) : self {
+ return new self(CodeClass::from($object), $property);
+ }
+
+ ///
+ /// Properties
+ ///
+
+ public function getClass () : CodeClass {
+ return $this->class;
+ }
+
+ public function getPropertyName () : string {
+ return $this->property;
+ }
+
+ public function exists () : bool {
+ return $this->reflection !== null;
+ }
+
+ ///
+ /// Property type - Types similar to CodeVariable
+ ///
+
+ /**
+ * @throws ReflectionException
+ */
+ public function hasType (string $type) : bool {
+ if (!$this->exists()) {
+ throw new ReflectionException("Property does not exist.");
+ }
+
+ $ourType = $this->reflection->getType();
+ return self::hasUnderlyingType($type, $ourType);
+ }
+
+ private static function hasUnderlyingType (string $type, ?ReflectionType $ourType) : bool {
+ // Case 1. No type hint
+ if ($ourType === null) {
+ return $type === "mixed";
+ }
+
+ // Case 2. Union or intersection types
+ if ($ourType instanceof ReflectionUnionType || $ourType instanceof ReflectionIntersectionType) {
+ foreach ($ourType->getTypes() as $inSetType) {
+ if (self::hasUnderlyingType($type, $inSetType)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Case 3. Known named type
+ return Type::areTypesEqual($type, $ourType->getName());
+ }
+
+ public function isCompositeType () : bool {
+ $type = $this->reflection->getType();
+
+ return $type instanceof ReflectionUnionType || $type instanceof ReflectionIntersectionType;
+ }
+
+ /**
+ * @throws ReflectionException
+ * @throws InvalidArgumentException
+ */
+ public function getType () : string {
+ if (!$this->exists()) {
+ throw new ReflectionException("Property does not exist.");
+ }
+
+ $type = $this->reflection->getType();
+ if ($type === null) {
+ return "mixed";
+ }
+
+ if ($this->isCompositeType()) {
+ throw new InvalidArgumentException("Composite types are not supported.");
+ }
+
+ return Type::normalizeType($type->getName());
+ }
+
+ /**
+ * @return Vector<string>
+ * @throws ReflectionException
+ */
+ public function getTypes () : Vector {
+ if (!$this->exists()) {
+ throw new ReflectionException("Property does not exist.");
+ }
+
+ $type = $this->reflection->getType();
+ if ($type === null) {
+ return Vector::from(["mixed"]);
+ }
+
+ if ($this->isCompositeType()) {
+ $types = Vector::from($type->getTypes());
+ } else {
+ $types = Vector::from([$type]);
+ }
+
+ return $types->map(
+ fn(ReflectionNamedType $t) => Type::normalizeType($t->getName())
+ );
+ }
+
+ /**
+ * @throws ReflectionException
+ */
+ public function getFullyDescribedType () : string {
+ if (!$this->exists()) {
+ throw new ReflectionException("Property does not exist.");
+ }
+
+ $type = $this->reflection->getType();
+ if ($type === null) {
+ return "mixed";
+ }
+
+ if ($type instanceof ReflectionNamedType) {
+ return Type::normalizeType($type->getName());
+ }
+
+ $types = $this->getTypes();
+
+ if ($type instanceof ReflectionIntersectionType) {
+ return (string)$types->implode("&");
+ }
+
+ if ($type instanceof ReflectionUnionType) {
+ return (string)$types->implode("|");
+ }
+
+ throw new LogicException("Unreachable code reached.");
+ }
+
+ ///
+ /// Property type - nullable
+ ///
+
+ const string RE_PHPDOC = '/@var\s+(\S+)\s*(\|null)?/';
+
+ /**
+ * @throws ReflectionException when the property does not exist
+ */
+ function isNullable () : bool {
+ if (!$this->exists()) {
+ throw new ReflectionException("Property does not exist.");
+ }
+
+ // CASE I. Nullable is explicitly indicated in the type hint (e.g. ?int)
+ $type = $this->reflection->getType();
+ if ($type !== null && $type->allowsNull()) {
+ return true;
+ }
+
+ // CASE II. Nullable is declared in PHPDoc
+ if (self::hasNullInDocComment()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if the property documentation block indicates the property
+ * can be null (e.g., "@var int|null")
+ */
+ private function hasNullInDocComment () : bool {
+ $docComment = $this->reflection->getDocComment();
+ if (!$docComment) {
+ return false;
+ }
+
+ $result = preg_match(self::RE_PHPDOC, $docComment, $matches);
+ if (!$result) {
+ return false;
+ }
+
+ return isset($matches[2]) && $matches[2] === '|null';
+ }
+
+}
diff --git a/omnitools/src/Reflection/CodeVariable.php b/omnitools/src/Reflection/CodeVariable.php
--- a/omnitools/src/Reflection/CodeVariable.php
+++ b/omnitools/src/Reflection/CodeVariable.php
@@ -25,29 +25,25 @@
public function hasType (string $type) : bool {
$ourType = gettype($this->variable);
+ if ($ourType === "object") {
+ return $this->variable::class === $type;
+ }
+
// For scalar types, gettype() doesn't return the same types
// as does reflection classes.
- return match ($ourType) {
- "boolean" => $type === "bool" || $type === "boolean",
- "integer" => $type === "int" || $type === "integer",
- "double" => $type === "float" || $type === "double",
- "object" => $this->variable::class === $type,
- default => $ourType === $type,
- };
+ return Type::areTypesEqual($type, $ourType);
}
public function getType () : string {
$type = gettype($this->variable);
+ if ($type === "object") {
+ return $this->variable::class;
+ }
+
// For scalar types, gettype() doesn't return the same types
// as does reflection classes.
- return match ($type) {
- "boolean" => "bool",
- "integer" => "int",
- "double" => "float",
- "object" => $this->variable::class,
- default => $type,
- };
+ return Type::normalizeType($type);
}
}
diff --git a/omnitools/src/Reflection/Type.php b/omnitools/src/Reflection/Type.php
--- a/omnitools/src/Reflection/Type.php
+++ b/omnitools/src/Reflection/Type.php
@@ -14,4 +14,17 @@
return $type;
}
+ public static function normalizeType (string $type) : string {
+ return match ($type) {
+ "boolean" => "bool",
+ "integer" => "int",
+ "double" => "float",
+ default => $type,
+ };
+ }
+
+ public static function areTypesEqual (string $type1, string $type2) : bool {
+ return self::normalizeType($type1) === self::normalizeType($type2);
+ }
+
}

File Metadata

Mime Type
text/plain
Expires
Wed, Nov 12, 10:22 (16 h, 57 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3158913
Default Alt Text
D3860.diff (8 KB)

Event Timeline