Skip to content

Commit

Permalink
feat: auth guard with multiple dynamic auth models (#605)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xb4lint committed Jun 29, 2020
1 parent c606cc5 commit 63a3934
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 53 deletions.
5 changes: 5 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ services:
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: NunoMaduro\Larastan\ReturnTypes\GuardExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: NunoMaduro\Larastan\ReturnTypes\RequestExtension
tags:
Expand Down
23 changes: 23 additions & 0 deletions src/Concerns/LoadsAuthModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace NunoMaduro\Larastan\Concerns;

use Illuminate\Config\Repository as ConfigRepository;

trait LoadsAuthModel
{
private function getAuthModel(ConfigRepository $config, ?string $guard = null): ?string
{
if (
($guard === null && ! ($guard = $config->get('auth.defaults.guard'))) ||
! ($provider = $config->get('auth.guards.'.$guard.'.provider')) ||
! ($authModel = $config->get('auth.providers.'.$provider.'.model'))
) {
return null;
}

return $authModel;
}
}
17 changes: 2 additions & 15 deletions src/Methods/Pipes/Auths.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace NunoMaduro\Larastan\Methods\Pipes;

use Closure;
use Illuminate\Config\Repository as ConfigRepository;
use Illuminate\Contracts\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\CanResetPassword;
Expand All @@ -20,6 +19,7 @@
final class Auths implements PipeContract
{
use Concerns\HasContainer;
use Concerns\LoadsAuthModel;

/**
* @var string[]
Expand All @@ -43,7 +43,7 @@ public function handle(PassableContract $passable, Closure $next): void
$config = $this->resolve('config');

if ($config !== null && in_array($classReflectionName, $this->classes, true)) {
$authModel = $this->getDefaultAuthModel($config);
$authModel = $this->getAuthModel($config);

if ($authModel !== null) {
$found = $passable->sendToPipeline($authModel);
Expand All @@ -58,17 +58,4 @@ public function handle(PassableContract $passable, Closure $next): void
$next($passable);
}
}

private function getDefaultAuthModel(ConfigRepository $config): ?string
{
if (
! ($guard = $config->get('auth.defaults.guard')) ||
! ($provider = $config->get('auth.guards.'.$guard.'.provider')) ||
! ($authModel = $config->get('auth.providers.'.$provider.'.model'))
) {
return null;
}

return $authModel;
}
}
27 changes: 8 additions & 19 deletions src/ReturnTypes/AuthExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace NunoMaduro\Larastan\ReturnTypes;

use Illuminate\Config\Repository as ConfigRepository;
use Illuminate\Support\Facades\Auth;
use NunoMaduro\Larastan\Concerns;
use PhpParser\Node\Expr\StaticCall;
Expand All @@ -22,6 +21,7 @@
final class AuthExtension implements DynamicStaticMethodReturnTypeExtension
{
use Concerns\HasContainer;
use Concerns\LoadsAuthModel;

/**
* {@inheritdoc}
Expand All @@ -47,28 +47,17 @@ public function getTypeFromStaticMethodCall(
StaticCall $methodCall,
Scope $scope
): Type {
$config = $this->getContainer()
->get('config');
$config = $this->getContainer()->get('config');
$authModel = null;

$authModel = $this->getDefaultAuthModel($config);

if ($authModel !== null) {
return TypeCombinator::addNull(new ObjectType($authModel));
if ($config !== null) {
$authModel = $this->getAuthModel($config);
}

return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

private function getDefaultAuthModel(ConfigRepository $config): ?string
{
if (
! ($guard = $config->get('auth.defaults.guard')) ||
! ($provider = $config->get('auth.guards.'.$guard.'.provider')) ||
! ($authModel = $config->get('auth.providers.'.$provider.'.model'))
) {
return null;
if ($authModel === null) {
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

return $authModel;
return TypeCombinator::addNull(new ObjectType($authModel));
}
}
27 changes: 8 additions & 19 deletions src/ReturnTypes/AuthManagerExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace NunoMaduro\Larastan\ReturnTypes;

use Illuminate\Auth\AuthManager;
use Illuminate\Config\Repository as ConfigRepository;
use NunoMaduro\Larastan\Concerns;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
Expand All @@ -19,6 +18,7 @@
final class AuthManagerExtension implements DynamicMethodReturnTypeExtension
{
use Concerns\HasContainer;
use Concerns\LoadsAuthModel;

/**
* {@inheritdoc}
Expand All @@ -38,28 +38,17 @@ public function getTypeFromMethodCall(
MethodCall $methodCall,
Scope $scope
): Type {
$config = $this->getContainer()
->get('config');
$config = $this->getContainer()->get('config');
$authModel = null;

$authModel = $this->getDefaultAuthModel($config);

if ($authModel !== null) {
return TypeCombinator::addNull(new ObjectType($authModel));
if ($config !== null) {
$authModel = $this->getAuthModel($config);
}

return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

private function getDefaultAuthModel(ConfigRepository $config): ?string
{
if (
! ($guard = $config->get('auth.defaults.guard')) ||
! ($provider = $config->get('auth.guards.'.$guard.'.provider')) ||
! ($authModel = $config->get('auth.providers.'.$provider.'.model'))
) {
return null;
if ($authModel === null) {
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

return $authModel;
return TypeCombinator::addNull(new ObjectType($authModel));
}
}
81 changes: 81 additions & 0 deletions src/ReturnTypes/GuardExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace NunoMaduro\Larastan\ReturnTypes;

use Illuminate\Contracts\Auth\Guard;
use NunoMaduro\Larastan\Concerns;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

final class GuardExtension implements DynamicMethodReturnTypeExtension
{
use Concerns\HasContainer;
use Concerns\LoadsAuthModel;

/**
* {@inheritdoc}
*/
public function getClass(): string
{
return Guard::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'user';
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type {
$config = $this->getContainer()->get('config');
$authModel = null;

if ($config !== null) {
$guard = $this->getGuardFromMethodCall($scope, $methodCall);
$authModel = $this->getAuthModel($config, $guard);
}

if ($authModel === null) {
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

return TypeCombinator::addNull(new ObjectType($authModel));
}

private function getGuardFromMethodCall(Scope $scope, MethodCall $methodCall): ?string
{
if (
! ($methodCall->var instanceof StaticCall) &&
! ($methodCall->var instanceof MethodCall) &&
! ($methodCall->var instanceof FuncCall)
) {
return null;
}

if (count($methodCall->var->args) !== 1) {
return null;
}

$guardType = $scope->getType($methodCall->var->args[0]->value);

if (! $guardType instanceof ConstantStringType) {
return null;
}

return $guardType->getValue();
}
}
7 changes: 7 additions & 0 deletions tests/Application/app/Admin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace App;

class Admin
{
}
51 changes: 51 additions & 0 deletions tests/Application/config/auth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

return [

'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'admin' => [
'driver' => 'session',
'provider' => 'admins',
],

'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],

'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
],

'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],

'password_timeout' => 10800,

];
17 changes: 17 additions & 0 deletions tests/Features/ReturnTypes/AuthExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Tests\Features\ReturnTypes;

use App\Admin;
use App\User;
use Illuminate\Auth\SessionGuard;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Support\Facades\Auth;

class AuthExtension
Expand Down Expand Up @@ -34,6 +36,21 @@ public function testLogout(): void
Auth::guard()->logout();
}

public function testGuard(): Guard
{
return Auth::guard('web');
}

public function testGuardUser(): ?User
{
return Auth::guard('web')->user();
}

public function testGuardAdminUser(): ?Admin
{
return Auth::guard('admin')->user();
}

public function testSessionGuard(): SessionGuard
{
return Auth::guard('session');
Expand Down

0 comments on commit 63a3934

Please sign in to comment.