Compare commits

..
Author SHA1 Message Date
Barry vd. Heuvel 3ad16a295a Remove closure in attribute test 2026-03-05 21:33:25 +01:00
isaackaara c3f795e909 fix: skip autoload exception when class existence is being checked (#1764)
The MetaCommand's custom autoloader throws a ReflectionException for any
class not found during meta generation. However, this breaks libraries
that use class_exists() to check for optional dependencies before loading
them (e.g. Doctrine ORM checking for StaticReflectionService removed in
doctrine/persistence v4).

This fix checks the call stack (up to 3 frames) for class_exists(),
interface_exists(), trait_exists(), or enum_exists() calls and returns
gracefully instead of throwing, allowing the existence check to return
false as expected.

Fixes #1750
2026-03-04 17:15:04 +01:00
2fba281b14 Fix PHPDoc placement before PHP 8 class attributes and add regression tests (#1769)
* Initial plan

* Add tests for PHP class attribute placement and fix whitespace in regex

Co-authored-by: barryvdh <[email protected]>

* Add advanced edge case tests: final+nested-brackets and multiple attributes

Co-authored-by: barryvdh <[email protected]>

* Add PHP 8.5 test for closures inside attribute arguments

Co-authored-by: barryvdh <[email protected]>

* composer fix-style

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: barryvdh <[email protected]>
Co-authored-by: laravel-ide-helper <[email protected]>
2026-03-04 12:00:42 +01:00
laravel-ide-helper 39bcb60b7c composer fix-style 2026-03-04 09:53:06 +00:00
isaackaara 16f685aa9f fix: don't extend root's parent class for facade stubs in helper file (#1766)
Since #1674 (commit 09623f2), the generated IDE helper file adds an
`extends` declaration for Macroable classes to expose inherited methods.
However, the check to skip facades only excluded classes in the
`\Illuminate\Support\Facades` namespace, so third-party facades (e.g.
`Spatie\Menu\Laravel\Facades\Menu`) would incorrectly extend the
facade root's parent class.

This caused the generated stub to conflict with the real facade class:

    namespace Spatie\Menu\Laravel\Facades {
        class Menu extends \Spatie\Menu\Menu {  // wrong!

The fix checks whether the extends class IS a Facade subclass (using
`is_subclass_of`) rather than comparing namespace strings. This
correctly excludes ALL facade classes (both Laravel and third-party)
while still allowing non-facade Macroable classes to extend their
parent.

Fixes #1724
2026-03-04 10:52:43 +01:00
5 changed files with 13 additions and 64 deletions
+1 -1
View File
@@ -187,7 +187,7 @@ class Alias
public function shouldExtendParentClass() public function shouldExtendParentClass()
{ {
return $this->parentClass return $this->parentClass
&& $this->getExtendsNamespace() !== '\\Illuminate\\Support\\Facades'; && !is_subclass_of($this->extends, Facade::class);
} }
/** /**
+12
View File
@@ -195,6 +195,18 @@ class MetaCommand extends Command
return; return;
} }
// Don't throw when class existence is being checked via class_exists(),
// interface_exists(), trait_exists(), or enum_exists(). These functions
// expect the autoloader to return gracefully when the class doesn't exist.
// Throwing here would break libraries that use class_exists() to check for
// optional dependencies (e.g. Doctrine ORM checking for removed classes).
$existsFunctions = ['class_exists', 'interface_exists', 'trait_exists', 'enum_exists'];
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3) as $frame) {
if (isset($frame['function']) && in_array($frame['function'], $existsFunctions, true)) {
return;
}
}
throw new \ReflectionException("Class '$class' not found."); throw new \ReflectionException("Class '$class' not found.");
}; };
@@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\PhpAttributesClosureInAttribute\Models;
use Illuminate\Database\Eloquent\Model;
// Tests: closure expression inside an attribute argument (PHP 8.5+)
#[SomeClosureAttr(fn () => true)]
class ClosureInAttribute extends Model
{
protected $table = 'simples';
}
@@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\PhpAttributesClosureInAttribute;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
class Test extends AbstractModelsCommand
{
/**
* @requires PHP 8.5
*/
public function test(): void
{
$command = $this->app->make(ModelsCommand::class);
$tester = $this->runCommand($command, [
'--write' => true,
]);
$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
$this->assertMatchesMockedSnapshot();
}
}
@@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\PhpAttributesClosureInAttribute\Models;
use Illuminate\Database\Eloquent\Model;
// Tests: closure expression inside an attribute argument (PHP 8.5+)
/**
* @property int $id
* @method static \Illuminate\Database\Eloquent\Builder<static>|ClosureInAttribute newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ClosureInAttribute newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ClosureInAttribute query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ClosureInAttribute whereId($value)
* @mixin \Eloquent
*/
#[SomeClosureAttr(fn() => true)]
class ClosureInAttribute extends Model
{
protected $table = 'simples';
}