Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F12579599
D3860.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
8 KB
Referenced Files
None
Subscribers
None
D3860.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Wed, Nov 12, 09:54 (15 h, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3158913
Default Alt Text
D3860.diff (8 KB)
Attached To
Mode
D3860: WIP: describe property type
Attached
Detach File
Event Timeline
Log In to Comment