Allow PHP-8.2 and up Compatibility instead of just PHP-8.4

This commit is contained in:
johnnyq
2026-06-12 17:06:10 -04:00
parent 2204bd52f4
commit d3a93652f3
220 changed files with 7198 additions and 2635 deletions

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,213 @@
<?php
namespace Illuminate\Support;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionEnum;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionUnionType;
class Reflector
{
/**
* This is a PHP 7.4 compatible implementation of is_callable.
*
* @param mixed $var
* @param bool $syntaxOnly
* @return bool
*/
public static function isCallable($var, $syntaxOnly = false)
{
if (! is_array($var)) {
return is_callable($var, $syntaxOnly);
}
if (! isset($var[0], $var[1]) || ! is_string($var[1] ?? null)) {
return false;
}
if ($syntaxOnly &&
(is_string($var[0]) || is_object($var[0])) &&
is_string($var[1])) {
return true;
}
$class = is_object($var[0]) ? get_class($var[0]) : $var[0];
$method = $var[1];
if (! class_exists($class)) {
return false;
}
if (method_exists($class, $method)) {
return (new ReflectionMethod($class, $method))->isPublic();
}
if (is_object($var[0]) && method_exists($class, '__call')) {
return (new ReflectionMethod($class, '__call'))->isPublic();
}
if (! is_object($var[0]) && method_exists($class, '__callStatic')) {
return (new ReflectionMethod($class, '__callStatic'))->isPublic();
}
return false;
}
/**
* Get the specified class attribute, optionally following an inheritance chain.
*
* @template TAttribute of object
*
* @param object|class-string $objectOrClass
* @param class-string<TAttribute> $attribute
* @param bool $ascend
* @return TAttribute|null
*/
public static function getClassAttribute($objectOrClass, $attribute, $ascend = false)
{
return static::getClassAttributes($objectOrClass, $attribute, $ascend)->flatten()->first();
}
/**
* Get the specified class attribute(s), optionally following an inheritance chain.
*
* @template TTarget of object
* @template TAttribute of object
*
* @param TTarget|class-string<TTarget> $objectOrClass
* @param class-string<TAttribute> $attribute
* @param bool $includeParents
* @return ($includeParents is true ? Collection<class-string<contravariant TTarget>, Collection<int, TAttribute>> : Collection<int, TAttribute>)
*/
public static function getClassAttributes($objectOrClass, $attribute, $includeParents = false)
{
$reflectionClass = new ReflectionClass($objectOrClass);
$attributes = [];
do {
$attributes[$reflectionClass->name] = new Collection(array_map(
fn (ReflectionAttribute $reflectionAttribute) => $reflectionAttribute->newInstance(),
$reflectionClass->getAttributes($attribute)
));
} while ($includeParents && false !== $reflectionClass = $reflectionClass->getParentClass());
return $includeParents ? new Collection($attributes) : array_first($attributes);
}
/**
* Get the class name of the given parameter's type, if possible.
*
* @param \ReflectionParameter $parameter
* @return string|null
*/
public static function getParameterClassName($parameter)
{
$type = $parameter->getType();
if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) {
return;
}
return static::getTypeName($parameter, $type);
}
/**
* Get the class names of the given parameter's type, including union types.
*
* @param \ReflectionParameter $parameter
* @return array
*/
public static function getParameterClassNames($parameter)
{
$type = $parameter->getType();
if (! $type instanceof ReflectionUnionType) {
return array_filter([static::getParameterClassName($parameter)]);
}
$unionTypes = [];
foreach ($type->getTypes() as $listedType) {
if (! $listedType instanceof ReflectionNamedType || $listedType->isBuiltin()) {
continue;
}
$unionTypes[] = static::getTypeName($parameter, $listedType);
}
return array_filter($unionTypes);
}
/**
* Get the given type's class name.
*
* @param \ReflectionParameter $parameter
* @param \ReflectionNamedType $type
* @return string
*/
protected static function getTypeName($parameter, $type)
{
$name = $type->getName();
if (! is_null($class = $parameter->getDeclaringClass())) {
if ($name === 'self') {
return $class->getName();
}
if ($name === 'parent' && $parent = $class->getParentClass()) {
return $parent->getName();
}
}
return $name;
}
/**
* Determine if the parameter's type is a subclass of the given type.
*
* @param \ReflectionParameter $parameter
* @param string $className
* @return bool
*/
public static function isParameterSubclassOf($parameter, $className)
{
$paramClassName = static::getParameterClassName($parameter);
return $paramClassName
&& (class_exists($paramClassName) || interface_exists($paramClassName))
&& (new ReflectionClass($paramClassName))->isSubclassOf($className);
}
/**
* Determine if the parameter's type is a Backed Enum with a string backing type.
*
* @param \ReflectionParameter $parameter
* @return bool
*/
public static function isParameterBackedEnumWithStringBackingType($parameter)
{
if (! $parameter->getType() instanceof ReflectionNamedType) {
return false;
}
$backedEnumClass = $parameter->getType()?->getName();
if (is_null($backedEnumClass)) {
return false;
}
if (enum_exists($backedEnumClass)) {
$reflectionBackedEnum = new ReflectionEnum($backedEnumClass);
return $reflectionBackedEnum->isBacked()
&& $reflectionBackedEnum->getBackingType()->getName() == 'string';
}
return false;
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace Illuminate\Support\Traits;
use Closure;
use Illuminate\Support\Collection;
use Illuminate\Support\Reflector;
use ReflectionFunction;
use ReflectionIntersectionType;
use ReflectionUnionType;
use RuntimeException;
trait ReflectsClosures
{
/**
* Get the class name of the first parameter of the given Closure.
*
* @param \Closure $closure
* @return string
*
* @throws \ReflectionException
* @throws \RuntimeException
*/
protected function firstClosureParameterType(Closure $closure)
{
$types = array_values($this->closureParameterTypes($closure));
if (! $types) {
throw new RuntimeException('The given Closure has no parameters.');
}
if ($types[0] === null) {
throw new RuntimeException('The first parameter of the given Closure is missing a type hint.');
}
return $types[0];
}
/**
* Get the class names of the first parameter of the given Closure, including union types.
*
* @param \Closure $closure
* @return array
*
* @throws \ReflectionException
* @throws \RuntimeException
*/
protected function firstClosureParameterTypes(Closure $closure)
{
$reflection = new ReflectionFunction($closure);
$types = (new Collection($reflection->getParameters()))
->mapWithKeys(function ($parameter) {
if ($parameter->isVariadic()) {
return [$parameter->getName() => null];
}
return [$parameter->getName() => Reflector::getParameterClassNames($parameter)];
})
->filter()
->values()
->all();
if (empty($types)) {
throw new RuntimeException('The given Closure has no parameters.');
}
if (isset($types[0]) && empty($types[0])) {
throw new RuntimeException('The first parameter of the given Closure is missing a type hint.');
}
return $types[0];
}
/**
* Get the class names / types of the parameters of the given Closure.
*
* @param \Closure $closure
* @return array
*
* @throws \ReflectionException
*/
protected function closureParameterTypes(Closure $closure)
{
$reflection = new ReflectionFunction($closure);
return (new Collection($reflection->getParameters()))
->mapWithKeys(function ($parameter) {
if ($parameter->isVariadic()) {
return [$parameter->getName() => null];
}
return [$parameter->getName() => Reflector::getParameterClassName($parameter)];
})
->all();
}
/**
* Get the class names / types of the return type of the given Closure.
*
* @param \Closure $closure
* @return list<class-string>
*
* @throws \ReflectionException
*/
protected function closureReturnTypes(Closure $closure)
{
$reflection = new ReflectionFunction($closure);
if ($reflection->getReturnType() === null ||
$reflection->getReturnType() instanceof ReflectionIntersectionType) {
return [];
}
$types = $reflection->getReturnType() instanceof ReflectionUnionType
? $reflection->getReturnType()->getTypes()
: [$reflection->getReturnType()];
return (new Collection($types))
->reject(fn ($type) => $type->isBuiltin())
->reject(fn ($type) => in_array($type->getName(), ['static', 'self']))
->map(fn ($type) => $type->getName())
->values()
->all();
}
}

View File

@@ -0,0 +1,38 @@
{
"name": "illuminate/reflection",
"description": "The Illuminate Reflection package.",
"license": "MIT",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^8.2",
"illuminate/contracts": "^12.0",
"illuminate/collections": "^12.0"
},
"autoload": {
"psr-4": {
"Illuminate\\Support\\": ""
},
"files": [
"helpers.php"
]
},
"extra": {
"branch-alias": {
"dev-master": "12.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,97 @@
<?php
use Illuminate\Support\Traits\ReflectsClosures;
if (! function_exists('lazy')) {
/**
* Create a lazy instance.
*
* @template TValue of object
*
* @param class-string<TValue>|(\Closure(TValue): mixed) $class
* @param (\Closure(TValue): mixed)|int $callback
* @param int $options
* @param array<string, mixed> $eager
* @return TValue
*/
function lazy($class, $callback = 0, $options = 0, $eager = [])
{
static $closureReflector;
$closureReflector ??= new class
{
use ReflectsClosures;
public function typeFromParameter($callback)
{
return $this->firstClosureParameterType($callback);
}
};
[$class, $callback, $options] = is_string($class)
? [$class, $callback, $options]
: [$closureReflector->typeFromParameter($class), $class, $callback ?: $options];
$reflectionClass = new ReflectionClass($class);
$instance = $reflectionClass->newLazyGhost(function ($instance) use ($callback) {
$result = $callback($instance);
if (is_array($result)) {
$instance->__construct(...$result);
}
}, $options);
foreach ($eager as $property => $value) {
$reflectionClass->getProperty($property)->setRawValueWithoutLazyInitialization($instance, $value);
}
return $instance;
}
}
if (! function_exists('proxy')) {
/**
* Create a lazy proxy instance.
*
* @template TValue of object
*
* @param class-string<TValue>|(\Closure(TValue): TValue) $class
* @param (\Closure(TValue): TValue)|int $callback
* @param int $options
* @param array<string, mixed> $eager
* @return TValue
*/
function proxy($class, $callback = 0, $options = 0, $eager = [])
{
static $closureReflector;
$closureReflector = new class
{
use ReflectsClosures;
public function get($callback)
{
return $this->closureReturnTypes($callback)[0] ?? $this->firstClosureParameterType($callback);
}
};
[$class, $callback, $options] = is_string($class)
? [$class, $callback, $options]
: [$closureReflector->get($class), $class, $callback ?: $options];
$reflectionClass = new ReflectionClass($class);
$proxy = $reflectionClass->newLazyProxy(function () use ($callback, $eager, &$proxy) {
$instance = $callback($proxy, $eager);
return $instance;
}, $options);
foreach ($eager as $property => $value) {
$reflectionClass->getProperty($property)->setRawValueWithoutLazyInitialization($proxy, $value);
}
return $proxy;
}
}