Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/BuilderMethodExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ public function setBroker(Broker $broker)
*/
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
if ($classReflection->isSubclassOf(Model::class) && !isset($this->methods[$classReflection->getName()])) {
if (!isset($this->methods[$classReflection->getName()]) && (
$classReflection->isSubclassOf(Model::class)
|| preg_match(
'/@mixin\s+' . preg_quote('\\' . Builder::class) . '/',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make the leading slash optional?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@twistor That / is the pattern delimiter!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the \\ for the namepsace.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, the backslashes!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@twistor sorry for delay, but I have a lot of work now, but promise to you to resolve issue in next few days

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@twistor I've moved mixin detection to a separate class and it will return class names without the leading slash, but if you'll try to use such form, for example, in IDE it will show incorrect namespace error (checked in PHPStorm). Of course, in IDE can be used even short class names, because it can parse use section, but in this case it is imposible to detect class namespace from doc block only.

(string) $classReflection->getNativeReflection()->getDocComment()
)
)) {
$builder = $this->broker->getClass(Builder::class);
$this->methods[$classReflection->getName()] = $this->createWrappedMethods($classReflection, $builder);

Expand Down
11 changes: 11 additions & 0 deletions src/FacadeMethodExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ public function hasMethod(ClassReflection $classReflection, string $methodName):
$instanceReflection = $this->broker->getClass(get_class($instance));
$this->methods[$classReflection->getName()] = $this->createMethods($classReflection, $instanceReflection);

if (preg_match_all(
'/@mixin\s+([\w\\\\]+)/',
(string) $instanceReflection->getNativeReflection()->getDocComment(),
$mixins
)) {
foreach ($mixins[1] as $mixin) {
$mixinInstanceReflection = $this->broker->getClass($mixin);
$this->methods[$classReflection->getName()] += $this->createMethods($classReflection, $mixinInstanceReflection);
}
}

if (isset($this->extensions[$instanceReflection->getName()])) {
$extensionMethod = $this->extensions[$instanceReflection->getName()];
$extensionReflection = $this->broker->getClass(get_class($instance->$extensionMethod()));
Expand Down
104 changes: 104 additions & 0 deletions tests/BuilderMethodExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php declare(strict_types = 1);

namespace Tests\Weebly\PHPStan\Laravel;

use PHPStan\Testing\TestCase;
use PHPStan\Broker\Broker;
use Weebly\PHPStan\Laravel\MethodReflectionFactory;
use PHPUnit\Framework\MockObject\MockObject;
use PHPStan\Reflection\Php\PhpMethodReflectionFactory;
use PHPStan\Reflection\Php\PhpMethodReflection;
use PHPStan\Type\FileTypeMapper;
use Illuminate\Database\Eloquent\Model;
use Weebly\PHPStan\Laravel\BuilderMethodExtension;
use stdClass;
use Illuminate\Database\Eloquent\Builder;

/**
* @package Tests\Weebly\PHPStan\Laravel
*/
class BuilderMethodExtensionTest extends TestCase
{
/**
* @var Broker
*/
private $broker;

public function testHasMethodInSubclassOfModel()
{
$this->assertFalse($this->hasMethod(stdClass::class, 'find'));
$this->assertTrue($this->hasMethod(ChildOfModel::class, 'find'));
$this->assertFalse($this->hasMethod(stdClass::class, 'select'));
$this->assertTrue($this->hasMethod(ChildOfModel::class, 'select'));
}

public function testHasMethodInClassWithMixinAnnotation()
{
$this->assertFalse($this->hasMethod(stdClass::class, 'find'));
$this->assertTrue($this->hasMethod(HasAMixinAnnotation::class, 'find'));
$this->assertFalse($this->hasMethod(stdClass::class, 'select'));
$this->assertTrue($this->hasMethod(HasAMixinAnnotation::class, 'select'));
}

public function testHasMethodInBuilder()
{
$this->assertFalse($this->hasMethod(stdClass::class, 'find'));
$this->assertTrue($this->hasMethod(Builder::class, 'find'));
$this->assertFalse($this->hasMethod(stdClass::class, 'select'));
$this->assertTrue($this->hasMethod(Builder::class, 'select'));
}

/**
* @inheritdoc
*/
protected function setUp()
{
parent::setUp();
$this->broker = $this->createBroker();
}

/**
* @return MethodReflectionFactory
*/
private function makeMethodReflectionFactoryMock()
{
/** @var MockObject|PhpMethodReflectionFactory $phpMethodReflectionFactory */
$phpMethodReflectionFactory = $this
->getMockBuilder(PhpMethodReflectionFactory::class)
->getMockForAbstractClass();
$methodReflectionMock = $this
->getMockBuilder(PhpMethodReflection::class)
->disableOriginalConstructor()
->getMock();
$phpMethodReflectionFactory->method('create')->willReturn($methodReflectionMock);
/** @var FileTypeMapper $fileTypeMapper */
$fileTypeMapper = $this->getContainer()->createInstance(FileTypeMapper::class);

return new MethodReflectionFactory($phpMethodReflectionFactory, $fileTypeMapper);
}

/**
* Check existence of the method in given class
*
* @param string $className
* @param string $methodName
* @return bool
*/
private function hasMethod(string $className, string $methodName): bool
{
$extension = new BuilderMethodExtension($this->makeMethodReflectionFactoryMock());
$extension->setBroker($this->broker);

return $extension->hasMethod($this->broker->getClass($className), $methodName);
}
}

class ChildOfModel extends Model {}

/**
* Some description
*
* @mixin \Illuminate\Database\Eloquent\Builder
* @method string getString()
*/
class HasAMixinAnnotation {}
98 changes: 98 additions & 0 deletions tests/FacadeMethodExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

namespace Tests\Weebly\PHPStan\Laravel;

use PHPStan\Testing\TestCase;
use PHPStan\Broker\Broker;
use Weebly\PHPStan\Laravel\MethodReflectionFactory;
use PHPUnit\Framework\MockObject\MockObject;
use PHPStan\Reflection\Php\PhpMethodReflectionFactory;
use PHPStan\Reflection\Php\PhpMethodReflection;
use PHPStan\Type\FileTypeMapper;
use Weebly\PHPStan\Laravel\FacadeMethodExtension;
use Illuminate\Support\Facades\Facade;

/**
* @package Tests\Weebly\PHPStan\Laravel
*/
class FacadeMethodExtensionTest extends TestCase
{
/**
* @var Broker
*/
private $broker;

public function testHasMethod()
{
// Native accessor method
$this->assertTrue($this->hasMethod(TestFacade::class, 'someMethod'));
$this->assertFalse($this->hasMethod(TestFacade::class, 'fakeMethod'));
// Method from accessor mixin
$this->assertTrue($this->hasMethod(TestFacade::class, 'table'));
$this->assertTrue($this->hasMethod(TestFacade::class, 'shouldUse'));
}

/**
* @inheritdoc
*/
protected function setUp()
{
parent::setUp();
$this->broker = $this->createBroker();
}

/**
* @return MethodReflectionFactory
*/
private function makeMethodReflectionFactoryMock()
{
/** @var MockObject|PhpMethodReflectionFactory $phpMethodReflectionFactory */
$phpMethodReflectionFactory = $this
->getMockBuilder(PhpMethodReflectionFactory::class)
->getMockForAbstractClass();
$methodReflectionMock = $this
->getMockBuilder(PhpMethodReflection::class)
->disableOriginalConstructor()
->getMock();
$phpMethodReflectionFactory->method('create')->willReturn($methodReflectionMock);
/** @var FileTypeMapper $fileTypeMapper */
$fileTypeMapper = $this->getContainer()->createInstance(FileTypeMapper::class);

return new MethodReflectionFactory($phpMethodReflectionFactory, $fileTypeMapper);
}

/**
* Check existence of the method in given class
*
* @param string $className
* @param string $methodName
* @return bool
*/
private function hasMethod(string $className, string $methodName): bool
{
$extension = new FacadeMethodExtension($this->makeMethodReflectionFactoryMock());
$extension->setBroker($this->broker);

return $extension->hasMethod($this->broker->getClass($className), $methodName);
}
}

/**
* @mixin \Illuminate\Database\Connection
* @mixin \Illuminate\Auth\AuthManager
*/
class TestFacadeAccessor {
function someMethod() {
return true;
}
}

class TestFacade extends Facade {
/**
* @inheritdoc
*/
protected static function getFacadeAccessor()
{
return new TestFacadeAccessor();
}
}