first commit

This commit is contained in:
Sampanna Rimal
2024-08-27 17:48:06 +05:45
commit 53c0140f58
10839 changed files with 1125847 additions and 0 deletions

1377
vendor/laravel/framework/CHANGELOG.md vendored Normal file

File diff suppressed because it is too large Load Diff

21
vendor/laravel/framework/LICENSE.md vendored Normal file
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.

47
vendor/laravel/framework/README.md vendored Normal file
View File

@ -0,0 +1,47 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
> **Note:** This repository contains the core code of the Laravel framework. If you want to build an application using Laravel, visit the main [Laravel repository](https://github.com/laravel/laravel).
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable, creative experience to be truly fulfilling. Laravel attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, yet powerful, providing tools needed for large, robust applications. A superb combination of simplicity, elegance, and innovation gives you a complete toolset required to build any application with which you are tasked.
## Learning Laravel
Laravel has the most extensive and thorough documentation and video tutorial library of any modern web application framework. The [Laravel documentation](https://laravel.com/docs) is in-depth and complete, making it a breeze to get started learning the framework.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you're not in the mood to read, [Laracasts](https://laracasts.com) contains thousands of video tutorials covering a range of topics including Laravel, modern PHP, unit testing, JavaScript, and more. Boost the skill level of yourself and your entire team by digging into our comprehensive video library.
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
Please review [our security policy](https://github.com/laravel/framework/security/policy) on how to report security vulnerabilities.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](LICENSE.md).

203
vendor/laravel/framework/composer.json vendored Normal file
View File

@ -0,0 +1,203 @@
{
"name": "laravel/framework",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"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.1",
"ext-ctype": "*",
"ext-filter": "*",
"ext-hash": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-session": "*",
"ext-tokenizer": "*",
"composer-runtime-api": "^2.2",
"brick/math": "^0.9.3|^0.10.2|^0.11|^0.12",
"doctrine/inflector": "^2.0.5",
"dragonmantank/cron-expression": "^3.3.2",
"egulias/email-validator": "^3.2.1|^4.0",
"fruitcake/php-cors": "^1.2",
"guzzlehttp/uri-template": "^1.0",
"laravel/prompts": "^0.1.9",
"laravel/serializable-closure": "^1.3",
"league/commonmark": "^2.2.1",
"league/flysystem": "^3.8.0",
"monolog/monolog": "^3.0",
"nesbot/carbon": "^2.67",
"nunomaduro/termwind": "^1.13",
"psr/container": "^1.1.1|^2.0.1",
"psr/log": "^1.0|^2.0|^3.0",
"psr/simple-cache": "^1.0|^2.0|^3.0",
"ramsey/uuid": "^4.7",
"symfony/console": "^6.2",
"symfony/error-handler": "^6.2",
"symfony/finder": "^6.2",
"symfony/http-foundation": "^6.4",
"symfony/http-kernel": "^6.2",
"symfony/mailer": "^6.2",
"symfony/mime": "^6.2",
"symfony/process": "^6.2",
"symfony/routing": "^6.2",
"symfony/uid": "^6.2",
"symfony/var-dumper": "^6.2",
"tijsverkoyen/css-to-inline-styles": "^2.2.5",
"vlucas/phpdotenv": "^5.4.1",
"voku/portable-ascii": "^2.0"
},
"replace": {
"illuminate/auth": "self.version",
"illuminate/broadcasting": "self.version",
"illuminate/bus": "self.version",
"illuminate/cache": "self.version",
"illuminate/collections": "self.version",
"illuminate/conditionable": "self.version",
"illuminate/config": "self.version",
"illuminate/console": "self.version",
"illuminate/container": "self.version",
"illuminate/contracts": "self.version",
"illuminate/cookie": "self.version",
"illuminate/database": "self.version",
"illuminate/encryption": "self.version",
"illuminate/events": "self.version",
"illuminate/filesystem": "self.version",
"illuminate/hashing": "self.version",
"illuminate/http": "self.version",
"illuminate/log": "self.version",
"illuminate/macroable": "self.version",
"illuminate/mail": "self.version",
"illuminate/notifications": "self.version",
"illuminate/pagination": "self.version",
"illuminate/pipeline": "self.version",
"illuminate/process": "self.version",
"illuminate/queue": "self.version",
"illuminate/redis": "self.version",
"illuminate/routing": "self.version",
"illuminate/session": "self.version",
"illuminate/support": "self.version",
"illuminate/testing": "self.version",
"illuminate/translation": "self.version",
"illuminate/validation": "self.version",
"illuminate/view": "self.version"
},
"require-dev": {
"ext-gmp": "*",
"ably/ably-php": "^1.0",
"aws/aws-sdk-php": "^3.235.5",
"doctrine/dbal": "^3.5.1",
"fakerphp/faker": "^1.21",
"guzzlehttp/guzzle": "^7.5",
"league/flysystem-aws-s3-v3": "^3.0",
"league/flysystem-ftp": "^3.0",
"league/flysystem-path-prefixing": "^3.3",
"league/flysystem-read-only": "^3.3",
"league/flysystem-sftp-v3": "^3.0",
"mockery/mockery": "^1.5.1",
"nyholm/psr7": "^1.2",
"orchestra/testbench-core": "^8.23.4",
"pda/pheanstalk": "^4.0",
"phpstan/phpstan": "^1.4.7",
"phpunit/phpunit": "^10.0.7",
"predis/predis": "^2.0.2",
"symfony/cache": "^6.2",
"symfony/http-client": "^6.2.4",
"symfony/psr-http-message-bridge": "^2.0"
},
"provide": {
"psr/container-implementation": "1.1|2.0",
"psr/simple-cache-implementation": "1.0|2.0|3.0"
},
"conflict": {
"carbonphp/carbon-doctrine-types": ">=3.0",
"doctrine/dbal": ">=4.0",
"mockery/mockery": "1.6.8",
"tightenco/collect": "<5.5.33",
"phpunit/phpunit": ">=11.0.0"
},
"autoload": {
"files": [
"src/Illuminate/Collections/helpers.php",
"src/Illuminate/Events/functions.php",
"src/Illuminate/Filesystem/functions.php",
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Support/helpers.php"
],
"psr-4": {
"Illuminate\\": "src/Illuminate/",
"Illuminate\\Support\\": [
"src/Illuminate/Macroable/",
"src/Illuminate/Collections/",
"src/Illuminate/Conditionable/"
]
}
},
"autoload-dev": {
"files": [
"tests/Database/stubs/MigrationCreatorFakeMigration.php"
],
"psr-4": {
"Illuminate\\Tests\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "10.x-dev"
}
},
"suggest": {
"ext-apcu": "Required to use the APC cache driver.",
"ext-fileinfo": "Required to use the Filesystem class.",
"ext-ftp": "Required to use the Flysystem FTP driver.",
"ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
"ext-memcached": "Required to use the memcache cache driver.",
"ext-pcntl": "Required to use all features of the queue worker and console signal trapping.",
"ext-pdo": "Required to use all database features.",
"ext-posix": "Required to use all features of the queue worker.",
"ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).",
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
"aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).",
"brianium/paratest": "Required to run tests in parallel (^6.0).",
"doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).",
"fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).",
"filp/whoops": "Required for friendly error pages in development (^2.14.3).",
"guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).",
"laravel/tinker": "Required to use the tinker console command (^2.0).",
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).",
"league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).",
"league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).",
"league/flysystem-read-only": "Required to use read-only disks (^3.3)",
"league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).",
"mockery/mockery": "Required to use mocking (^1.5.1).",
"nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).",
"pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).",
"phpunit/phpunit": "Required to use assertions and run tests (^9.5.8|^10.0.7).",
"predis/predis": "Required to use the predis connector (^2.0.2).",
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).",
"symfony/cache": "Required to PSR-6 cache bridge (^6.2).",
"symfony/filesystem": "Required to enable support for relative symbolic links (^6.2).",
"symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.2).",
"symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.2).",
"symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.2).",
"symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)."
},
"config": {
"sort-packages": true,
"allow-plugins": {
"composer/package-versions-deprecated": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

View File

@ -0,0 +1,114 @@
<?php
namespace Illuminate\Auth\Access;
use Exception;
use Throwable;
class AuthorizationException extends Exception
{
/**
* The response from the gate.
*
* @var \Illuminate\Auth\Access\Response
*/
protected $response;
/**
* The HTTP response status code.
*
* @var int|null
*/
protected $status;
/**
* Create a new authorization exception instance.
*
* @param string|null $message
* @param mixed $code
* @param \Throwable|null $previous
* @return void
*/
public function __construct($message = null, $code = null, Throwable $previous = null)
{
parent::__construct($message ?? 'This action is unauthorized.', 0, $previous);
$this->code = $code ?: 0;
}
/**
* Get the response from the gate.
*
* @return \Illuminate\Auth\Access\Response
*/
public function response()
{
return $this->response;
}
/**
* Set the response from the gate.
*
* @param \Illuminate\Auth\Access\Response $response
* @return $this
*/
public function setResponse($response)
{
$this->response = $response;
return $this;
}
/**
* Set the HTTP response status code.
*
* @param int|null $status
* @return $this
*/
public function withStatus($status)
{
$this->status = $status;
return $this;
}
/**
* Set the HTTP response status code to 404.
*
* @return $this
*/
public function asNotFound()
{
return $this->withStatus(404);
}
/**
* Determine if the HTTP status code has been set.
*
* @return bool
*/
public function hasStatus()
{
return $this->status !== null;
}
/**
* Get the HTTP status code.
*
* @return int|null
*/
public function status()
{
return $this->status;
}
/**
* Create a deny response object from this exception.
*
* @return \Illuminate\Auth\Access\Response
*/
public function toResponse()
{
return Response::deny($this->message, $this->code)->withStatus($this->status);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Illuminate\Auth\Access\Events;
class GateEvaluated
{
/**
* The authenticatable model.
*
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
public $user;
/**
* The ability being evaluated.
*
* @var string
*/
public $ability;
/**
* The result of the evaluation.
*
* @var bool|null
*/
public $result;
/**
* The arguments given during evaluation.
*
* @var array
*/
public $arguments;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param bool|null $result
* @param array $arguments
* @return void
*/
public function __construct($user, $ability, $result, $arguments)
{
$this->user = $user;
$this->ability = $ability;
$this->result = $result;
$this->arguments = $arguments;
}
}

View File

@ -0,0 +1,898 @@
<?php
namespace Illuminate\Auth\Access;
use Closure;
use Exception;
use Illuminate\Auth\Access\Events\GateEvaluated;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionFunction;
class Gate implements GateContract
{
use HandlesAuthorization;
/**
* The container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The user resolver callable.
*
* @var callable
*/
protected $userResolver;
/**
* All of the defined abilities.
*
* @var array
*/
protected $abilities = [];
/**
* All of the defined policies.
*
* @var array
*/
protected $policies = [];
/**
* All of the registered before callbacks.
*
* @var array
*/
protected $beforeCallbacks = [];
/**
* All of the registered after callbacks.
*
* @var array
*/
protected $afterCallbacks = [];
/**
* All of the defined abilities using class@method notation.
*
* @var array
*/
protected $stringCallbacks = [];
/**
* The default denial response for gates and policies.
*
* @var \Illuminate\Auth\Access\Response|null
*/
protected $defaultDenialResponse;
/**
* The callback to be used to guess policy names.
*
* @var callable|null
*/
protected $guessPolicyNamesUsingCallback;
/**
* Create a new gate instance.
*
* @param \Illuminate\Contracts\Container\Container $container
* @param callable $userResolver
* @param array $abilities
* @param array $policies
* @param array $beforeCallbacks
* @param array $afterCallbacks
* @param callable|null $guessPolicyNamesUsingCallback
* @return void
*/
public function __construct(Container $container,
callable $userResolver,
array $abilities = [],
array $policies = [],
array $beforeCallbacks = [],
array $afterCallbacks = [],
callable $guessPolicyNamesUsingCallback = null)
{
$this->policies = $policies;
$this->container = $container;
$this->abilities = $abilities;
$this->userResolver = $userResolver;
$this->afterCallbacks = $afterCallbacks;
$this->beforeCallbacks = $beforeCallbacks;
$this->guessPolicyNamesUsingCallback = $guessPolicyNamesUsingCallback;
}
/**
* Determine if a given ability has been defined.
*
* @param string|array $ability
* @return bool
*/
public function has($ability)
{
$abilities = is_array($ability) ? $ability : func_get_args();
foreach ($abilities as $ability) {
if (! isset($this->abilities[$ability])) {
return false;
}
}
return true;
}
/**
* Perform an on-demand authorization check. Throw an authorization exception if the condition or callback is false.
*
* @param \Illuminate\Auth\Access\Response|\Closure|bool $condition
* @param string|null $message
* @param string|null $code
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function allowIf($condition, $message = null, $code = null)
{
return $this->authorizeOnDemand($condition, $message, $code, true);
}
/**
* Perform an on-demand authorization check. Throw an authorization exception if the condition or callback is true.
*
* @param \Illuminate\Auth\Access\Response|\Closure|bool $condition
* @param string|null $message
* @param string|null $code
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function denyIf($condition, $message = null, $code = null)
{
return $this->authorizeOnDemand($condition, $message, $code, false);
}
/**
* Authorize a given condition or callback.
*
* @param \Illuminate\Auth\Access\Response|\Closure|bool $condition
* @param string|null $message
* @param string|null $code
* @param bool $allowWhenResponseIs
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
protected function authorizeOnDemand($condition, $message, $code, $allowWhenResponseIs)
{
$user = $this->resolveUser();
if ($condition instanceof Closure) {
$response = $this->canBeCalledWithUser($user, $condition)
? $condition($user)
: new Response(false, $message, $code);
} else {
$response = $condition;
}
return with($response instanceof Response ? $response : new Response(
(bool) $response === $allowWhenResponseIs, $message, $code
))->authorize();
}
/**
* Define a new ability.
*
* @param string $ability
* @param callable|array|string $callback
* @return $this
*
* @throws \InvalidArgumentException
*/
public function define($ability, $callback)
{
if (is_array($callback) && isset($callback[0]) && is_string($callback[0])) {
$callback = $callback[0].'@'.$callback[1];
}
if (is_callable($callback)) {
$this->abilities[$ability] = $callback;
} elseif (is_string($callback)) {
$this->stringCallbacks[$ability] = $callback;
$this->abilities[$ability] = $this->buildAbilityCallback($ability, $callback);
} else {
throw new InvalidArgumentException("Callback must be a callable, callback array, or a 'Class@method' string.");
}
return $this;
}
/**
* Define abilities for a resource.
*
* @param string $name
* @param string $class
* @param array|null $abilities
* @return $this
*/
public function resource($name, $class, array $abilities = null)
{
$abilities = $abilities ?: [
'viewAny' => 'viewAny',
'view' => 'view',
'create' => 'create',
'update' => 'update',
'delete' => 'delete',
];
foreach ($abilities as $ability => $method) {
$this->define($name.'.'.$ability, $class.'@'.$method);
}
return $this;
}
/**
* Create the ability callback for a callback string.
*
* @param string $ability
* @param string $callback
* @return \Closure
*/
protected function buildAbilityCallback($ability, $callback)
{
return function () use ($ability, $callback) {
if (str_contains($callback, '@')) {
[$class, $method] = Str::parseCallback($callback);
} else {
$class = $callback;
}
$policy = $this->resolvePolicy($class);
$arguments = func_get_args();
$user = array_shift($arguments);
$result = $this->callPolicyBefore(
$policy, $user, $ability, $arguments
);
if (! is_null($result)) {
return $result;
}
return isset($method)
? $policy->{$method}(...func_get_args())
: $policy(...func_get_args());
};
}
/**
* Define a policy class for a given class type.
*
* @param string $class
* @param string $policy
* @return $this
*/
public function policy($class, $policy)
{
$this->policies[$class] = $policy;
return $this;
}
/**
* Register a callback to run before all Gate checks.
*
* @param callable $callback
* @return $this
*/
public function before(callable $callback)
{
$this->beforeCallbacks[] = $callback;
return $this;
}
/**
* Register a callback to run after all Gate checks.
*
* @param callable $callback
* @return $this
*/
public function after(callable $callback)
{
$this->afterCallbacks[] = $callback;
return $this;
}
/**
* Determine if all of the given abilities should be granted for the current user.
*
* @param iterable|string $ability
* @param array|mixed $arguments
* @return bool
*/
public function allows($ability, $arguments = [])
{
return $this->check($ability, $arguments);
}
/**
* Determine if any of the given abilities should be denied for the current user.
*
* @param iterable|string $ability
* @param array|mixed $arguments
* @return bool
*/
public function denies($ability, $arguments = [])
{
return ! $this->allows($ability, $arguments);
}
/**
* Determine if all of the given abilities should be granted for the current user.
*
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function check($abilities, $arguments = [])
{
return collect($abilities)->every(
fn ($ability) => $this->inspect($ability, $arguments)->allowed()
);
}
/**
* Determine if any one of the given abilities should be granted for the current user.
*
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function any($abilities, $arguments = [])
{
return collect($abilities)->contains(fn ($ability) => $this->check($ability, $arguments));
}
/**
* Determine if all of the given abilities should be denied for the current user.
*
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function none($abilities, $arguments = [])
{
return ! $this->any($abilities, $arguments);
}
/**
* Determine if the given ability should be granted for the current user.
*
* @param string $ability
* @param array|mixed $arguments
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function authorize($ability, $arguments = [])
{
return $this->inspect($ability, $arguments)->authorize();
}
/**
* Inspect the user for the given ability.
*
* @param string $ability
* @param array|mixed $arguments
* @return \Illuminate\Auth\Access\Response
*/
public function inspect($ability, $arguments = [])
{
try {
$result = $this->raw($ability, $arguments);
if ($result instanceof Response) {
return $result;
}
return $result
? Response::allow()
: ($this->defaultDenialResponse ?? Response::deny());
} catch (AuthorizationException $e) {
return $e->toResponse();
}
}
/**
* Get the raw result from the authorization callback.
*
* @param string $ability
* @param array|mixed $arguments
* @return mixed
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function raw($ability, $arguments = [])
{
$arguments = Arr::wrap($arguments);
$user = $this->resolveUser();
// First we will call the "before" callbacks for the Gate. If any of these give
// back a non-null response, we will immediately return that result in order
// to let the developers override all checks for some authorization cases.
$result = $this->callBeforeCallbacks(
$user, $ability, $arguments
);
if (is_null($result)) {
$result = $this->callAuthCallback($user, $ability, $arguments);
}
// After calling the authorization callback, we will call the "after" callbacks
// that are registered with the Gate, which allows a developer to do logging
// if that is required for this application. Then we'll return the result.
return tap($this->callAfterCallbacks(
$user, $ability, $arguments, $result
), function ($result) use ($user, $ability, $arguments) {
$this->dispatchGateEvaluatedEvent($user, $ability, $arguments, $result);
});
}
/**
* Determine whether the callback/method can be called with the given user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param \Closure|string|array $class
* @param string|null $method
* @return bool
*/
protected function canBeCalledWithUser($user, $class, $method = null)
{
if (! is_null($user)) {
return true;
}
if (! is_null($method)) {
return $this->methodAllowsGuests($class, $method);
}
if (is_array($class)) {
$className = is_string($class[0]) ? $class[0] : get_class($class[0]);
return $this->methodAllowsGuests($className, $class[1]);
}
return $this->callbackAllowsGuests($class);
}
/**
* Determine if the given class method allows guests.
*
* @param string $class
* @param string $method
* @return bool
*/
protected function methodAllowsGuests($class, $method)
{
try {
$reflection = new ReflectionClass($class);
$method = $reflection->getMethod($method);
} catch (Exception) {
return false;
}
if ($method) {
$parameters = $method->getParameters();
return isset($parameters[0]) && $this->parameterAllowsGuests($parameters[0]);
}
return false;
}
/**
* Determine if the callback allows guests.
*
* @param callable $callback
* @return bool
*
* @throws \ReflectionException
*/
protected function callbackAllowsGuests($callback)
{
$parameters = (new ReflectionFunction($callback))->getParameters();
return isset($parameters[0]) && $this->parameterAllowsGuests($parameters[0]);
}
/**
* Determine if the given parameter allows guests.
*
* @param \ReflectionParameter $parameter
* @return bool
*/
protected function parameterAllowsGuests($parameter)
{
return ($parameter->hasType() && $parameter->allowsNull()) ||
($parameter->isDefaultValueAvailable() && is_null($parameter->getDefaultValue()));
}
/**
* Resolve and call the appropriate authorization callback.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param array $arguments
* @return bool
*/
protected function callAuthCallback($user, $ability, array $arguments)
{
$callback = $this->resolveAuthCallback($user, $ability, $arguments);
return $callback($user, ...$arguments);
}
/**
* Call all of the before callbacks and return if a result is given.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param array $arguments
* @return bool|null
*/
protected function callBeforeCallbacks($user, $ability, array $arguments)
{
foreach ($this->beforeCallbacks as $before) {
if (! $this->canBeCalledWithUser($user, $before)) {
continue;
}
if (! is_null($result = $before($user, $ability, $arguments))) {
return $result;
}
}
}
/**
* Call all of the after callbacks with check result.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @param bool $result
* @return bool|null
*/
protected function callAfterCallbacks($user, $ability, array $arguments, $result)
{
foreach ($this->afterCallbacks as $after) {
if (! $this->canBeCalledWithUser($user, $after)) {
continue;
}
$afterResult = $after($user, $ability, $result, $arguments);
$result ??= $afterResult;
}
return $result;
}
/**
* Dispatch a gate evaluation event.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param array $arguments
* @param bool|null $result
* @return void
*/
protected function dispatchGateEvaluatedEvent($user, $ability, array $arguments, $result)
{
if ($this->container->bound(Dispatcher::class)) {
$this->container->make(Dispatcher::class)->dispatch(
new GateEvaluated($user, $ability, $result, $arguments)
);
}
}
/**
* Resolve the callable for the given ability and arguments.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string $ability
* @param array $arguments
* @return callable
*/
protected function resolveAuthCallback($user, $ability, array $arguments)
{
if (isset($arguments[0]) &&
! is_null($policy = $this->getPolicyFor($arguments[0])) &&
$callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)) {
return $callback;
}
if (isset($this->stringCallbacks[$ability])) {
[$class, $method] = Str::parseCallback($this->stringCallbacks[$ability]);
if ($this->canBeCalledWithUser($user, $class, $method ?: '__invoke')) {
return $this->abilities[$ability];
}
}
if (isset($this->abilities[$ability]) &&
$this->canBeCalledWithUser($user, $this->abilities[$ability])) {
return $this->abilities[$ability];
}
return function () {
//
};
}
/**
* Get a policy instance for a given class.
*
* @param object|string $class
* @return mixed
*/
public function getPolicyFor($class)
{
if (is_object($class)) {
$class = get_class($class);
}
if (! is_string($class)) {
return;
}
if (isset($this->policies[$class])) {
return $this->resolvePolicy($this->policies[$class]);
}
foreach ($this->guessPolicyName($class) as $guessedPolicy) {
if (class_exists($guessedPolicy)) {
return $this->resolvePolicy($guessedPolicy);
}
}
foreach ($this->policies as $expected => $policy) {
if (is_subclass_of($class, $expected)) {
return $this->resolvePolicy($policy);
}
}
}
/**
* Guess the policy name for the given class.
*
* @param string $class
* @return array
*/
protected function guessPolicyName($class)
{
if ($this->guessPolicyNamesUsingCallback) {
return Arr::wrap(call_user_func($this->guessPolicyNamesUsingCallback, $class));
}
$classDirname = str_replace('/', '\\', dirname(str_replace('\\', '/', $class)));
$classDirnameSegments = explode('\\', $classDirname);
return Arr::wrap(Collection::times(count($classDirnameSegments), function ($index) use ($class, $classDirnameSegments) {
$classDirname = implode('\\', array_slice($classDirnameSegments, 0, $index));
return $classDirname.'\\Policies\\'.class_basename($class).'Policy';
})->reverse()->values()->first(function ($class) {
return class_exists($class);
}) ?: [$classDirname.'\\Policies\\'.class_basename($class).'Policy']);
}
/**
* Specify a callback to be used to guess policy names.
*
* @param callable $callback
* @return $this
*/
public function guessPolicyNamesUsing(callable $callback)
{
$this->guessPolicyNamesUsingCallback = $callback;
return $this;
}
/**
* Build a policy class instance of the given type.
*
* @param object|string $class
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function resolvePolicy($class)
{
return $this->container->make($class);
}
/**
* Resolve the callback for a policy check.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @param mixed $policy
* @return bool|callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments, $policy)
{
if (! is_callable([$policy, $this->formatAbilityToMethod($ability)])) {
return false;
}
return function () use ($user, $ability, $arguments, $policy) {
// This callback will be responsible for calling the policy's before method and
// running this policy method if necessary. This is used to when objects are
// mapped to policy objects in the user's configurations or on this class.
$result = $this->callPolicyBefore(
$policy, $user, $ability, $arguments
);
// When we receive a non-null result from this before method, we will return it
// as the "final" results. This will allow developers to override the checks
// in this policy to return the result for all rules defined in the class.
if (! is_null($result)) {
return $result;
}
$method = $this->formatAbilityToMethod($ability);
return $this->callPolicyMethod($policy, $method, $user, $arguments);
};
}
/**
* Call the "before" method on the given policy, if applicable.
*
* @param mixed $policy
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @return mixed
*/
protected function callPolicyBefore($policy, $user, $ability, $arguments)
{
if (! method_exists($policy, 'before')) {
return;
}
if ($this->canBeCalledWithUser($user, $policy, 'before')) {
return $policy->before($user, $ability, ...$arguments);
}
}
/**
* Call the appropriate method on the given policy.
*
* @param mixed $policy
* @param string $method
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $arguments
* @return mixed
*/
protected function callPolicyMethod($policy, $method, $user, array $arguments)
{
// If this first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument array
// because this policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
if (! is_callable([$policy, $method])) {
return;
}
if ($this->canBeCalledWithUser($user, $policy, $method)) {
return $policy->{$method}($user, ...$arguments);
}
}
/**
* Format the policy ability into a method name.
*
* @param string $ability
* @return string
*/
protected function formatAbilityToMethod($ability)
{
return str_contains($ability, '-') ? Str::camel($ability) : $ability;
}
/**
* Get a gate instance for the given user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|mixed $user
* @return static
*/
public function forUser($user)
{
$callback = fn () => $user;
return new static(
$this->container, $callback, $this->abilities,
$this->policies, $this->beforeCallbacks, $this->afterCallbacks,
$this->guessPolicyNamesUsingCallback
);
}
/**
* Resolve the user from the user resolver.
*
* @return mixed
*/
protected function resolveUser()
{
return call_user_func($this->userResolver);
}
/**
* Get all of the defined abilities.
*
* @return array
*/
public function abilities()
{
return $this->abilities;
}
/**
* Get all of the defined policies.
*
* @return array
*/
public function policies()
{
return $this->policies;
}
/**
* Set the default denial response for gates and policies.
*
* @param \Illuminate\Auth\Access\Response $response
* @return $this
*/
public function defaultDenialResponse(Response $response)
{
$this->defaultDenialResponse = $response;
return $this;
}
/**
* Set the container instance used by the gate.
*
* @param \Illuminate\Contracts\Container\Container $container
* @return $this
*/
public function setContainer(Container $container)
{
$this->container = $container;
return $this;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Illuminate\Auth\Access;
trait HandlesAuthorization
{
/**
* Create a new access response.
*
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
protected function allow($message = null, $code = null)
{
return Response::allow($message, $code);
}
/**
* Throws an unauthorized exception.
*
* @param string|null $message
* @param mixed|null $code
* @return \Illuminate\Auth\Access\Response
*/
protected function deny($message = null, $code = null)
{
return Response::deny($message, $code);
}
/**
* Deny with a HTTP status code.
*
* @param int $status
* @param string|null $message
* @param int|null $code
* @return \Illuminate\Auth\Access\Response
*/
public function denyWithStatus($status, $message = null, $code = null)
{
return Response::denyWithStatus($status, $message, $code);
}
/**
* Deny with a 404 HTTP status code.
*
* @param string|null $message
* @param int|null $code
* @return \Illuminate\Auth\Access\Response
*/
public function denyAsNotFound($message = null, $code = null)
{
return Response::denyWithStatus(404, $message, $code);
}
}

View File

@ -0,0 +1,215 @@
<?php
namespace Illuminate\Auth\Access;
use Illuminate\Contracts\Support\Arrayable;
class Response implements Arrayable
{
/**
* Indicates whether the response was allowed.
*
* @var bool
*/
protected $allowed;
/**
* The response message.
*
* @var string|null
*/
protected $message;
/**
* The response code.
*
* @var mixed
*/
protected $code;
/**
* The HTTP response status code.
*
* @var int|null
*/
protected $status;
/**
* Create a new response.
*
* @param bool $allowed
* @param string|null $message
* @param mixed $code
* @return void
*/
public function __construct($allowed, $message = '', $code = null)
{
$this->code = $code;
$this->allowed = $allowed;
$this->message = $message;
}
/**
* Create a new "allow" Response.
*
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
public static function allow($message = null, $code = null)
{
return new static(true, $message, $code);
}
/**
* Create a new "deny" Response.
*
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
public static function deny($message = null, $code = null)
{
return new static(false, $message, $code);
}
/**
* Create a new "deny" Response with a HTTP status code.
*
* @param int $status
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
public static function denyWithStatus($status, $message = null, $code = null)
{
return static::deny($message, $code)->withStatus($status);
}
/**
* Create a new "deny" Response with a 404 HTTP status code.
*
* @param string|null $message
* @param mixed $code
* @return \Illuminate\Auth\Access\Response
*/
public static function denyAsNotFound($message = null, $code = null)
{
return static::denyWithStatus(404, $message, $code);
}
/**
* Determine if the response was allowed.
*
* @return bool
*/
public function allowed()
{
return $this->allowed;
}
/**
* Determine if the response was denied.
*
* @return bool
*/
public function denied()
{
return ! $this->allowed();
}
/**
* Get the response message.
*
* @return string|null
*/
public function message()
{
return $this->message;
}
/**
* Get the response code / reason.
*
* @return mixed
*/
public function code()
{
return $this->code;
}
/**
* Throw authorization exception if response was denied.
*
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function authorize()
{
if ($this->denied()) {
throw (new AuthorizationException($this->message(), $this->code()))
->setResponse($this)
->withStatus($this->status);
}
return $this;
}
/**
* Set the HTTP response status code.
*
* @param null|int $status
* @return $this
*/
public function withStatus($status)
{
$this->status = $status;
return $this;
}
/**
* Set the HTTP response status code to 404.
*
* @return $this
*/
public function asNotFound()
{
return $this->withStatus(404);
}
/**
* Get the HTTP status code.
*
* @return int|null
*/
public function status()
{
return $this->status;
}
/**
* Convert the response to an array.
*
* @return array
*/
public function toArray()
{
return [
'allowed' => $this->allowed(),
'message' => $this->message(),
'code' => $this->code(),
];
}
/**
* Get the string representation of the message.
*
* @return string
*/
public function __toString()
{
return (string) $this->message();
}
}

View File

@ -0,0 +1,342 @@
<?php
namespace Illuminate\Auth;
use Closure;
use Illuminate\Contracts\Auth\Factory as FactoryContract;
use InvalidArgumentException;
/**
* @mixin \Illuminate\Contracts\Auth\Guard
* @mixin \Illuminate\Contracts\Auth\StatefulGuard
*/
class AuthManager implements FactoryContract
{
use CreatesUserProviders;
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The registered custom driver creators.
*
* @var array
*/
protected $customCreators = [];
/**
* The array of created "drivers".
*
* @var array
*/
protected $guards = [];
/**
* The user resolver shared by various services.
*
* Determines the default user for Gate, Request, and the Authenticatable contract.
*
* @var \Closure
*/
protected $userResolver;
/**
* Create a new Auth manager instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
$this->userResolver = fn ($guard = null) => $this->guard($guard)->user();
}
/**
* Attempt to get the guard from the local cache.
*
* @param string|null $name
* @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
*/
public function guard($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}
/**
* Resolve the given guard.
*
* @param string $name
* @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
*
* @throws \InvalidArgumentException
*/
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
}
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($name, $config);
}
throw new InvalidArgumentException(
"Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
);
}
/**
* Call a custom driver creator.
*
* @param string $name
* @param array $config
* @return mixed
*/
protected function callCustomCreator($name, array $config)
{
return $this->customCreators[$config['driver']]($this->app, $name, $config);
}
/**
* Create a session based authentication guard.
*
* @param string $name
* @param array $config
* @return \Illuminate\Auth\SessionGuard
*/
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider'] ?? null);
$guard = new SessionGuard(
$name,
$provider,
$this->app['session.store'],
);
// When using the remember me functionality of the authentication services we
// will need to be set the encryption instance of the guard, which allows
// secure, encrypted cookie values to get generated for those cookies.
if (method_exists($guard, 'setCookieJar')) {
$guard->setCookieJar($this->app['cookie']);
}
if (method_exists($guard, 'setDispatcher')) {
$guard->setDispatcher($this->app['events']);
}
if (method_exists($guard, 'setRequest')) {
$guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
}
if (isset($config['remember'])) {
$guard->setRememberDuration($config['remember']);
}
return $guard;
}
/**
* Create a token based authentication guard.
*
* @param string $name
* @param array $config
* @return \Illuminate\Auth\TokenGuard
*/
public function createTokenDriver($name, $config)
{
// The token guard implements a basic API token based guard implementation
// that takes an API token field from the request and matches it to the
// user in the database or another persistence layer where users are.
$guard = new TokenGuard(
$this->createUserProvider($config['provider'] ?? null),
$this->app['request'],
$config['input_key'] ?? 'api_token',
$config['storage_key'] ?? 'api_token',
$config['hash'] ?? false
);
$this->app->refresh('request', $guard, 'setRequest');
return $guard;
}
/**
* Get the guard configuration.
*
* @param string $name
* @return array
*/
protected function getConfig($name)
{
return $this->app['config']["auth.guards.{$name}"];
}
/**
* Get the default authentication driver name.
*
* @return string
*/
public function getDefaultDriver()
{
return $this->app['config']['auth.defaults.guard'];
}
/**
* Set the default guard driver the factory should serve.
*
* @param string $name
* @return void
*/
public function shouldUse($name)
{
$name = $name ?: $this->getDefaultDriver();
$this->setDefaultDriver($name);
$this->userResolver = fn ($name = null) => $this->guard($name)->user();
}
/**
* Set the default authentication driver name.
*
* @param string $name
* @return void
*/
public function setDefaultDriver($name)
{
$this->app['config']['auth.defaults.guard'] = $name;
}
/**
* Register a new callback based request guard.
*
* @param string $driver
* @param callable $callback
* @return $this
*/
public function viaRequest($driver, callable $callback)
{
return $this->extend($driver, function () use ($callback) {
$guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());
$this->app->refresh('request', $guard, 'setRequest');
return $guard;
});
}
/**
* Get the user resolver callback.
*
* @return \Closure
*/
public function userResolver()
{
return $this->userResolver;
}
/**
* Set the callback to be used to resolve users.
*
* @param \Closure $userResolver
* @return $this
*/
public function resolveUsersUsing(Closure $userResolver)
{
$this->userResolver = $userResolver;
return $this;
}
/**
* Register a custom driver creator Closure.
*
* @param string $driver
* @param \Closure $callback
* @return $this
*/
public function extend($driver, Closure $callback)
{
$this->customCreators[$driver] = $callback;
return $this;
}
/**
* Register a custom provider creator Closure.
*
* @param string $name
* @param \Closure $callback
* @return $this
*/
public function provider($name, Closure $callback)
{
$this->customProviderCreators[$name] = $callback;
return $this;
}
/**
* Determines if any guards have already been resolved.
*
* @return bool
*/
public function hasResolvedGuards()
{
return count($this->guards) > 0;
}
/**
* Forget all of the resolved guard instances.
*
* @return $this
*/
public function forgetGuards()
{
$this->guards = [];
return $this;
}
/**
* Set the application instance used by the manager.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return $this
*/
public function setApplication($app)
{
$this->app = $app;
return $this;
}
/**
* Dynamically call the default driver instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->guard()->{$method}(...$parameters);
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace Illuminate\Auth;
use Illuminate\Auth\Access\Gate;
use Illuminate\Auth\Middleware\RequirePassword;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerAuthenticator();
$this->registerUserResolver();
$this->registerAccessGate();
$this->registerRequirePassword();
$this->registerRequestRebindHandler();
$this->registerEventRebindHandler();
}
/**
* Register the authenticator services.
*
* @return void
*/
protected function registerAuthenticator()
{
$this->app->singleton('auth', fn ($app) => new AuthManager($app));
$this->app->singleton('auth.driver', fn ($app) => $app['auth']->guard());
}
/**
* Register a resolver for the authenticated user.
*
* @return void
*/
protected function registerUserResolver()
{
$this->app->bind(AuthenticatableContract::class, fn ($app) => call_user_func($app['auth']->userResolver()));
}
/**
* Register the access gate service.
*
* @return void
*/
protected function registerAccessGate()
{
$this->app->singleton(GateContract::class, function ($app) {
return new Gate($app, fn () => call_user_func($app['auth']->userResolver()));
});
}
/**
* Register a resolver for the authenticated user.
*
* @return void
*/
protected function registerRequirePassword()
{
$this->app->bind(RequirePassword::class, function ($app) {
return new RequirePassword(
$app[ResponseFactory::class],
$app[UrlGenerator::class],
$app['config']->get('auth.password_timeout')
);
});
}
/**
* Handle the re-binding of the request binding.
*
* @return void
*/
protected function registerRequestRebindHandler()
{
$this->app->rebinding('request', function ($app, $request) {
$request->setUserResolver(function ($guard = null) use ($app) {
return call_user_func($app['auth']->userResolver(), $guard);
});
});
}
/**
* Handle the re-binding of the event dispatcher binding.
*
* @return void
*/
protected function registerEventRebindHandler()
{
$this->app->rebinding('events', function ($app, $dispatcher) {
if (! $app->resolved('auth') ||
$app['auth']->hasResolvedGuards() === false) {
return;
}
if (method_exists($guard = $app['auth']->guard(), 'setDispatcher')) {
$guard->setDispatcher($dispatcher);
}
});
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Illuminate\Auth;
trait Authenticatable
{
/**
* The column name of the "remember me" token.
*
* @var string
*/
protected $rememberTokenName = 'remember_token';
/**
* Get the name of the unique identifier for the user.
*
* @return string
*/
public function getAuthIdentifierName()
{
return $this->getKeyName();
}
/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier()
{
return $this->{$this->getAuthIdentifierName()};
}
/**
* Get the unique broadcast identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifierForBroadcasting()
{
return $this->getAuthIdentifier();
}
/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the token value for the "remember me" session.
*
* @return string|null
*/
public function getRememberToken()
{
if (! empty($this->getRememberTokenName())) {
return (string) $this->{$this->getRememberTokenName()};
}
}
/**
* Set the token value for the "remember me" session.
*
* @param string $value
* @return void
*/
public function setRememberToken($value)
{
if (! empty($this->getRememberTokenName())) {
$this->{$this->getRememberTokenName()} = $value;
}
}
/**
* Get the column name for the "remember me" token.
*
* @return string
*/
public function getRememberTokenName()
{
return $this->rememberTokenName;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Illuminate\Auth;
use Exception;
class AuthenticationException extends Exception
{
/**
* All of the guards that were checked.
*
* @var array
*/
protected $guards;
/**
* The path the user should be redirected to.
*
* @var string|null
*/
protected $redirectTo;
/**
* Create a new authentication exception.
*
* @param string $message
* @param array $guards
* @param string|null $redirectTo
* @return void
*/
public function __construct($message = 'Unauthenticated.', array $guards = [], $redirectTo = null)
{
parent::__construct($message);
$this->guards = $guards;
$this->redirectTo = $redirectTo;
}
/**
* Get the guards that were checked.
*
* @return array
*/
public function guards()
{
return $this->guards;
}
/**
* Get the path the user should be redirected to.
*
* @return string|null
*/
public function redirectTo()
{
return $this->redirectTo;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Illuminate\Auth\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'auth:clear-resets')]
class ClearResetsCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'auth:clear-resets {name? : The name of the password broker}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Flush expired password reset tokens';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->laravel['auth.password']->broker($this->argument('name'))->getRepository()->deleteExpired();
$this->components->info('Expired reset tokens cleared successfully.');
}
}

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav mr-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
<!-- Authentication Links -->
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
</main>
</div>
</body>
</html>

View File

@ -0,0 +1,91 @@
<?php
namespace Illuminate\Auth;
use InvalidArgumentException;
trait CreatesUserProviders
{
/**
* The registered custom provider creators.
*
* @var array
*/
protected $customProviderCreators = [];
/**
* Create the user provider implementation for the driver.
*
* @param string|null $provider
* @return \Illuminate\Contracts\Auth\UserProvider|null
*
* @throws \InvalidArgumentException
*/
public function createUserProvider($provider = null)
{
if (is_null($config = $this->getProviderConfiguration($provider))) {
return;
}
if (isset($this->customProviderCreators[$driver = ($config['driver'] ?? null)])) {
return call_user_func(
$this->customProviderCreators[$driver], $this->app, $config
);
}
return match ($driver) {
'database' => $this->createDatabaseProvider($config),
'eloquent' => $this->createEloquentProvider($config),
default => throw new InvalidArgumentException(
"Authentication user provider [{$driver}] is not defined."
),
};
}
/**
* Get the user provider configuration.
*
* @param string|null $provider
* @return array|null
*/
protected function getProviderConfiguration($provider)
{
if ($provider = $provider ?: $this->getDefaultUserProvider()) {
return $this->app['config']['auth.providers.'.$provider];
}
}
/**
* Create an instance of the database user provider.
*
* @param array $config
* @return \Illuminate\Auth\DatabaseUserProvider
*/
protected function createDatabaseProvider($config)
{
$connection = $this->app['db']->connection($config['connection'] ?? null);
return new DatabaseUserProvider($connection, $this->app['hash'], $config['table']);
}
/**
* Create an instance of the Eloquent user provider.
*
* @param array $config
* @return \Illuminate\Auth\EloquentUserProvider
*/
protected function createEloquentProvider($config)
{
return new EloquentUserProvider($this->app['hash'], $config['model']);
}
/**
* Get the default user provider name.
*
* @return string
*/
public function getDefaultUserProvider()
{
return $this->app['config']['auth.defaults.provider'];
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace Illuminate\Auth;
use Closure;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\ConnectionInterface;
class DatabaseUserProvider implements UserProvider
{
/**
* The active database connection.
*
* @var \Illuminate\Database\ConnectionInterface
*/
protected $connection;
/**
* The hasher implementation.
*
* @var \Illuminate\Contracts\Hashing\Hasher
*/
protected $hasher;
/**
* The table containing the users.
*
* @var string
*/
protected $table;
/**
* Create a new database user provider.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
* @param string $table
* @return void
*/
public function __construct(ConnectionInterface $connection, HasherContract $hasher, $table)
{
$this->connection = $connection;
$this->table = $table;
$this->hasher = $hasher;
}
/**
* Retrieve a user by their unique identifier.
*
* @param mixed $identifier
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveById($identifier)
{
$user = $this->connection->table($this->table)->find($identifier);
return $this->getGenericUser($user);
}
/**
* Retrieve a user by their unique identifier and "remember me" token.
*
* @param mixed $identifier
* @param string $token
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByToken($identifier, $token)
{
$user = $this->getGenericUser(
$this->connection->table($this->table)->find($identifier)
);
return $user && $user->getRememberToken() && hash_equals($user->getRememberToken(), $token)
? $user : null;
}
/**
* Update the "remember me" token for the given user in storage.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $token
* @return void
*/
public function updateRememberToken(UserContract $user, $token)
{
$this->connection->table($this->table)
->where($user->getAuthIdentifierName(), $user->getAuthIdentifier())
->update([$user->getRememberTokenName() => $token]);
}
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
$credentials = array_filter(
$credentials,
fn ($key) => ! str_contains($key, 'password'),
ARRAY_FILTER_USE_KEY
);
if (empty($credentials)) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// generic "user" object that will be utilized by the Guard instances.
$query = $this->connection->table($this->table);
foreach ($credentials as $key => $value) {
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} elseif ($value instanceof Closure) {
$value($query);
} else {
$query->where($key, $value);
}
}
// Now we are ready to execute the query to see if we have a user matching
// the given credentials. If not, we will just return null and indicate
// that there are no matching users from the given credential arrays.
$user = $query->first();
return $this->getGenericUser($user);
}
/**
* Get the generic user.
*
* @param mixed $user
* @return \Illuminate\Auth\GenericUser|null
*/
protected function getGenericUser($user)
{
if (! is_null($user)) {
return new GenericUser((array) $user);
}
}
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
return $this->hasher->check(
$credentials['password'], $user->getAuthPassword()
);
}
}

View File

@ -0,0 +1,255 @@
<?php
namespace Illuminate\Auth;
use Closure;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Support\Arrayable;
class EloquentUserProvider implements UserProvider
{
/**
* The hasher implementation.
*
* @var \Illuminate\Contracts\Hashing\Hasher
*/
protected $hasher;
/**
* The Eloquent user model.
*
* @var string
*/
protected $model;
/**
* The callback that may modify the user retrieval queries.
*
* @var (\Closure(\Illuminate\Database\Eloquent\Builder):mixed)|null
*/
protected $queryCallback;
/**
* Create a new database user provider.
*
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
* @param string $model
* @return void
*/
public function __construct(HasherContract $hasher, $model)
{
$this->model = $model;
$this->hasher = $hasher;
}
/**
* Retrieve a user by their unique identifier.
*
* @param mixed $identifier
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveById($identifier)
{
$model = $this->createModel();
return $this->newModelQuery($model)
->where($model->getAuthIdentifierName(), $identifier)
->first();
}
/**
* Retrieve a user by their unique identifier and "remember me" token.
*
* @param mixed $identifier
* @param string $token
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByToken($identifier, $token)
{
$model = $this->createModel();
$retrievedModel = $this->newModelQuery($model)->where(
$model->getAuthIdentifierName(), $identifier
)->first();
if (! $retrievedModel) {
return;
}
$rememberToken = $retrievedModel->getRememberToken();
return $rememberToken && hash_equals($rememberToken, $token) ? $retrievedModel : null;
}
/**
* Update the "remember me" token for the given user in storage.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $token
* @return void
*/
public function updateRememberToken(UserContract $user, $token)
{
$user->setRememberToken($token);
$timestamps = $user->timestamps;
$user->timestamps = false;
$user->save();
$user->timestamps = $timestamps;
}
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
$credentials = array_filter(
$credentials,
fn ($key) => ! str_contains($key, 'password'),
ARRAY_FILTER_USE_KEY
);
if (empty($credentials)) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->newModelQuery();
foreach ($credentials as $key => $value) {
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} elseif ($value instanceof Closure) {
$value($query);
} else {
$query->where($key, $value);
}
}
return $query->first();
}
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
if (is_null($plain = $credentials['password'])) {
return false;
}
return $this->hasher->check($plain, $user->getAuthPassword());
}
/**
* Get a new query builder for the model instance.
*
* @param \Illuminate\Database\Eloquent\Model|null $model
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function newModelQuery($model = null)
{
$query = is_null($model)
? $this->createModel()->newQuery()
: $model->newQuery();
with($query, $this->queryCallback);
return $query;
}
/**
* Create a new instance of the model.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function createModel()
{
$class = '\\'.ltrim($this->model, '\\');
return new $class;
}
/**
* Gets the hasher implementation.
*
* @return \Illuminate\Contracts\Hashing\Hasher
*/
public function getHasher()
{
return $this->hasher;
}
/**
* Sets the hasher implementation.
*
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
* @return $this
*/
public function setHasher(HasherContract $hasher)
{
$this->hasher = $hasher;
return $this;
}
/**
* Gets the name of the Eloquent user model.
*
* @return string
*/
public function getModel()
{
return $this->model;
}
/**
* Sets the name of the Eloquent user model.
*
* @param string $model
* @return $this
*/
public function setModel($model)
{
$this->model = $model;
return $this;
}
/**
* Get the callback that modifies the query before retrieving users.
*
* @return \Closure|null
*/
public function getQueryCallback()
{
return $this->queryCallback;
}
/**
* Sets the callback to modify the query before retrieving users.
*
* @param (\Closure(\Illuminate\Database\Eloquent\Builder):mixed)|null $queryCallback
* @return $this
*/
public function withQuery($queryCallback = null)
{
$this->queryCallback = $queryCallback;
return $this;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Illuminate\Auth\Events;
class Attempting
{
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The credentials for the user.
*
* @var array
*/
public $credentials;
/**
* Indicates if the user should be "remembered".
*
* @var bool
*/
public $remember;
/**
* Create a new event instance.
*
* @param string $guard
* @param array $credentials
* @param bool $remember
* @return void
*/
public function __construct($guard, $credentials, $remember)
{
$this->guard = $guard;
$this->remember = $remember;
$this->credentials = $credentials;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Authenticated
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class CurrentDeviceLogout
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Illuminate\Auth\Events;
class Failed
{
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The user the attempter was trying to authenticate as.
*
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
public $user;
/**
* The credentials provided by the attempter.
*
* @var array
*/
public $credentials;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
* @return void
*/
public function __construct($guard, $user, $credentials)
{
$this->user = $user;
$this->guard = $guard;
$this->credentials = $credentials;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Http\Request;
class Lockout
{
/**
* The throttled request.
*
* @var \Illuminate\Http\Request
*/
public $request;
/**
* Create a new event instance.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
public function __construct(Request $request)
{
$this->request = $request;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Login
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Indicates if the user should be "remembered".
*
* @var bool
*/
public $remember;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @return void
*/
public function __construct($guard, $user, $remember)
{
$this->user = $user;
$this->guard = $guard;
$this->remember = $remember;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Logout
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class OtherDeviceLogout
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class PasswordReset
{
use SerializesModels;
/**
* The user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Registered
{
use SerializesModels;
/**
* The authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Validated
{
use SerializesModels;
/**
* The authentication guard name.
*
* @var string
*/
public $guard;
/**
* The user retrieved and validated from the User Provider.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
public $user;
/**
* Create a new event instance.
*
* @param string $guard
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
public function __construct($guard, $user)
{
$this->user = $user;
$this->guard = $guard;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Illuminate\Auth\Events;
use Illuminate\Queue\SerializesModels;
class Verified
{
use SerializesModels;
/**
* The verified user.
*
* @var \Illuminate\Contracts\Auth\MustVerifyEmail
*/
public $user;
/**
* Create a new event instance.
*
* @param \Illuminate\Contracts\Auth\MustVerifyEmail $user
* @return void
*/
public function __construct($user)
{
$this->user = $user;
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace Illuminate\Auth;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
class GenericUser implements UserContract
{
/**
* All of the user's attributes.
*
* @var array
*/
protected $attributes;
/**
* Create a new generic User object.
*
* @param array $attributes
* @return void
*/
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
/**
* Get the name of the unique identifier for the user.
*
* @return string
*/
public function getAuthIdentifierName()
{
return 'id';
}
/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier()
{
return $this->attributes[$this->getAuthIdentifierName()];
}
/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword()
{
return $this->attributes['password'];
}
/**
* Get the "remember me" token value.
*
* @return string
*/
public function getRememberToken()
{
return $this->attributes[$this->getRememberTokenName()];
}
/**
* Set the "remember me" token value.
*
* @param string $value
* @return void
*/
public function setRememberToken($value)
{
$this->attributes[$this->getRememberTokenName()] = $value;
}
/**
* Get the column name for the "remember me" token.
*
* @return string
*/
public function getRememberTokenName()
{
return 'remember_token';
}
/**
* Dynamically access the user's attributes.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->attributes[$key];
}
/**
* Dynamically set an attribute on the user.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function __set($key, $value)
{
$this->attributes[$key] = $value;
}
/**
* Dynamically check if a value is set on the user.
*
* @param string $key
* @return bool
*/
public function __isset($key)
{
return isset($this->attributes[$key]);
}
/**
* Dynamically unset a value on the user.
*
* @param string $key
* @return void
*/
public function __unset($key)
{
unset($this->attributes[$key]);
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace Illuminate\Auth;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\UserProvider;
/**
* These methods are typically the same across all guards.
*/
trait GuardHelpers
{
/**
* The currently authenticated user.
*
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
protected $user;
/**
* The user provider implementation.
*
* @var \Illuminate\Contracts\Auth\UserProvider
*/
protected $provider;
/**
* Determine if the current user is authenticated. If not, throw an exception.
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function authenticate()
{
return $this->user() ?? throw new AuthenticationException;
}
/**
* Determine if the guard has a user instance.
*
* @return bool
*/
public function hasUser()
{
return ! is_null($this->user);
}
/**
* Determine if the current user is authenticated.
*
* @return bool
*/
public function check()
{
return ! is_null($this->user());
}
/**
* Determine if the current user is a guest.
*
* @return bool
*/
public function guest()
{
return ! $this->check();
}
/**
* Get the ID for the currently authenticated user.
*
* @return int|string|null
*/
public function id()
{
if ($this->user()) {
return $this->user()->getAuthIdentifier();
}
}
/**
* Set the current user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return $this
*/
public function setUser(AuthenticatableContract $user)
{
$this->user = $user;
return $this;
}
/**
* Forget the current user.
*
* @return $this
*/
public function forgetUser()
{
$this->user = null;
return $this;
}
/**
* Get the user provider used by the guard.
*
* @return \Illuminate\Contracts\Auth\UserProvider
*/
public function getProvider()
{
return $this->provider;
}
/**
* Set the user provider used by the guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @return void
*/
public function setProvider(UserProvider $provider)
{
$this->provider = $provider;
}
}

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,22 @@
<?php
namespace Illuminate\Auth\Listeners;
use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Auth\MustVerifyEmail;
class SendEmailVerificationNotification
{
/**
* Handle the event.
*
* @param \Illuminate\Auth\Events\Registered $event
* @return void
*/
public function handle(Registered $event)
{
if ($event->user instanceof MustVerifyEmail && ! $event->user->hasVerifiedEmail()) {
$event->user->sendEmailVerificationNotification();
}
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests;
use Illuminate\Http\Request;
class Authenticate implements AuthenticatesRequests
{
/**
* The authentication factory instance.
*
* @var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
* @return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Specify the guards for the middleware.
*
* @param string $guard
* @param string $others
* @return string
*/
public static function using($guard, ...$others)
{
return static::class.':'.implode(',', [$guard, ...$others]);
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string[] ...$guards
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($request, $guards);
return $next($request);
}
/**
* Determine if the user is logged in to any of the given guards.
*
* @param \Illuminate\Http\Request $request
* @param array $guards
* @return void
*
* @throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate($request, array $guards)
{
if (empty($guards)) {
$guards = [null];
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
$this->unauthenticated($request, $guards);
}
/**
* Handle an unauthenticated user.
*
* @param \Illuminate\Http\Request $request
* @param array $guards
* @return void
*
* @throws \Illuminate\Auth\AuthenticationException
*/
protected function unauthenticated($request, array $guards)
{
throw new AuthenticationException(
'Unauthenticated.', $guards, $this->redirectTo($request)
);
}
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo(Request $request)
{
//
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
class AuthenticateWithBasicAuth
{
/**
* The guard factory instance.
*
* @var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
* @return void
*/
public function __construct(AuthFactory $auth)
{
$this->auth = $auth;
}
/**
* Specify the guard and field for the middleware.
*
* @param string|null $guard
* @param string|null $field
* @return string
*
* @named-arguments-supported
*/
public static function using($guard = null, $field = null)
{
return static::class.':'.implode(',', func_get_args());
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @param string|null $field
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
public function handle($request, Closure $next, $guard = null, $field = null)
{
$this->auth->guard($guard)->basic($field ?: 'email');
return $next($request);
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Access\Gate;
use Illuminate\Database\Eloquent\Model;
class Authorize
{
/**
* The gate instance.
*
* @var \Illuminate\Contracts\Auth\Access\Gate
*/
protected $gate;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function __construct(Gate $gate)
{
$this->gate = $gate;
}
/**
* Specify the ability and models for the middleware.
*
* @param string $ability
* @param string ...$models
* @return string
*/
public static function using($ability, ...$models)
{
return static::class.':'.implode(',', [$ability, ...$models]);
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $ability
* @param array|null ...$models
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function handle($request, Closure $next, $ability, ...$models)
{
$this->gate->authorize($ability, $this->getGateArguments($request, $models));
return $next($request);
}
/**
* Get the arguments parameter for the gate.
*
* @param \Illuminate\Http\Request $request
* @param array|null $models
* @return \Illuminate\Database\Eloquent\Model|array|string
*/
protected function getGateArguments($request, $models)
{
if (is_null($models)) {
return [];
}
return collect($models)->map(function ($model) use ($request) {
return $model instanceof Model ? $model : $this->getModel($request, $model);
})->all();
}
/**
* Get the model to authorize.
*
* @param \Illuminate\Http\Request $request
* @param string $model
* @return \Illuminate\Database\Eloquent\Model|string
*/
protected function getModel($request, $model)
{
if ($this->isClassName($model)) {
return trim($model);
}
return $request->route($model, null) ??
((preg_match("/^['\"](.*)['\"]$/", trim($model), $matches)) ? $matches[1] : null);
}
/**
* Checks if the given string looks like a fully qualified class name.
*
* @param string $value
* @return bool
*/
protected function isClassName($value)
{
return str_contains($value, '\\');
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\URL;
class EnsureEmailIsVerified
{
/**
* Specify the redirect route for the middleware.
*
* @param string $route
* @return string
*/
public static function redirectTo($route)
{
return static::class.':'.$route;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $redirectToRoute
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse|null
*/
public function handle($request, Closure $next, $redirectToRoute = null)
{
if (! $request->user() ||
($request->user() instanceof MustVerifyEmail &&
! $request->user()->hasVerifiedEmail())) {
return $request->expectsJson()
? abort(403, 'Your email address is not verified.')
: Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice'));
}
return $next($request);
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Contracts\Routing\UrlGenerator;
class RequirePassword
{
/**
* The response factory instance.
*
* @var \Illuminate\Contracts\Routing\ResponseFactory
*/
protected $responseFactory;
/**
* The URL generator instance.
*
* @var \Illuminate\Contracts\Routing\UrlGenerator
*/
protected $urlGenerator;
/**
* The password timeout.
*
* @var int
*/
protected $passwordTimeout;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Routing\ResponseFactory $responseFactory
* @param \Illuminate\Contracts\Routing\UrlGenerator $urlGenerator
* @param int|null $passwordTimeout
* @return void
*/
public function __construct(ResponseFactory $responseFactory, UrlGenerator $urlGenerator, $passwordTimeout = null)
{
$this->responseFactory = $responseFactory;
$this->urlGenerator = $urlGenerator;
$this->passwordTimeout = $passwordTimeout ?: 10800;
}
/**
* Specify the redirect route and timeout for the middleware.
*
* @param string|null $redirectToRoute
* @param string|int|null $passwordTimeoutSeconds
* @return string
*
* @named-arguments-supported
*/
public static function using($redirectToRoute = null, $passwordTimeoutSeconds = null)
{
return static::class.':'.implode(',', func_get_args());
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $redirectToRoute
* @param string|int|null $passwordTimeoutSeconds
* @return mixed
*/
public function handle($request, Closure $next, $redirectToRoute = null, $passwordTimeoutSeconds = null)
{
if ($this->shouldConfirmPassword($request, $passwordTimeoutSeconds)) {
if ($request->expectsJson()) {
return $this->responseFactory->json([
'message' => 'Password confirmation required.',
], 423);
}
return $this->responseFactory->redirectGuest(
$this->urlGenerator->route($redirectToRoute ?: 'password.confirm')
);
}
return $next($request);
}
/**
* Determine if the confirmation timeout has expired.
*
* @param \Illuminate\Http\Request $request
* @param int|null $passwordTimeoutSeconds
* @return bool
*/
protected function shouldConfirmPassword($request, $passwordTimeoutSeconds = null)
{
$confirmedAt = time() - $request->session()->get('auth.password_confirmed_at', 0);
return $confirmedAt > ($passwordTimeoutSeconds ?? $this->passwordTimeout);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Illuminate\Auth;
use Illuminate\Auth\Notifications\VerifyEmail;
trait MustVerifyEmail
{
/**
* Determine if the user has verified their email address.
*
* @return bool
*/
public function hasVerifiedEmail()
{
return ! is_null($this->email_verified_at);
}
/**
* Mark the given user's email as verified.
*
* @return bool
*/
public function markEmailAsVerified()
{
return $this->forceFill([
'email_verified_at' => $this->freshTimestamp(),
])->save();
}
/**
* Send the email verification notification.
*
* @return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmail);
}
/**
* Get the email address that should be used for verification.
*
* @return string
*/
public function getEmailForVerification()
{
return $this->email;
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace Illuminate\Auth\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;
class ResetPassword extends Notification
{
/**
* The password reset token.
*
* @var string
*/
public $token;
/**
* The callback that should be used to create the reset password URL.
*
* @var (\Closure(mixed, string): string)|null
*/
public static $createUrlCallback;
/**
* The callback that should be used to build the mail message.
*
* @var (\Closure(mixed, string): \Illuminate\Notifications\Messages\MailMessage|\Illuminate\Contracts\Mail\Mailable)|null
*/
public static $toMailCallback;
/**
* Create a notification instance.
*
* @param string $token
* @return void
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Get the notification's channels.
*
* @param mixed $notifiable
* @return array|string
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this->token);
}
return $this->buildMailMessage($this->resetUrl($notifiable));
}
/**
* Get the reset password notification mail message for the given URL.
*
* @param string $url
* @return \Illuminate\Notifications\Messages\MailMessage
*/
protected function buildMailMessage($url)
{
return (new MailMessage)
->subject(Lang::get('Reset Password Notification'))
->line(Lang::get('You are receiving this email because we received a password reset request for your account.'))
->action(Lang::get('Reset Password'), $url)
->line(Lang::get('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]))
->line(Lang::get('If you did not request a password reset, no further action is required.'));
}
/**
* Get the reset URL for the given notifiable.
*
* @param mixed $notifiable
* @return string
*/
protected function resetUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable, $this->token);
}
return url(route('password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
}
/**
* Set a callback that should be used when creating the reset password button URL.
*
* @param \Closure(mixed, string): string $callback
* @return void
*/
public static function createUrlUsing($callback)
{
static::$createUrlCallback = $callback;
}
/**
* Set a callback that should be used when building the notification mail message.
*
* @param \Closure(mixed, string): (\Illuminate\Notifications\Messages\MailMessage|\Illuminate\Contracts\Mail\Mailable) $callback
* @return void
*/
public static function toMailUsing($callback)
{
static::$toMailCallback = $callback;
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace Illuminate\Auth\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\URL;
class VerifyEmail extends Notification
{
/**
* The callback that should be used to create the verify email URL.
*
* @var \Closure|null
*/
public static $createUrlCallback;
/**
* The callback that should be used to build the mail message.
*
* @var \Closure|null
*/
public static $toMailCallback;
/**
* Get the notification's channels.
*
* @param mixed $notifiable
* @return array|string
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$verificationUrl = $this->verificationUrl($notifiable);
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
}
return $this->buildMailMessage($verificationUrl);
}
/**
* Get the verify email notification mail message for the given URL.
*
* @param string $url
* @return \Illuminate\Notifications\Messages\MailMessage
*/
protected function buildMailMessage($url)
{
return (new MailMessage)
->subject(Lang::get('Verify Email Address'))
->line(Lang::get('Please click the button below to verify your email address.'))
->action(Lang::get('Verify Email Address'), $url)
->line(Lang::get('If you did not create an account, no further action is required.'));
}
/**
* Get the verification URL for the given notifiable.
*
* @param mixed $notifiable
* @return string
*/
protected function verificationUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable);
}
return URL::temporarySignedRoute(
'verification.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
}
/**
* Set a callback that should be used when creating the email verification URL.
*
* @param \Closure $callback
* @return void
*/
public static function createUrlUsing($callback)
{
static::$createUrlCallback = $callback;
}
/**
* Set a callback that should be used when building the notification mail message.
*
* @param \Closure $callback
* @return void
*/
public static function toMailUsing($callback)
{
static::$toMailCallback = $callback;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Illuminate\Auth\Passwords;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
trait CanResetPassword
{
/**
* Get the e-mail address where password reset links are sent.
*
* @return string
*/
public function getEmailForPasswordReset()
{
return $this->email;
}
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
}

View File

@ -0,0 +1,246 @@
<?php
namespace Illuminate\Auth\Passwords;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
class DatabaseTokenRepository implements TokenRepositoryInterface
{
/**
* The database connection instance.
*
* @var \Illuminate\Database\ConnectionInterface
*/
protected $connection;
/**
* The Hasher implementation.
*
* @var \Illuminate\Contracts\Hashing\Hasher
*/
protected $hasher;
/**
* The token database table.
*
* @var string
*/
protected $table;
/**
* The hashing key.
*
* @var string
*/
protected $hashKey;
/**
* The number of seconds a token should last.
*
* @var int
*/
protected $expires;
/**
* Minimum number of seconds before re-redefining the token.
*
* @var int
*/
protected $throttle;
/**
* Create a new token repository instance.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
* @param string $table
* @param string $hashKey
* @param int $expires
* @param int $throttle
* @return void
*/
public function __construct(ConnectionInterface $connection, HasherContract $hasher,
$table, $hashKey, $expires = 60,
$throttle = 60)
{
$this->table = $table;
$this->hasher = $hasher;
$this->hashKey = $hashKey;
$this->expires = $expires * 60;
$this->connection = $connection;
$this->throttle = $throttle;
}
/**
* Create a new token record.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return string
*/
public function create(CanResetPasswordContract $user)
{
$email = $user->getEmailForPasswordReset();
$this->deleteExisting($user);
// We will create a new, random token for the user so that we can e-mail them
// a safe link to the password reset form. Then we will insert a record in
// the database so that we can verify the token within the actual reset.
$token = $this->createNewToken();
$this->getTable()->insert($this->getPayload($email, $token));
return $token;
}
/**
* Delete all existing reset tokens from the database.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return int
*/
protected function deleteExisting(CanResetPasswordContract $user)
{
return $this->getTable()->where('email', $user->getEmailForPasswordReset())->delete();
}
/**
* Build the record payload for the table.
*
* @param string $email
* @param string $token
* @return array
*/
protected function getPayload($email, $token)
{
return ['email' => $email, 'token' => $this->hasher->make($token), 'created_at' => new Carbon];
}
/**
* Determine if a token record exists and is valid.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $token
* @return bool
*/
public function exists(CanResetPasswordContract $user, $token)
{
$record = (array) $this->getTable()->where(
'email', $user->getEmailForPasswordReset()
)->first();
return $record &&
! $this->tokenExpired($record['created_at']) &&
$this->hasher->check($token, $record['token']);
}
/**
* Determine if the token has expired.
*
* @param string $createdAt
* @return bool
*/
protected function tokenExpired($createdAt)
{
return Carbon::parse($createdAt)->addSeconds($this->expires)->isPast();
}
/**
* Determine if the given user recently created a password reset token.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return bool
*/
public function recentlyCreatedToken(CanResetPasswordContract $user)
{
$record = (array) $this->getTable()->where(
'email', $user->getEmailForPasswordReset()
)->first();
return $record && $this->tokenRecentlyCreated($record['created_at']);
}
/**
* Determine if the token was recently created.
*
* @param string $createdAt
* @return bool
*/
protected function tokenRecentlyCreated($createdAt)
{
if ($this->throttle <= 0) {
return false;
}
return Carbon::parse($createdAt)->addSeconds(
$this->throttle
)->isFuture();
}
/**
* Delete a token record by user.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return void
*/
public function delete(CanResetPasswordContract $user)
{
$this->deleteExisting($user);
}
/**
* Delete expired tokens.
*
* @return void
*/
public function deleteExpired()
{
$expiredAt = Carbon::now()->subSeconds($this->expires);
$this->getTable()->where('created_at', '<', $expiredAt)->delete();
}
/**
* Create a new token for the user.
*
* @return string
*/
public function createNewToken()
{
return hash_hmac('sha256', Str::random(40), $this->hashKey);
}
/**
* Get the database connection instance.
*
* @return \Illuminate\Database\ConnectionInterface
*/
public function getConnection()
{
return $this->connection;
}
/**
* Begin a new database query against the table.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function getTable()
{
return $this->connection->table($this->table);
}
/**
* Get the hasher instance.
*
* @return \Illuminate\Contracts\Hashing\Hasher
*/
public function getHasher()
{
return $this->hasher;
}
}

View File

@ -0,0 +1,190 @@
<?php
namespace Illuminate\Auth\Passwords;
use Closure;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Arr;
use UnexpectedValueException;
class PasswordBroker implements PasswordBrokerContract
{
/**
* The password token repository.
*
* @var \Illuminate\Auth\Passwords\TokenRepositoryInterface
*/
protected $tokens;
/**
* The user provider implementation.
*
* @var \Illuminate\Contracts\Auth\UserProvider
*/
protected $users;
/**
* Create a new password broker instance.
*
* @param \Illuminate\Auth\Passwords\TokenRepositoryInterface $tokens
* @param \Illuminate\Contracts\Auth\UserProvider $users
* @return void
*/
public function __construct(TokenRepositoryInterface $tokens, UserProvider $users)
{
$this->users = $users;
$this->tokens = $tokens;
}
/**
* Send a password reset link to a user.
*
* @param array $credentials
* @param \Closure|null $callback
* @return string
*/
public function sendResetLink(array $credentials, Closure $callback = null)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
if (is_null($user)) {
return static::INVALID_USER;
}
if ($this->tokens->recentlyCreatedToken($user)) {
return static::RESET_THROTTLED;
}
$token = $this->tokens->create($user);
if ($callback) {
return $callback($user, $token) ?? static::RESET_LINK_SENT;
}
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
$user->sendPasswordResetNotification($token);
return static::RESET_LINK_SENT;
}
/**
* Reset the password for the given token.
*
* @param array $credentials
* @param \Closure $callback
* @return mixed
*/
public function reset(array $credentials, Closure $callback)
{
$user = $this->validateReset($credentials);
// If the responses from the validate method is not a user instance, we will
// assume that it is a redirect and simply return it from this method and
// the user is properly redirected having an error message on the post.
if (! $user instanceof CanResetPasswordContract) {
return $user;
}
$password = $credentials['password'];
// Once the reset has been validated, we'll call the given callback with the
// new password. This gives the user an opportunity to store the password
// in their persistent storage. Then we'll delete the token and return.
$callback($user, $password);
$this->tokens->delete($user);
return static::PASSWORD_RESET;
}
/**
* Validate a password reset for the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\CanResetPassword|string
*/
protected function validateReset(array $credentials)
{
if (is_null($user = $this->getUser($credentials))) {
return static::INVALID_USER;
}
if (! $this->tokens->exists($user, $credentials['token'])) {
return static::INVALID_TOKEN;
}
return $user;
}
/**
* Get the user for the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\CanResetPassword|null
*
* @throws \UnexpectedValueException
*/
public function getUser(array $credentials)
{
$credentials = Arr::except($credentials, ['token']);
$user = $this->users->retrieveByCredentials($credentials);
if ($user && ! $user instanceof CanResetPasswordContract) {
throw new UnexpectedValueException('User must implement CanResetPassword interface.');
}
return $user;
}
/**
* Create a new password reset token for the given user.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return string
*/
public function createToken(CanResetPasswordContract $user)
{
return $this->tokens->create($user);
}
/**
* Delete password reset tokens of the given user.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return void
*/
public function deleteToken(CanResetPasswordContract $user)
{
$this->tokens->delete($user);
}
/**
* Validate the given password reset token.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $token
* @return bool
*/
public function tokenExists(CanResetPasswordContract $user, $token)
{
return $this->tokens->exists($user, $token);
}
/**
* Get the password reset token repository implementation.
*
* @return \Illuminate\Auth\Passwords\TokenRepositoryInterface
*/
public function getRepository()
{
return $this->tokens;
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace Illuminate\Auth\Passwords;
use Illuminate\Contracts\Auth\PasswordBrokerFactory as FactoryContract;
use InvalidArgumentException;
/**
* @mixin \Illuminate\Contracts\Auth\PasswordBroker
*/
class PasswordBrokerManager implements FactoryContract
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The array of created "drivers".
*
* @var array
*/
protected $brokers = [];
/**
* Create a new PasswordBroker manager instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
/**
* Attempt to get the broker from the local cache.
*
* @param string|null $name
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->brokers[$name] ?? ($this->brokers[$name] = $this->resolve($name));
}
/**
* Resolve the given broker.
*
* @param string $name
* @return \Illuminate\Contracts\Auth\PasswordBroker
*
* @throws \InvalidArgumentException
*/
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Password resetter [{$name}] is not defined.");
}
// The password broker uses a token repository to validate tokens and send user
// password e-mails, as well as validating that password reset process as an
// aggregate service of sorts providing a convenient interface for resets.
return new PasswordBroker(
$this->createTokenRepository($config),
$this->app['auth']->createUserProvider($config['provider'] ?? null)
);
}
/**
* Create a token repository instance based on the given configuration.
*
* @param array $config
* @return \Illuminate\Auth\Passwords\TokenRepositoryInterface
*/
protected function createTokenRepository(array $config)
{
$key = $this->app['config']['app.key'];
if (str_starts_with($key, 'base64:')) {
$key = base64_decode(substr($key, 7));
}
$connection = $config['connection'] ?? null;
return new DatabaseTokenRepository(
$this->app['db']->connection($connection),
$this->app['hash'],
$config['table'],
$key,
$config['expire'],
$config['throttle'] ?? 0
);
}
/**
* Get the password broker configuration.
*
* @param string $name
* @return array|null
*/
protected function getConfig($name)
{
return $this->app['config']["auth.passwords.{$name}"];
}
/**
* Get the default password broker name.
*
* @return string
*/
public function getDefaultDriver()
{
return $this->app['config']['auth.defaults.passwords'];
}
/**
* Set the default password broker name.
*
* @param string $name
* @return void
*/
public function setDefaultDriver($name)
{
$this->app['config']['auth.defaults.passwords'] = $name;
}
/**
* Dynamically call the default driver instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->broker()->{$method}(...$parameters);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Illuminate\Auth\Passwords;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class PasswordResetServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerPasswordBroker();
}
/**
* Register the password broker instance.
*
* @return void
*/
protected function registerPasswordBroker()
{
$this->app->singleton('auth.password', function ($app) {
return new PasswordBrokerManager($app);
});
$this->app->bind('auth.password.broker', function ($app) {
return $app->make('auth.password')->broker();
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['auth.password', 'auth.password.broker'];
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Illuminate\Auth\Passwords;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
interface TokenRepositoryInterface
{
/**
* Create a new token.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return string
*/
public function create(CanResetPasswordContract $user);
/**
* Determine if a token record exists and is valid.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $token
* @return bool
*/
public function exists(CanResetPasswordContract $user, $token);
/**
* Determine if the given user recently created a password reset token.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return bool
*/
public function recentlyCreatedToken(CanResetPasswordContract $user);
/**
* Delete a token record.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return void
*/
public function delete(CanResetPasswordContract $user);
/**
* Delete expired tokens.
*
* @return void
*/
public function deleteExpired();
}

View File

@ -0,0 +1,96 @@
<?php
namespace Illuminate\Auth;
class Recaller
{
/**
* The "recaller" / "remember me" cookie string.
*
* @var string
*/
protected $recaller;
/**
* Create a new recaller instance.
*
* @param string $recaller
* @return void
*/
public function __construct($recaller)
{
$this->recaller = @unserialize($recaller, ['allowed_classes' => false]) ?: $recaller;
}
/**
* Get the user ID from the recaller.
*
* @return string
*/
public function id()
{
return explode('|', $this->recaller, 3)[0];
}
/**
* Get the "remember token" token from the recaller.
*
* @return string
*/
public function token()
{
return explode('|', $this->recaller, 3)[1];
}
/**
* Get the password from the recaller.
*
* @return string
*/
public function hash()
{
return explode('|', $this->recaller, 4)[2];
}
/**
* Determine if the recaller is valid.
*
* @return bool
*/
public function valid()
{
return $this->properString() && $this->hasAllSegments();
}
/**
* Determine if the recaller is an invalid string.
*
* @return bool
*/
protected function properString()
{
return is_string($this->recaller) && str_contains($this->recaller, '|');
}
/**
* Determine if the recaller has all segments.
*
* @return bool
*/
protected function hasAllSegments()
{
$segments = explode('|', $this->recaller);
return count($segments) >= 3 && trim($segments[0]) !== '' && trim($segments[1]) !== '';
}
/**
* Get the recaller's segments.
*
* @return array
*/
public function segments()
{
return explode('|', $this->recaller);
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace Illuminate\Auth;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Traits\Macroable;
class RequestGuard implements Guard
{
use GuardHelpers, Macroable;
/**
* The guard callback.
*
* @var callable
*/
protected $callback;
/**
* The request instance.
*
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* Create a new authentication guard.
*
* @param callable $callback
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Auth\UserProvider|null $provider
* @return void
*/
public function __construct(callable $callback, Request $request, UserProvider $provider = null)
{
$this->request = $request;
$this->callback = $callback;
$this->provider = $provider;
}
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
return $this->user = call_user_func(
$this->callback, $this->request, $this->getProvider()
);
}
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = [])
{
return ! is_null((new static(
$this->callback, $credentials['request'], $this->getProvider()
))->user());
}
/**
* Set the current request instance.
*
* @param \Illuminate\Http\Request $request
* @return $this
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
}

View File

@ -0,0 +1,962 @@
<?php
namespace Illuminate\Auth;
use Illuminate\Auth\Events\Attempting;
use Illuminate\Auth\Events\Authenticated;
use Illuminate\Auth\Events\CurrentDeviceLogout;
use Illuminate\Auth\Events\Failed;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Auth\Events\OtherDeviceLogout;
use Illuminate\Auth\Events\Validated;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Auth\SupportsBasicAuth;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Cookie\QueueingFactory as CookieJar;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Session\Session;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Support\Timebox;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
use GuardHelpers, Macroable;
/**
* The name of the guard. Typically "web".
*
* Corresponds to guard name in authentication configuration.
*
* @var string
*/
public readonly string $name;
/**
* The user we last attempted to retrieve.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
protected $lastAttempted;
/**
* Indicates if the user was authenticated via a recaller cookie.
*
* @var bool
*/
protected $viaRemember = false;
/**
* The number of minutes that the "remember me" cookie should be valid for.
*
* @var int
*/
protected $rememberDuration = 576000;
/**
* The session used by the guard.
*
* @var \Illuminate\Contracts\Session\Session
*/
protected $session;
/**
* The Illuminate cookie creator service.
*
* @var \Illuminate\Contracts\Cookie\QueueingFactory
*/
protected $cookie;
/**
* The request instance.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The event dispatcher instance.
*
* @var \Illuminate\Contracts\Events\Dispatcher
*/
protected $events;
/**
* The timebox instance.
*
* @var \Illuminate\Support\Timebox
*/
protected $timebox;
/**
* Indicates if the logout method has been called.
*
* @var bool
*/
protected $loggedOut = false;
/**
* Indicates if a token user retrieval has been attempted.
*
* @var bool
*/
protected $recallAttempted = false;
/**
* Create a new authentication guard.
*
* @param string $name
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Illuminate\Contracts\Session\Session $session
* @param \Symfony\Component\HttpFoundation\Request|null $request
* @param \Illuminate\Support\Timebox|null $timebox
* @return void
*/
public function __construct($name,
UserProvider $provider,
Session $session,
Request $request = null,
Timebox $timebox = null)
{
$this->name = $name;
$this->session = $session;
$this->request = $request;
$this->provider = $provider;
$this->timebox = $timebox ?: new Timebox;
}
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if ($this->loggedOut) {
return;
}
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
$this->user = $this->userFromRecaller($recaller);
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, true);
}
}
return $this->user;
}
/**
* Pull a user from the repository by its "remember me" cookie token.
*
* @param \Illuminate\Auth\Recaller $recaller
* @return mixed
*/
protected function userFromRecaller($recaller)
{
if (! $recaller->valid() || $this->recallAttempted) {
return;
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$this->recallAttempted = true;
$this->viaRemember = ! is_null($user = $this->provider->retrieveByToken(
$recaller->id(), $recaller->token()
));
return $user;
}
/**
* Get the decrypted recaller cookie for the request.
*
* @return \Illuminate\Auth\Recaller|null
*/
protected function recaller()
{
if (is_null($this->request)) {
return;
}
if ($recaller = $this->request->cookies->get($this->getRecallerName())) {
return new Recaller($recaller);
}
}
/**
* Get the ID for the currently authenticated user.
*
* @return int|string|null
*/
public function id()
{
if ($this->loggedOut) {
return;
}
return $this->user()
? $this->user()->getAuthIdentifier()
: $this->session->get($this->getName());
}
/**
* Log a user into the application without sessions or cookies.
*
* @param array $credentials
* @return bool
*/
public function once(array $credentials = [])
{
$this->fireAttemptEvent($credentials);
if ($this->validate($credentials)) {
$this->setUser($this->lastAttempted);
return true;
}
return false;
}
/**
* Log the given user ID into the application without sessions or cookies.
*
* @param mixed $id
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function onceUsingId($id)
{
if (! is_null($user = $this->provider->retrieveById($id))) {
$this->setUser($user);
return $user;
}
return false;
}
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = [])
{
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
return $this->hasValidCredentials($user, $credentials);
}
/**
* Attempt to authenticate using HTTP Basic Auth.
*
* @param string $field
* @param array $extraConditions
* @return \Symfony\Component\HttpFoundation\Response|null
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
public function basic($field = 'email', $extraConditions = [])
{
if ($this->check()) {
return;
}
// If a username is set on the HTTP basic request, we will return out without
// interrupting the request lifecycle. Otherwise, we'll need to generate a
// request indicating that the given credentials were invalid for login.
if ($this->attemptBasic($this->getRequest(), $field, $extraConditions)) {
return;
}
return $this->failedBasicResponse();
}
/**
* Perform a stateless HTTP Basic login attempt.
*
* @param string $field
* @param array $extraConditions
* @return \Symfony\Component\HttpFoundation\Response|null
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
public function onceBasic($field = 'email', $extraConditions = [])
{
$credentials = $this->basicCredentials($this->getRequest(), $field);
if (! $this->once(array_merge($credentials, $extraConditions))) {
return $this->failedBasicResponse();
}
}
/**
* Attempt to authenticate using basic authentication.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param string $field
* @param array $extraConditions
* @return bool
*/
protected function attemptBasic(Request $request, $field, $extraConditions = [])
{
if (! $request->getUser()) {
return false;
}
return $this->attempt(array_merge(
$this->basicCredentials($request, $field), $extraConditions
));
}
/**
* Get the credential array for an HTTP Basic request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param string $field
* @return array
*/
protected function basicCredentials(Request $request, $field)
{
return [$field => $request->getUser(), 'password' => $request->getPassword()];
}
/**
* Get the response for basic authentication.
*
* @return void
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*/
protected function failedBasicResponse()
{
throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
}
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @return bool
*/
public function attempt(array $credentials = [], $remember = false)
{
$this->fireAttemptEvent($credentials, $remember);
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
// If an implementation of UserInterface was returned, we'll ask the provider
// to validate the user against the given credentials, and if they are in
// fact valid we'll log the users into the application and return true.
if ($this->hasValidCredentials($user, $credentials)) {
$this->login($user, $remember);
return true;
}
// If the authentication attempt fails we will fire an event so that the user
// may be notified of any suspicious attempts to access their account from
// an unrecognized user. A developer may listen to this event as needed.
$this->fireFailedEvent($user, $credentials);
return false;
}
/**
* Attempt to authenticate a user with credentials and additional callbacks.
*
* @param array $credentials
* @param array|callable|null $callbacks
* @param bool $remember
* @return bool
*/
public function attemptWhen(array $credentials = [], $callbacks = null, $remember = false)
{
$this->fireAttemptEvent($credentials, $remember);
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
// This method does the exact same thing as attempt, but also executes callbacks after
// the user is retrieved and validated. If one of the callbacks returns falsy we do
// not login the user. Instead, we will fail the specific authentication attempt.
if ($this->hasValidCredentials($user, $credentials) && $this->shouldLogin($callbacks, $user)) {
$this->login($user, $remember);
return true;
}
$this->fireFailedEvent($user, $credentials);
return false;
}
/**
* Determine if the user matches the credentials.
*
* @param mixed $user
* @param array $credentials
* @return bool
*/
protected function hasValidCredentials($user, $credentials)
{
return $this->timebox->call(function ($timebox) use ($user, $credentials) {
$validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
if ($validated) {
$timebox->returnEarly();
$this->fireValidatedEvent($user);
}
return $validated;
}, 200 * 1000);
}
/**
* Determine if the user should login by executing the given callbacks.
*
* @param array|callable|null $callbacks
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return bool
*/
protected function shouldLogin($callbacks, AuthenticatableContract $user)
{
foreach (Arr::wrap($callbacks) as $callback) {
if (! $callback($user, $this)) {
return false;
}
}
return true;
}
/**
* Log the given user ID into the application.
*
* @param mixed $id
* @param bool $remember
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function loginUsingId($id, $remember = false)
{
if (! is_null($user = $this->provider->retrieveById($id))) {
$this->login($user, $remember);
return $user;
}
return false;
}
/**
* Log a user into the application.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @return void
*/
public function login(AuthenticatableContract $user, $remember = false)
{
$this->updateSession($user->getAuthIdentifier());
// If the user should be permanently "remembered" by the application we will
// queue a permanent cookie that contains the encrypted copy of the user
// identifier. We will then decrypt this later to retrieve the users.
if ($remember) {
$this->ensureRememberTokenIsSet($user);
$this->queueRecallerCookie($user);
}
// If we have an event dispatcher instance set we will fire an event so that
// any listeners will hook into the authentication events and run actions
// based on the login and logout events fired from the guard instances.
$this->fireLoginEvent($user, $remember);
$this->setUser($user);
}
/**
* Update the session with the given ID.
*
* @param string $id
* @return void
*/
protected function updateSession($id)
{
$this->session->put($this->getName(), $id);
$this->session->migrate(true);
}
/**
* Create a new "remember me" token for the user if one doesn't already exist.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function ensureRememberTokenIsSet(AuthenticatableContract $user)
{
if (empty($user->getRememberToken())) {
$this->cycleRememberToken($user);
}
}
/**
* Queue the recaller cookie into the cookie jar.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function queueRecallerCookie(AuthenticatableContract $user)
{
$this->getCookieJar()->queue($this->createRecaller(
$user->getAuthIdentifier().'|'.$user->getRememberToken().'|'.$user->getAuthPassword()
));
}
/**
* Create a "remember me" cookie for a given ID.
*
* @param string $value
* @return \Symfony\Component\HttpFoundation\Cookie
*/
protected function createRecaller($value)
{
return $this->getCookieJar()->make($this->getRecallerName(), $value, $this->getRememberDuration());
}
/**
* Log the user out of the application.
*
* @return void
*/
public function logout()
{
$user = $this->user();
$this->clearUserDataFromStorage();
if (! is_null($this->user) && ! empty($user->getRememberToken())) {
$this->cycleRememberToken($user);
}
// If we have an event dispatcher instance, we can fire off the logout event
// so any further processing can be done. This allows the developer to be
// listening for anytime a user signs out of this application manually.
if (isset($this->events)) {
$this->events->dispatch(new Logout($this->name, $user));
}
// Once we have fired the logout event we will clear the users out of memory
// so they are no longer available as the user is no longer considered as
// being signed into this application and should not be available here.
$this->user = null;
$this->loggedOut = true;
}
/**
* Log the user out of the application on their current device only.
*
* This method does not cycle the "remember" token.
*
* @return void
*/
public function logoutCurrentDevice()
{
$user = $this->user();
$this->clearUserDataFromStorage();
// If we have an event dispatcher instance, we can fire off the logout event
// so any further processing can be done. This allows the developer to be
// listening for anytime a user signs out of this application manually.
if (isset($this->events)) {
$this->events->dispatch(new CurrentDeviceLogout($this->name, $user));
}
// Once we have fired the logout event we will clear the users out of memory
// so they are no longer available as the user is no longer considered as
// being signed into this application and should not be available here.
$this->user = null;
$this->loggedOut = true;
}
/**
* Remove the user data from the session and cookies.
*
* @return void
*/
protected function clearUserDataFromStorage()
{
$this->session->remove($this->getName());
$this->getCookieJar()->unqueue($this->getRecallerName());
if (! is_null($this->recaller())) {
$this->getCookieJar()->queue(
$this->getCookieJar()->forget($this->getRecallerName())
);
}
}
/**
* Refresh the "remember me" token for the user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function cycleRememberToken(AuthenticatableContract $user)
{
$user->setRememberToken($token = Str::random(60));
$this->provider->updateRememberToken($user, $token);
}
/**
* Invalidate other sessions for the current user.
*
* The application must be using the AuthenticateSession middleware.
*
* @param string $password
* @param string $attribute
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function logoutOtherDevices($password, $attribute = 'password')
{
if (! $this->user()) {
return;
}
$result = $this->rehashUserPassword($password, $attribute);
if ($this->recaller() ||
$this->getCookieJar()->hasQueued($this->getRecallerName())) {
$this->queueRecallerCookie($this->user());
}
$this->fireOtherDeviceLogoutEvent($this->user());
return $result;
}
/**
* Rehash the current user's password.
*
* @param string $password
* @param string $attribute
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*
* @throws \InvalidArgumentException
*/
protected function rehashUserPassword($password, $attribute)
{
if (! Hash::check($password, $this->user()->{$attribute})) {
throw new InvalidArgumentException('The given password does not match the current password.');
}
return tap($this->user()->forceFill([
$attribute => Hash::make($password),
]))->save();
}
/**
* Register an authentication attempt event listener.
*
* @param mixed $callback
* @return void
*/
public function attempting($callback)
{
$this->events?->listen(Events\Attempting::class, $callback);
}
/**
* Fire the attempt event with the arguments.
*
* @param array $credentials
* @param bool $remember
* @return void
*/
protected function fireAttemptEvent(array $credentials, $remember = false)
{
$this->events?->dispatch(new Attempting($this->name, $credentials, $remember));
}
/**
* Fires the validated event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function fireValidatedEvent($user)
{
$this->events?->dispatch(new Validated($this->name, $user));
}
/**
* Fire the login event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @return void
*/
protected function fireLoginEvent($user, $remember = false)
{
$this->events?->dispatch(new Login($this->name, $user, $remember));
}
/**
* Fire the authenticated event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function fireAuthenticatedEvent($user)
{
$this->events?->dispatch(new Authenticated($this->name, $user));
}
/**
* Fire the other device logout event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function fireOtherDeviceLogoutEvent($user)
{
$this->events?->dispatch(new OtherDeviceLogout($this->name, $user));
}
/**
* Fire the failed authentication attempt event with the given arguments.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
* @return void
*/
protected function fireFailedEvent($user, array $credentials)
{
$this->events?->dispatch(new Failed($this->name, $user, $credentials));
}
/**
* Get the last user we attempted to authenticate.
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*/
public function getLastAttempted()
{
return $this->lastAttempted;
}
/**
* Get a unique identifier for the auth session value.
*
* @return string
*/
public function getName()
{
return 'login_'.$this->name.'_'.sha1(static::class);
}
/**
* Get the name of the cookie used to store the "recaller".
*
* @return string
*/
public function getRecallerName()
{
return 'remember_'.$this->name.'_'.sha1(static::class);
}
/**
* Determine if the user was authenticated via "remember me" cookie.
*
* @return bool
*/
public function viaRemember()
{
return $this->viaRemember;
}
/**
* Get the number of minutes the remember me cookie should be valid for.
*
* @return int
*/
protected function getRememberDuration()
{
return $this->rememberDuration;
}
/**
* Set the number of minutes the remember me cookie should be valid for.
*
* @param int $minutes
* @return $this
*/
public function setRememberDuration($minutes)
{
$this->rememberDuration = $minutes;
return $this;
}
/**
* Get the cookie creator instance used by the guard.
*
* @return \Illuminate\Contracts\Cookie\QueueingFactory
*
* @throws \RuntimeException
*/
public function getCookieJar()
{
if (! isset($this->cookie)) {
throw new RuntimeException('Cookie jar has not been set.');
}
return $this->cookie;
}
/**
* Set the cookie creator instance used by the guard.
*
* @param \Illuminate\Contracts\Cookie\QueueingFactory $cookie
* @return void
*/
public function setCookieJar(CookieJar $cookie)
{
$this->cookie = $cookie;
}
/**
* Get the event dispatcher instance.
*
* @return \Illuminate\Contracts\Events\Dispatcher
*/
public function getDispatcher()
{
return $this->events;
}
/**
* Set the event dispatcher instance.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function setDispatcher(Dispatcher $events)
{
$this->events = $events;
}
/**
* Get the session store used by the guard.
*
* @return \Illuminate\Contracts\Session\Session
*/
public function getSession()
{
return $this->session;
}
/**
* Return the currently cached user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function getUser()
{
return $this->user;
}
/**
* Set the current user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return $this
*/
public function setUser(AuthenticatableContract $user)
{
$this->user = $user;
$this->loggedOut = false;
$this->fireAuthenticatedEvent($user);
return $this;
}
/**
* Get the current request instance.
*
* @return \Symfony\Component\HttpFoundation\Request
*/
public function getRequest()
{
return $this->request ?: Request::createFromGlobals();
}
/**
* Set the current request instance.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @return $this
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
/**
* Get the timebox instance used by the guard.
*
* @return \Illuminate\Support\Timebox
*/
public function getTimebox()
{
return $this->timebox;
}
}

View File

@ -0,0 +1,149 @@
<?php
namespace Illuminate\Auth;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
class TokenGuard implements Guard
{
use GuardHelpers;
/**
* The request instance.
*
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* The name of the query string item from the request containing the API token.
*
* @var string
*/
protected $inputKey;
/**
* The name of the token "column" in persistent storage.
*
* @var string
*/
protected $storageKey;
/**
* Indicates if the API token is hashed in storage.
*
* @var bool
*/
protected $hash = false;
/**
* Create a new authentication guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Illuminate\Http\Request $request
* @param string $inputKey
* @param string $storageKey
* @param bool $hash
* @return void
*/
public function __construct(
UserProvider $provider,
Request $request,
$inputKey = 'api_token',
$storageKey = 'api_token',
$hash = false)
{
$this->hash = $hash;
$this->request = $request;
$this->provider = $provider;
$this->inputKey = $inputKey;
$this->storageKey = $storageKey;
}
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$user = null;
$token = $this->getTokenForRequest();
if (! empty($token)) {
$user = $this->provider->retrieveByCredentials([
$this->storageKey => $this->hash ? hash('sha256', $token) : $token,
]);
}
return $this->user = $user;
}
/**
* Get the token for the current request.
*
* @return string|null
*/
public function getTokenForRequest()
{
$token = $this->request->query($this->inputKey);
if (empty($token)) {
$token = $this->request->input($this->inputKey);
}
if (empty($token)) {
$token = $this->request->bearerToken();
}
if (empty($token)) {
$token = $this->request->getPassword();
}
return $token;
}
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = [])
{
if (empty($credentials[$this->inputKey])) {
return false;
}
$credentials = [$this->storageKey => $credentials[$this->inputKey]];
if ($this->provider->retrieveByCredentials($credentials)) {
return true;
}
return false;
}
/**
* Set the current request instance.
*
* @param \Illuminate\Http\Request $request
* @return $this
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
}

View File

@ -0,0 +1,45 @@
{
"name": "illuminate/auth",
"description": "The Illuminate Auth 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.1",
"ext-hash": "*",
"illuminate/collections": "^10.0",
"illuminate/contracts": "^10.0",
"illuminate/http": "^10.0",
"illuminate/macroable": "^10.0",
"illuminate/queue": "^10.0",
"illuminate/support": "^10.0"
},
"autoload": {
"psr-4": {
"Illuminate\\Auth\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "10.x-dev"
}
},
"suggest": {
"illuminate/console": "Required to use the auth:clear-resets command (^10.0).",
"illuminate/queue": "Required to fire login / logout events (^10.0).",
"illuminate/session": "Required to use the session based guard (^10.0)."
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@ -0,0 +1,44 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Broadcast;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class BroadcastController extends Controller
{
/**
* Authenticate the request for channel access.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function authenticate(Request $request)
{
if ($request->hasSession()) {
$request->session()->reflash();
}
return Broadcast::auth($request);
}
/**
* Authenticate the current user.
*
* See: https://pusher.com/docs/channels/server_api/authenticating-users/#user-authentication.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function authenticateUser(Request $request)
{
if ($request->hasSession()) {
$request->session()->reflash();
}
return Broadcast::resolveAuthenticatedUser($request)
?? throw new AccessDeniedHttpException;
}
}

View File

@ -0,0 +1,156 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Broadcasting\Factory as BroadcastingFactory;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use ReflectionClass;
use ReflectionProperty;
class BroadcastEvent implements ShouldQueue
{
use Queueable;
/**
* The event instance.
*
* @var mixed
*/
public $event;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries;
/**
* The number of seconds the job can run before timing out.
*
* @var int
*/
public $timeout;
/**
* The number of seconds to wait before retrying the job when encountering an uncaught exception.
*
* @var int
*/
public $backoff;
/**
* The maximum number of unhandled exceptions to allow before failing.
*
* @var int
*/
public $maxExceptions;
/**
* Create a new job handler instance.
*
* @param mixed $event
* @return void
*/
public function __construct($event)
{
$this->event = $event;
$this->tries = property_exists($event, 'tries') ? $event->tries : null;
$this->timeout = property_exists($event, 'timeout') ? $event->timeout : null;
$this->backoff = property_exists($event, 'backoff') ? $event->backoff : null;
$this->afterCommit = property_exists($event, 'afterCommit') ? $event->afterCommit : null;
$this->maxExceptions = property_exists($event, 'maxExceptions') ? $event->maxExceptions : null;
}
/**
* Handle the queued job.
*
* @param \Illuminate\Contracts\Broadcasting\Factory $manager
* @return void
*/
public function handle(BroadcastingFactory $manager)
{
$name = method_exists($this->event, 'broadcastAs')
? $this->event->broadcastAs() : get_class($this->event);
$channels = Arr::wrap($this->event->broadcastOn());
if (empty($channels)) {
return;
}
$connections = method_exists($this->event, 'broadcastConnections')
? $this->event->broadcastConnections()
: [null];
$payload = $this->getPayloadFromEvent($this->event);
foreach ($connections as $connection) {
$manager->connection($connection)->broadcast(
$channels, $name, $payload
);
}
}
/**
* Get the payload for the given event.
*
* @param mixed $event
* @return array
*/
protected function getPayloadFromEvent($event)
{
if (method_exists($event, 'broadcastWith') &&
! is_null($payload = $event->broadcastWith())) {
return array_merge($payload, ['socket' => data_get($event, 'socket')]);
}
$payload = [];
foreach ((new ReflectionClass($event))->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
$payload[$property->getName()] = $this->formatProperty($property->getValue($event));
}
unset($payload['broadcastQueue']);
return $payload;
}
/**
* Format the given value for a property.
*
* @param mixed $value
* @return mixed
*/
protected function formatProperty($value)
{
if ($value instanceof Arrayable) {
return $value->toArray();
}
return $value;
}
/**
* Get the display name for the queued job.
*
* @return string
*/
public function displayName()
{
return get_class($this->event);
}
/**
* Prepare the instance for cloning.
*
* @return void
*/
public function __clone()
{
$this->event = clone $this->event;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Illuminate\Broadcasting;
use RuntimeException;
class BroadcastException extends RuntimeException
{
//
}

View File

@ -0,0 +1,492 @@
<?php
namespace Illuminate\Broadcasting;
use Ably\AblyRest;
use Closure;
use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Broadcasting\Broadcasters\AblyBroadcaster;
use Illuminate\Broadcasting\Broadcasters\LogBroadcaster;
use Illuminate\Broadcasting\Broadcasters\NullBroadcaster;
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
use Illuminate\Broadcasting\Broadcasters\RedisBroadcaster;
use Illuminate\Bus\UniqueLock;
use Illuminate\Contracts\Broadcasting\Factory as FactoryContract;
use Illuminate\Contracts\Broadcasting\ShouldBeUnique;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcherContract;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Contracts\Foundation\CachesRoutes;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Pusher\Pusher;
/**
* @mixin \Illuminate\Contracts\Broadcasting\Broadcaster
*/
class BroadcastManager implements FactoryContract
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $app;
/**
* The array of resolved broadcast drivers.
*
* @var array
*/
protected $drivers = [];
/**
* The registered custom driver creators.
*
* @var array
*/
protected $customCreators = [];
/**
* Create a new manager instance.
*
* @param \Illuminate\Contracts\Container\Container $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
/**
* Register the routes for handling broadcast channel authentication and sockets.
*
* @param array|null $attributes
* @return void
*/
public function routes(array $attributes = null)
{
if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) {
return;
}
$attributes = $attributes ?: ['middleware' => ['web']];
$this->app['router']->group($attributes, function ($router) {
$router->match(
['get', 'post'], '/broadcasting/auth',
'\\'.BroadcastController::class.'@authenticate'
)->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
});
}
/**
* Register the routes for handling broadcast user authentication.
*
* @param array|null $attributes
* @return void
*/
public function userRoutes(array $attributes = null)
{
if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) {
return;
}
$attributes = $attributes ?: ['middleware' => ['web']];
$this->app['router']->group($attributes, function ($router) {
$router->match(
['get', 'post'], '/broadcasting/user-auth',
'\\'.BroadcastController::class.'@authenticateUser'
)->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
});
}
/**
* Register the routes for handling broadcast authentication and sockets.
*
* Alias of "routes" method.
*
* @param array|null $attributes
* @return void
*/
public function channelRoutes(array $attributes = null)
{
$this->routes($attributes);
}
/**
* Get the socket ID for the given request.
*
* @param \Illuminate\Http\Request|null $request
* @return string|null
*/
public function socket($request = null)
{
if (! $request && ! $this->app->bound('request')) {
return;
}
$request = $request ?: $this->app['request'];
return $request->header('X-Socket-ID');
}
/**
* Begin broadcasting an event.
*
* @param mixed|null $event
* @return \Illuminate\Broadcasting\PendingBroadcast
*/
public function event($event = null)
{
return new PendingBroadcast($this->app->make('events'), $event);
}
/**
* Queue the given event for broadcast.
*
* @param mixed $event
* @return void
*/
public function queue($event)
{
if ($event instanceof ShouldBroadcastNow ||
(is_object($event) &&
method_exists($event, 'shouldBroadcastNow') &&
$event->shouldBroadcastNow())) {
return $this->app->make(BusDispatcherContract::class)->dispatchNow(new BroadcastEvent(clone $event));
}
$queue = null;
if (method_exists($event, 'broadcastQueue')) {
$queue = $event->broadcastQueue();
} elseif (isset($event->broadcastQueue)) {
$queue = $event->broadcastQueue;
} elseif (isset($event->queue)) {
$queue = $event->queue;
}
$broadcastEvent = new BroadcastEvent(clone $event);
if ($event instanceof ShouldBeUnique) {
$broadcastEvent = new UniqueBroadcastEvent(clone $event);
if ($this->mustBeUniqueAndCannotAcquireLock($broadcastEvent)) {
return;
}
}
$this->app->make('queue')
->connection($event->connection ?? null)
->pushOn($queue, $broadcastEvent);
}
/**
* Determine if the broadcastable event must be unique and determine if we can acquire the necessary lock.
*
* @param mixed $event
* @return bool
*/
protected function mustBeUniqueAndCannotAcquireLock($event)
{
return ! (new UniqueLock(
method_exists($event, 'uniqueVia')
? $event->uniqueVia()
: $this->app->make(Cache::class)
))->acquire($event);
}
/**
* Get a driver instance.
*
* @param string|null $driver
* @return mixed
*/
public function connection($driver = null)
{
return $this->driver($driver);
}
/**
* Get a driver instance.
*
* @param string|null $name
* @return mixed
*/
public function driver($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->drivers[$name] = $this->get($name);
}
/**
* Attempt to get the connection from the local cache.
*
* @param string $name
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*/
protected function get($name)
{
return $this->drivers[$name] ?? $this->resolve($name);
}
/**
* Resolve the given broadcaster.
*
* @param string $name
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*
* @throws \InvalidArgumentException
*/
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Broadcast connection [{$name}] is not defined.");
}
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($config);
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (! method_exists($this, $driverMethod)) {
throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
}
return $this->{$driverMethod}($config);
}
/**
* Call a custom driver creator.
*
* @param array $config
* @return mixed
*/
protected function callCustomCreator(array $config)
{
return $this->customCreators[$config['driver']]($this->app, $config);
}
/**
* Create an instance of the driver.
*
* @param array $config
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*/
protected function createReverbDriver(array $config)
{
return $this->createPusherDriver($config);
}
/**
* Create an instance of the driver.
*
* @param array $config
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*/
protected function createPusherDriver(array $config)
{
return new PusherBroadcaster($this->pusher($config));
}
/**
* Get a Pusher instance for the given configuration.
*
* @param array $config
* @return \Pusher\Pusher
*/
public function pusher(array $config)
{
$pusher = new Pusher(
$config['key'],
$config['secret'],
$config['app_id'],
$config['options'] ?? [],
isset($config['client_options']) && ! empty($config['client_options'])
? new GuzzleClient($config['client_options'])
: null,
);
if ($config['log'] ?? false) {
$pusher->setLogger($this->app->make(LoggerInterface::class));
}
return $pusher;
}
/**
* Create an instance of the driver.
*
* @param array $config
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*/
protected function createAblyDriver(array $config)
{
return new AblyBroadcaster($this->ably($config));
}
/**
* Get an Ably instance for the given configuration.
*
* @param array $config
* @return \Ably\AblyRest
*/
public function ably(array $config)
{
return new AblyRest($config);
}
/**
* Create an instance of the driver.
*
* @param array $config
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*/
protected function createRedisDriver(array $config)
{
return new RedisBroadcaster(
$this->app->make('redis'), $config['connection'] ?? null,
$this->app['config']->get('database.redis.options.prefix', '')
);
}
/**
* Create an instance of the driver.
*
* @param array $config
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*/
protected function createLogDriver(array $config)
{
return new LogBroadcaster(
$this->app->make(LoggerInterface::class)
);
}
/**
* Create an instance of the driver.
*
* @param array $config
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*/
protected function createNullDriver(array $config)
{
return new NullBroadcaster;
}
/**
* Get the connection configuration.
*
* @param string $name
* @return array
*/
protected function getConfig($name)
{
if (! is_null($name) && $name !== 'null') {
return $this->app['config']["broadcasting.connections.{$name}"];
}
return ['driver' => 'null'];
}
/**
* Get the default driver name.
*
* @return string
*/
public function getDefaultDriver()
{
return $this->app['config']['broadcasting.default'];
}
/**
* Set the default driver name.
*
* @param string $name
* @return void
*/
public function setDefaultDriver($name)
{
$this->app['config']['broadcasting.default'] = $name;
}
/**
* Disconnect the given disk and remove from local cache.
*
* @param string|null $name
* @return void
*/
public function purge($name = null)
{
$name ??= $this->getDefaultDriver();
unset($this->drivers[$name]);
}
/**
* Register a custom driver creator Closure.
*
* @param string $driver
* @param \Closure $callback
* @return $this
*/
public function extend($driver, Closure $callback)
{
$this->customCreators[$driver] = $callback;
return $this;
}
/**
* Get the application instance used by the manager.
*
* @return \Illuminate\Contracts\Foundation\Application
*/
public function getApplication()
{
return $this->app;
}
/**
* Set the application instance used by the manager.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return $this
*/
public function setApplication($app)
{
$this->app = $app;
return $this;
}
/**
* Forget all of the resolved driver instances.
*
* @return $this
*/
public function forgetDrivers()
{
$this->drivers = [];
return $this;
}
/**
* Dynamically call the default driver instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->driver()->$method(...$parameters);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
use Illuminate\Contracts\Broadcasting\Factory as BroadcastingFactory;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton(BroadcastManager::class, fn ($app) => new BroadcastManager($app));
$this->app->singleton(BroadcasterContract::class, function ($app) {
return $app->make(BroadcastManager::class)->connection();
});
$this->app->alias(
BroadcastManager::class, BroadcastingFactory::class
);
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
BroadcastManager::class,
BroadcastingFactory::class,
BroadcasterContract::class,
];
}
}

View File

@ -0,0 +1,246 @@
<?php
namespace Illuminate\Broadcasting\Broadcasters;
use Ably\AblyRest;
use Ably\Exceptions\AblyException;
use Ably\Models\Message as AblyMessage;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* @author Matthew Hall (matthall28@gmail.com)
* @author Taylor Otwell (taylor@laravel.com)
*/
class AblyBroadcaster extends Broadcaster
{
/**
* The AblyRest SDK instance.
*
* @var \Ably\AblyRest
*/
protected $ably;
/**
* Create a new broadcaster instance.
*
* @param \Ably\AblyRest $ably
* @return void
*/
public function __construct(AblyRest $ably)
{
$this->ably = $ably;
}
/**
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function auth($request)
{
$channelName = $this->normalizeChannelName($request->channel_name);
if (empty($request->channel_name) ||
($this->isGuardedChannel($request->channel_name) &&
! $this->retrieveUser($request, $channelName))) {
throw new AccessDeniedHttpException;
}
return parent::verifyUserCanAccessChannel(
$request, $channelName
);
}
/**
* Return the valid authentication response.
*
* @param \Illuminate\Http\Request $request
* @param mixed $result
* @return mixed
*/
public function validAuthenticationResponse($request, $result)
{
if (str_starts_with($request->channel_name, 'private')) {
$signature = $this->generateAblySignature(
$request->channel_name, $request->socket_id
);
return ['auth' => $this->getPublicToken().':'.$signature];
}
$channelName = $this->normalizeChannelName($request->channel_name);
$user = $this->retrieveUser($request, $channelName);
$broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
? $user->getAuthIdentifierForBroadcasting()
: $user->getAuthIdentifier();
$signature = $this->generateAblySignature(
$request->channel_name,
$request->socket_id,
$userData = array_filter([
'user_id' => (string) $broadcastIdentifier,
'user_info' => $result,
])
);
return [
'auth' => $this->getPublicToken().':'.$signature,
'channel_data' => json_encode($userData),
];
}
/**
* Generate the signature needed for Ably authentication headers.
*
* @param string $channelName
* @param string $socketId
* @param array|null $userData
* @return string
*/
public function generateAblySignature($channelName, $socketId, $userData = null)
{
return hash_hmac(
'sha256',
sprintf('%s:%s%s', $socketId, $channelName, $userData ? ':'.json_encode($userData) : ''),
$this->getPrivateToken(),
);
}
/**
* Broadcast the given event.
*
* @param array $channels
* @param string $event
* @param array $payload
* @return void
*
* @throws \Illuminate\Broadcasting\BroadcastException
*/
public function broadcast(array $channels, $event, array $payload = [])
{
try {
foreach ($this->formatChannels($channels) as $channel) {
$this->ably->channels->get($channel)->publish(
$this->buildAblyMessage($event, $payload)
);
}
} catch (AblyException $e) {
throw new BroadcastException(
sprintf('Ably error: %s', $e->getMessage())
);
}
}
/**
* Build an Ably message object for broadcasting.
*
* @param string $event
* @param array $payload
* @return \Ably\Models\Message
*/
protected function buildAblyMessage($event, array $payload = [])
{
return tap(new AblyMessage, function ($message) use ($event, $payload) {
$message->name = $event;
$message->data = $payload;
$message->connectionKey = data_get($payload, 'socket');
});
}
/**
* Return true if the channel is protected by authentication.
*
* @param string $channel
* @return bool
*/
public function isGuardedChannel($channel)
{
return Str::startsWith($channel, ['private-', 'presence-']);
}
/**
* Remove prefix from channel name.
*
* @param string $channel
* @return string
*/
public function normalizeChannelName($channel)
{
if ($this->isGuardedChannel($channel)) {
return str_starts_with($channel, 'private-')
? Str::replaceFirst('private-', '', $channel)
: Str::replaceFirst('presence-', '', $channel);
}
return $channel;
}
/**
* Format the channel array into an array of strings.
*
* @param array $channels
* @return array
*/
protected function formatChannels(array $channels)
{
return array_map(function ($channel) {
$channel = (string) $channel;
if (Str::startsWith($channel, ['private-', 'presence-'])) {
return str_starts_with($channel, 'private-')
? Str::replaceFirst('private-', 'private:', $channel)
: Str::replaceFirst('presence-', 'presence:', $channel);
}
return 'public:'.$channel;
}, $channels);
}
/**
* Get the public token value from the Ably key.
*
* @return string
*/
protected function getPublicToken()
{
return Str::before($this->ably->options->key, ':');
}
/**
* Get the private token value from the Ably key.
*
* @return string
*/
protected function getPrivateToken()
{
return Str::after($this->ably->options->key, ':');
}
/**
* Get the underlying Ably SDK instance.
*
* @return \Ably\AblyRest
*/
public function getAbly()
{
return $this->ably;
}
/**
* Set the underlying Ably SDK instance.
*
* @param \Ably\AblyRest $ably
* @return void
*/
public function setAbly($ably)
{
$this->ably = $ably;
}
}

View File

@ -0,0 +1,386 @@
<?php
namespace Illuminate\Broadcasting\Broadcasters;
use Closure;
use Exception;
use Illuminate\Container\Container;
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
use Illuminate\Contracts\Routing\BindingRegistrar;
use Illuminate\Contracts\Routing\UrlRoutable;
use Illuminate\Support\Arr;
use Illuminate\Support\Reflector;
use ReflectionClass;
use ReflectionFunction;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
abstract class Broadcaster implements BroadcasterContract
{
/**
* The callback to resolve the authenticated user information.
*
* @var \Closure|null
*/
protected $authenticatedUserCallback = null;
/**
* The registered channel authenticators.
*
* @var array
*/
protected $channels = [];
/**
* The registered channel options.
*
* @var array
*/
protected $channelOptions = [];
/**
* The binding registrar instance.
*
* @var \Illuminate\Contracts\Routing\BindingRegistrar
*/
protected $bindingRegistrar;
/**
* Resolve the authenticated user payload for the incoming connection request.
*
* See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication.
*
* @param \Illuminate\Http\Request $request
* @return array|null
*/
public function resolveAuthenticatedUser($request)
{
if ($this->authenticatedUserCallback) {
return $this->authenticatedUserCallback->__invoke($request);
}
}
/**
* Register the user retrieval callback used to authenticate connections.
*
* See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication.
*
* @param \Closure $callback
* @return void
*/
public function resolveAuthenticatedUserUsing(Closure $callback)
{
$this->authenticatedUserCallback = $callback;
}
/**
* Register a channel authenticator.
*
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $channel
* @param callable|string $callback
* @param array $options
* @return $this
*/
public function channel($channel, $callback, $options = [])
{
if ($channel instanceof HasBroadcastChannel) {
$channel = $channel->broadcastChannelRoute();
} elseif (is_string($channel) && class_exists($channel) && is_a($channel, HasBroadcastChannel::class, true)) {
$channel = (new $channel)->broadcastChannelRoute();
}
$this->channels[$channel] = $callback;
$this->channelOptions[$channel] = $options;
return $this;
}
/**
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @param string $channel
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
protected function verifyUserCanAccessChannel($request, $channel)
{
foreach ($this->channels as $pattern => $callback) {
if (! $this->channelNameMatchesPattern($channel, $pattern)) {
continue;
}
$parameters = $this->extractAuthParameters($pattern, $channel, $callback);
$handler = $this->normalizeChannelHandlerToCallable($callback);
$result = $handler($this->retrieveUser($request, $channel), ...$parameters);
if ($result === false) {
throw new AccessDeniedHttpException;
} elseif ($result) {
return $this->validAuthenticationResponse($request, $result);
}
}
throw new AccessDeniedHttpException;
}
/**
* Extract the parameters from the given pattern and channel.
*
* @param string $pattern
* @param string $channel
* @param callable|string $callback
* @return array
*/
protected function extractAuthParameters($pattern, $channel, $callback)
{
$callbackParameters = $this->extractParameters($callback);
return collect($this->extractChannelKeys($pattern, $channel))->reject(function ($value, $key) {
return is_numeric($key);
})->map(function ($value, $key) use ($callbackParameters) {
return $this->resolveBinding($key, $value, $callbackParameters);
})->values()->all();
}
/**
* Extracts the parameters out of what the user passed to handle the channel authentication.
*
* @param callable|string $callback
* @return \ReflectionParameter[]
*
* @throws \Exception
*/
protected function extractParameters($callback)
{
if (is_callable($callback)) {
return (new ReflectionFunction($callback))->getParameters();
} elseif (is_string($callback)) {
return $this->extractParametersFromClass($callback);
}
throw new Exception('Given channel handler is an unknown type.');
}
/**
* Extracts the parameters out of a class channel's "join" method.
*
* @param string $callback
* @return \ReflectionParameter[]
*
* @throws \Exception
*/
protected function extractParametersFromClass($callback)
{
$reflection = new ReflectionClass($callback);
if (! $reflection->hasMethod('join')) {
throw new Exception('Class based channel must define a "join" method.');
}
return $reflection->getMethod('join')->getParameters();
}
/**
* Extract the channel keys from the incoming channel name.
*
* @param string $pattern
* @param string $channel
* @return array
*/
protected function extractChannelKeys($pattern, $channel)
{
preg_match('/^'.preg_replace('/\{(.*?)\}/', '(?<$1>[^\.]+)', $pattern).'/', $channel, $keys);
return $keys;
}
/**
* Resolve the given parameter binding.
*
* @param string $key
* @param string $value
* @param array $callbackParameters
* @return mixed
*/
protected function resolveBinding($key, $value, $callbackParameters)
{
$newValue = $this->resolveExplicitBindingIfPossible($key, $value);
return $newValue === $value ? $this->resolveImplicitBindingIfPossible(
$key, $value, $callbackParameters
) : $newValue;
}
/**
* Resolve an explicit parameter binding if applicable.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
protected function resolveExplicitBindingIfPossible($key, $value)
{
$binder = $this->binder();
if ($binder && $binder->getBindingCallback($key)) {
return call_user_func($binder->getBindingCallback($key), $value);
}
return $value;
}
/**
* Resolve an implicit parameter binding if applicable.
*
* @param string $key
* @param mixed $value
* @param array $callbackParameters
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
protected function resolveImplicitBindingIfPossible($key, $value, $callbackParameters)
{
foreach ($callbackParameters as $parameter) {
if (! $this->isImplicitlyBindable($key, $parameter)) {
continue;
}
$className = Reflector::getParameterClassName($parameter);
if (is_null($model = (new $className)->resolveRouteBinding($value))) {
throw new AccessDeniedHttpException;
}
return $model;
}
return $value;
}
/**
* Determine if a given key and parameter is implicitly bindable.
*
* @param string $key
* @param \ReflectionParameter $parameter
* @return bool
*/
protected function isImplicitlyBindable($key, $parameter)
{
return $parameter->getName() === $key &&
Reflector::isParameterSubclassOf($parameter, UrlRoutable::class);
}
/**
* Format the channel array into an array of strings.
*
* @param array $channels
* @return array
*/
protected function formatChannels(array $channels)
{
return array_map(function ($channel) {
return (string) $channel;
}, $channels);
}
/**
* Get the model binding registrar instance.
*
* @return \Illuminate\Contracts\Routing\BindingRegistrar
*/
protected function binder()
{
if (! $this->bindingRegistrar) {
$this->bindingRegistrar = Container::getInstance()->bound(BindingRegistrar::class)
? Container::getInstance()->make(BindingRegistrar::class) : null;
}
return $this->bindingRegistrar;
}
/**
* Normalize the given callback into a callable.
*
* @param mixed $callback
* @return callable
*/
protected function normalizeChannelHandlerToCallable($callback)
{
return is_callable($callback) ? $callback : function (...$args) use ($callback) {
return Container::getInstance()
->make($callback)
->join(...$args);
};
}
/**
* Retrieve the authenticated user using the configured guard (if any).
*
* @param \Illuminate\Http\Request $request
* @param string $channel
* @return mixed
*/
protected function retrieveUser($request, $channel)
{
$options = $this->retrieveChannelOptions($channel);
$guards = $options['guards'] ?? null;
if (is_null($guards)) {
return $request->user();
}
foreach (Arr::wrap($guards) as $guard) {
if ($user = $request->user($guard)) {
return $user;
}
}
}
/**
* Retrieve options for a certain channel.
*
* @param string $channel
* @return array
*/
protected function retrieveChannelOptions($channel)
{
foreach ($this->channelOptions as $pattern => $options) {
if (! $this->channelNameMatchesPattern($channel, $pattern)) {
continue;
}
return $options;
}
return [];
}
/**
* Check if the channel name from the request matches a pattern from registered channels.
*
* @param string $channel
* @param string $pattern
* @return bool
*/
protected function channelNameMatchesPattern($channel, $pattern)
{
return preg_match('/^'.preg_replace('/\{(.*?)\}/', '([^\.]+)', $pattern).'$/', $channel);
}
/**
* Get all of the registered channels.
*
* @return \Illuminate\Support\Collection
*/
public function getChannels()
{
return collect($this->channels);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Illuminate\Broadcasting\Broadcasters;
use Psr\Log\LoggerInterface;
class LogBroadcaster extends Broadcaster
{
/**
* The logger implementation.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Create a new broadcaster instance.
*
* @param \Psr\Log\LoggerInterface $logger
* @return void
*/
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function auth($request)
{
//
}
/**
* {@inheritdoc}
*/
public function validAuthenticationResponse($request, $result)
{
//
}
/**
* {@inheritdoc}
*/
public function broadcast(array $channels, $event, array $payload = [])
{
$channels = implode(', ', $this->formatChannels($channels));
$payload = json_encode($payload, JSON_PRETTY_PRINT);
$this->logger->info('Broadcasting ['.$event.'] on channels ['.$channels.'] with payload:'.PHP_EOL.$payload);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Illuminate\Broadcasting\Broadcasters;
class NullBroadcaster extends Broadcaster
{
/**
* {@inheritdoc}
*/
public function auth($request)
{
//
}
/**
* {@inheritdoc}
*/
public function validAuthenticationResponse($request, $result)
{
//
}
/**
* {@inheritdoc}
*/
public function broadcast(array $channels, $event, array $payload = [])
{
//
}
}

View File

@ -0,0 +1,190 @@
<?php
namespace Illuminate\Broadcasting\Broadcasters;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Pusher\ApiErrorException;
use Pusher\Pusher;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class PusherBroadcaster extends Broadcaster
{
use UsePusherChannelConventions;
/**
* The Pusher SDK instance.
*
* @var \Pusher\Pusher
*/
protected $pusher;
/**
* Create a new broadcaster instance.
*
* @param \Pusher\Pusher $pusher
* @return void
*/
public function __construct(Pusher $pusher)
{
$this->pusher = $pusher;
}
/**
* Resolve the authenticated user payload for an incoming connection request.
*
* See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication
* See: https://pusher.com/docs/channels/server_api/authenticating-users/#response
*
* @param \Illuminate\Http\Request $request
* @return array|null
*/
public function resolveAuthenticatedUser($request)
{
if (! $user = parent::resolveAuthenticatedUser($request)) {
return;
}
if (method_exists($this->pusher, 'authenticateUser')) {
return $this->pusher->authenticateUser($request->socket_id, $user);
}
$settings = $this->pusher->getSettings();
$encodedUser = json_encode($user);
$decodedString = "{$request->socket_id}::user::{$encodedUser}";
$auth = $settings['auth_key'].':'.hash_hmac(
'sha256', $decodedString, $settings['secret']
);
return [
'auth' => $auth,
'user_data' => $encodedUser,
];
}
/**
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function auth($request)
{
$channelName = $this->normalizeChannelName($request->channel_name);
if (empty($request->channel_name) ||
($this->isGuardedChannel($request->channel_name) &&
! $this->retrieveUser($request, $channelName))) {
throw new AccessDeniedHttpException;
}
return parent::verifyUserCanAccessChannel(
$request, $channelName
);
}
/**
* Return the valid authentication response.
*
* @param \Illuminate\Http\Request $request
* @param mixed $result
* @return mixed
*/
public function validAuthenticationResponse($request, $result)
{
if (str_starts_with($request->channel_name, 'private')) {
return $this->decodePusherResponse(
$request,
method_exists($this->pusher, 'authorizeChannel')
? $this->pusher->authorizeChannel($request->channel_name, $request->socket_id)
: $this->pusher->socket_auth($request->channel_name, $request->socket_id)
);
}
$channelName = $this->normalizeChannelName($request->channel_name);
$user = $this->retrieveUser($request, $channelName);
$broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
? $user->getAuthIdentifierForBroadcasting()
: $user->getAuthIdentifier();
return $this->decodePusherResponse(
$request,
method_exists($this->pusher, 'authorizePresenceChannel')
? $this->pusher->authorizePresenceChannel($request->channel_name, $request->socket_id, $broadcastIdentifier, $result)
: $this->pusher->presence_auth($request->channel_name, $request->socket_id, $broadcastIdentifier, $result)
);
}
/**
* Decode the given Pusher response.
*
* @param \Illuminate\Http\Request $request
* @param mixed $response
* @return array
*/
protected function decodePusherResponse($request, $response)
{
if (! $request->input('callback', false)) {
return json_decode($response, true);
}
return response()->json(json_decode($response, true))
->withCallback($request->callback);
}
/**
* Broadcast the given event.
*
* @param array $channels
* @param string $event
* @param array $payload
* @return void
*
* @throws \Illuminate\Broadcasting\BroadcastException
*/
public function broadcast(array $channels, $event, array $payload = [])
{
$socket = Arr::pull($payload, 'socket');
$parameters = $socket !== null ? ['socket_id' => $socket] : [];
$channels = Collection::make($this->formatChannels($channels));
try {
$channels->chunk(100)->each(function ($channels) use ($event, $payload, $parameters) {
$this->pusher->trigger($channels->toArray(), $event, $payload, $parameters);
});
} catch (ApiErrorException $e) {
throw new BroadcastException(
sprintf('Pusher error: %s.', $e->getMessage())
);
}
}
/**
* Get the Pusher SDK instance.
*
* @return \Pusher\Pusher
*/
public function getPusher()
{
return $this->pusher;
}
/**
* Set the Pusher SDK instance.
*
* @param \Pusher\Pusher $pusher
* @return void
*/
public function setPusher($pusher)
{
$this->pusher = $pusher;
}
}

View File

@ -0,0 +1,169 @@
<?php
namespace Illuminate\Broadcasting\Broadcasters;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Contracts\Redis\Factory as Redis;
use Illuminate\Support\Arr;
use Predis\Connection\ConnectionException;
use RedisException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class RedisBroadcaster extends Broadcaster
{
use UsePusherChannelConventions;
/**
* The Redis instance.
*
* @var \Illuminate\Contracts\Redis\Factory
*/
protected $redis;
/**
* The Redis connection to use for broadcasting.
*
* @var string|null
*/
protected $connection = null;
/**
* The Redis key prefix.
*
* @var string
*/
protected $prefix = '';
/**
* Create a new broadcaster instance.
*
* @param \Illuminate\Contracts\Redis\Factory $redis
* @param string|null $connection
* @param string $prefix
* @return void
*/
public function __construct(Redis $redis, $connection = null, $prefix = '')
{
$this->redis = $redis;
$this->prefix = $prefix;
$this->connection = $connection;
}
/**
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function auth($request)
{
$channelName = $this->normalizeChannelName(
str_replace($this->prefix, '', $request->channel_name)
);
if (empty($request->channel_name) ||
($this->isGuardedChannel($request->channel_name) &&
! $this->retrieveUser($request, $channelName))) {
throw new AccessDeniedHttpException;
}
return parent::verifyUserCanAccessChannel(
$request, $channelName
);
}
/**
* Return the valid authentication response.
*
* @param \Illuminate\Http\Request $request
* @param mixed $result
* @return mixed
*/
public function validAuthenticationResponse($request, $result)
{
if (is_bool($result)) {
return json_encode($result);
}
$channelName = $this->normalizeChannelName($request->channel_name);
$user = $this->retrieveUser($request, $channelName);
$broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
? $user->getAuthIdentifierForBroadcasting()
: $user->getAuthIdentifier();
return json_encode(['channel_data' => [
'user_id' => $broadcastIdentifier,
'user_info' => $result,
]]);
}
/**
* Broadcast the given event.
*
* @param array $channels
* @param string $event
* @param array $payload
* @return void
*
* @throws \Illuminate\Broadcasting\BroadcastException
*/
public function broadcast(array $channels, $event, array $payload = [])
{
if (empty($channels)) {
return;
}
$connection = $this->redis->connection($this->connection);
$payload = json_encode([
'event' => $event,
'data' => $payload,
'socket' => Arr::pull($payload, 'socket'),
]);
try {
$connection->eval(
$this->broadcastMultipleChannelsScript(),
0, $payload, ...$this->formatChannels($channels)
);
} catch (ConnectionException|RedisException $e) {
throw new BroadcastException(
sprintf('Redis error: %s.', $e->getMessage())
);
}
}
/**
* Get the Lua script for broadcasting to multiple channels.
*
* ARGV[1] - The payload
* ARGV[2...] - The channels
*
* @return string
*/
protected function broadcastMultipleChannelsScript()
{
return <<<'LUA'
for i = 2, #ARGV do
redis.call('publish', ARGV[i], ARGV[1])
end
LUA;
}
/**
* Format the channel array into an array of strings.
*
* @param array $channels
* @return array
*/
protected function formatChannels(array $channels)
{
return array_map(function ($channel) {
return $this->prefix.$channel;
}, parent::formatChannels($channels));
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Illuminate\Broadcasting\Broadcasters;
use Illuminate\Support\Str;
trait UsePusherChannelConventions
{
/**
* Return true if the channel is protected by authentication.
*
* @param string $channel
* @return bool
*/
public function isGuardedChannel($channel)
{
return Str::startsWith($channel, ['private-', 'presence-']);
}
/**
* Remove prefix from channel name.
*
* @param string $channel
* @return string
*/
public function normalizeChannelName($channel)
{
foreach (['private-encrypted-', 'private-', 'presence-'] as $prefix) {
if (Str::startsWith($channel, $prefix)) {
return Str::replaceFirst($prefix, '', $channel);
}
}
return $channel;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
class Channel
{
/**
* The channel's name.
*
* @var string
*/
public $name;
/**
* Create a new channel instance.
*
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name
* @return void
*/
public function __construct($name)
{
$this->name = $name instanceof HasBroadcastChannel ? $name->broadcastChannel() : $name;
}
/**
* Convert the channel instance to a string.
*
* @return string
*/
public function __toString()
{
return $this->name;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Illuminate\Broadcasting;
class EncryptedPrivateChannel extends Channel
{
/**
* Create a new channel instance.
*
* @param string $name
* @return void
*/
public function __construct($name)
{
parent::__construct('private-encrypted-'.$name);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Support\Arr;
trait InteractsWithBroadcasting
{
/**
* The broadcaster connection to use to broadcast the event.
*
* @var array
*/
protected $broadcastConnection = [null];
/**
* Broadcast the event using a specific broadcaster.
*
* @param array|string|null $connection
* @return $this
*/
public function broadcastVia($connection = null)
{
$this->broadcastConnection = is_null($connection)
? [null]
: Arr::wrap($connection);
return $this;
}
/**
* Get the broadcaster connections the event should be broadcast on.
*
* @return array
*/
public function broadcastConnections()
{
return $this->broadcastConnection;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Support\Facades\Broadcast;
trait InteractsWithSockets
{
/**
* The socket ID for the user that raised the event.
*
* @var string|null
*/
public $socket;
/**
* Exclude the current user from receiving the broadcast.
*
* @return $this
*/
public function dontBroadcastToCurrentUser()
{
$this->socket = Broadcast::socket();
return $this;
}
/**
* Broadcast the event to everyone.
*
* @return $this
*/
public function broadcastToEveryone()
{
$this->socket = null;
return $this;
}
}

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,74 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Contracts\Events\Dispatcher;
class PendingBroadcast
{
/**
* The event dispatcher implementation.
*
* @var \Illuminate\Contracts\Events\Dispatcher
*/
protected $events;
/**
* The event instance.
*
* @var mixed
*/
protected $event;
/**
* Create a new pending broadcast instance.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @param mixed $event
* @return void
*/
public function __construct(Dispatcher $events, $event)
{
$this->event = $event;
$this->events = $events;
}
/**
* Broadcast the event using a specific broadcaster.
*
* @param string|null $connection
* @return $this
*/
public function via($connection = null)
{
if (method_exists($this->event, 'broadcastVia')) {
$this->event->broadcastVia($connection);
}
return $this;
}
/**
* Broadcast the event to everyone except the current user.
*
* @return $this
*/
public function toOthers()
{
if (method_exists($this->event, 'dontBroadcastToCurrentUser')) {
$this->event->dontBroadcastToCurrentUser();
}
return $this;
}
/**
* Handle the object's destruction.
*
* @return void
*/
public function __destruct()
{
$this->events->dispatch($this->event);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Illuminate\Broadcasting;
class PresenceChannel extends Channel
{
/**
* Create a new channel instance.
*
* @param string $name
* @return void
*/
public function __construct($name)
{
parent::__construct('presence-'.$name);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
class PrivateChannel extends Channel
{
/**
* Create a new channel instance.
*
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name
* @return void
*/
public function __construct($name)
{
$name = $name instanceof HasBroadcastChannel ? $name->broadcastChannel() : $name;
parent::__construct('private-'.$name);
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Illuminate\Broadcasting;
use Illuminate\Container\Container;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Queue\ShouldBeUnique;
class UniqueBroadcastEvent extends BroadcastEvent implements ShouldBeUnique
{
/**
* The unique lock identifier.
*
* @var mixed
*/
public $uniqueId;
/**
* The number of seconds the unique lock should be maintained.
*
* @var int
*/
public $uniqueFor;
/**
* Create a new event instance.
*
* @param mixed $event
* @return void
*/
public function __construct($event)
{
$this->uniqueId = get_class($event);
if (method_exists($event, 'uniqueId')) {
$this->uniqueId .= $event->uniqueId();
} elseif (property_exists($event, 'uniqueId')) {
$this->uniqueId .= $event->uniqueId;
}
if (method_exists($event, 'uniqueFor')) {
$this->uniqueFor = $event->uniqueFor();
} elseif (property_exists($event, 'uniqueFor')) {
$this->uniqueFor = $event->uniqueFor;
}
parent::__construct($event);
}
/**
* Resolve the cache implementation that should manage the event's uniqueness.
*
* @return \Illuminate\Contracts\Cache\Repository
*/
public function uniqueVia()
{
return method_exists($this->event, 'uniqueVia')
? $this->event->uniqueVia()
: Container::getInstance()->make(Repository::class);
}
}

View File

@ -0,0 +1,45 @@
{
"name": "illuminate/broadcasting",
"description": "The Illuminate Broadcasting 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.1",
"psr/log": "^1.0|^2.0|^3.0",
"illuminate/bus": "^10.0",
"illuminate/collections": "^10.0",
"illuminate/container": "^10.0",
"illuminate/contracts": "^10.0",
"illuminate/queue": "^10.0",
"illuminate/support": "^10.0"
},
"autoload": {
"psr-4": {
"Illuminate\\Broadcasting\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "10.x-dev"
}
},
"suggest": {
"ext-hash": "Required to use the Ably and Pusher broadcast drivers.",
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0)."
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@ -0,0 +1,506 @@
<?php
namespace Illuminate\Bus;
use Carbon\CarbonImmutable;
use Closure;
use Illuminate\Contracts\Queue\Factory as QueueFactory;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Queue\CallQueuedClosure;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use JsonSerializable;
use Throwable;
class Batch implements Arrayable, JsonSerializable
{
/**
* The queue factory implementation.
*
* @var \Illuminate\Contracts\Queue\Factory
*/
protected $queue;
/**
* The repository implementation.
*
* @var \Illuminate\Bus\BatchRepository
*/
protected $repository;
/**
* The batch ID.
*
* @var string
*/
public $id;
/**
* The batch name.
*
* @var string
*/
public $name;
/**
* The total number of jobs that belong to the batch.
*
* @var int
*/
public $totalJobs;
/**
* The total number of jobs that are still pending.
*
* @var int
*/
public $pendingJobs;
/**
* The total number of jobs that have failed.
*
* @var int
*/
public $failedJobs;
/**
* The IDs of the jobs that have failed.
*
* @var array
*/
public $failedJobIds;
/**
* The batch options.
*
* @var array
*/
public $options;
/**
* The date indicating when the batch was created.
*
* @var \Carbon\CarbonImmutable
*/
public $createdAt;
/**
* The date indicating when the batch was cancelled.
*
* @var \Carbon\CarbonImmutable|null
*/
public $cancelledAt;
/**
* The date indicating when the batch was finished.
*
* @var \Carbon\CarbonImmutable|null
*/
public $finishedAt;
/**
* Create a new batch instance.
*
* @param \Illuminate\Contracts\Queue\Factory $queue
* @param \Illuminate\Bus\BatchRepository $repository
* @param string $id
* @param string $name
* @param int $totalJobs
* @param int $pendingJobs
* @param int $failedJobs
* @param array $failedJobIds
* @param array $options
* @param \Carbon\CarbonImmutable $createdAt
* @param \Carbon\CarbonImmutable|null $cancelledAt
* @param \Carbon\CarbonImmutable|null $finishedAt
* @return void
*/
public function __construct(QueueFactory $queue,
BatchRepository $repository,
string $id,
string $name,
int $totalJobs,
int $pendingJobs,
int $failedJobs,
array $failedJobIds,
array $options,
CarbonImmutable $createdAt,
?CarbonImmutable $cancelledAt = null,
?CarbonImmutable $finishedAt = null)
{
$this->queue = $queue;
$this->repository = $repository;
$this->id = $id;
$this->name = $name;
$this->totalJobs = $totalJobs;
$this->pendingJobs = $pendingJobs;
$this->failedJobs = $failedJobs;
$this->failedJobIds = $failedJobIds;
$this->options = $options;
$this->createdAt = $createdAt;
$this->cancelledAt = $cancelledAt;
$this->finishedAt = $finishedAt;
}
/**
* Get a fresh instance of the batch represented by this ID.
*
* @return self
*/
public function fresh()
{
return $this->repository->find($this->id);
}
/**
* Add additional jobs to the batch.
*
* @param \Illuminate\Support\Enumerable|object|array $jobs
* @return self
*/
public function add($jobs)
{
$count = 0;
$jobs = Collection::wrap($jobs)->map(function ($job) use (&$count) {
$job = $job instanceof Closure ? CallQueuedClosure::create($job) : $job;
if (is_array($job)) {
$count += count($job);
return with($this->prepareBatchedChain($job), function ($chain) {
return $chain->first()
->allOnQueue($this->options['queue'] ?? null)
->allOnConnection($this->options['connection'] ?? null)
->chain($chain->slice(1)->values()->all());
});
} else {
$job->withBatchId($this->id);
$count++;
}
return $job;
});
$this->repository->transaction(function () use ($jobs, $count) {
$this->repository->incrementTotalJobs($this->id, $count);
$this->queue->connection($this->options['connection'] ?? null)->bulk(
$jobs->all(),
$data = '',
$this->options['queue'] ?? null
);
});
return $this->fresh();
}
/**
* Prepare a chain that exists within the jobs being added.
*
* @param array $chain
* @return \Illuminate\Support\Collection
*/
protected function prepareBatchedChain(array $chain)
{
return collect($chain)->map(function ($job) {
$job = $job instanceof Closure ? CallQueuedClosure::create($job) : $job;
return $job->withBatchId($this->id);
});
}
/**
* Get the total number of jobs that have been processed by the batch thus far.
*
* @return int
*/
public function processedJobs()
{
return $this->totalJobs - $this->pendingJobs;
}
/**
* Get the percentage of jobs that have been processed (between 0-100).
*
* @return int
*/
public function progress()
{
return $this->totalJobs > 0 ? round(($this->processedJobs() / $this->totalJobs) * 100) : 0;
}
/**
* Record that a job within the batch finished successfully, executing any callbacks if necessary.
*
* @param string $jobId
* @return void
*/
public function recordSuccessfulJob(string $jobId)
{
$counts = $this->decrementPendingJobs($jobId);
if ($this->hasProgressCallbacks()) {
$batch = $this->fresh();
collect($this->options['progress'])->each(function ($handler) use ($batch) {
$this->invokeHandlerCallback($handler, $batch);
});
}
if ($counts->pendingJobs === 0) {
$this->repository->markAsFinished($this->id);
}
if ($counts->pendingJobs === 0 && $this->hasThenCallbacks()) {
$batch = $this->fresh();
collect($this->options['then'])->each(function ($handler) use ($batch) {
$this->invokeHandlerCallback($handler, $batch);
});
}
if ($counts->allJobsHaveRanExactlyOnce() && $this->hasFinallyCallbacks()) {
$batch = $this->fresh();
collect($this->options['finally'])->each(function ($handler) use ($batch) {
$this->invokeHandlerCallback($handler, $batch);
});
}
}
/**
* Decrement the pending jobs for the batch.
*
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function decrementPendingJobs(string $jobId)
{
return $this->repository->decrementPendingJobs($this->id, $jobId);
}
/**
* Determine if the batch has finished executing.
*
* @return bool
*/
public function finished()
{
return ! is_null($this->finishedAt);
}
/**
* Determine if the batch has "progress" callbacks.
*
* @return bool
*/
public function hasProgressCallbacks()
{
return isset($this->options['progress']) && ! empty($this->options['progress']);
}
/**
* Determine if the batch has "success" callbacks.
*
* @return bool
*/
public function hasThenCallbacks()
{
return isset($this->options['then']) && ! empty($this->options['then']);
}
/**
* Determine if the batch allows jobs to fail without cancelling the batch.
*
* @return bool
*/
public function allowsFailures()
{
return Arr::get($this->options, 'allowFailures', false) === true;
}
/**
* Determine if the batch has job failures.
*
* @return bool
*/
public function hasFailures()
{
return $this->failedJobs > 0;
}
/**
* Record that a job within the batch failed to finish successfully, executing any callbacks if necessary.
*
* @param string $jobId
* @param \Throwable $e
* @return void
*/
public function recordFailedJob(string $jobId, $e)
{
$counts = $this->incrementFailedJobs($jobId);
if ($counts->failedJobs === 1 && ! $this->allowsFailures()) {
$this->cancel();
}
if ($this->hasProgressCallbacks() && $this->allowsFailures()) {
$batch = $this->fresh();
collect($this->options['progress'])->each(function ($handler) use ($batch, $e) {
$this->invokeHandlerCallback($handler, $batch, $e);
});
}
if ($counts->failedJobs === 1 && $this->hasCatchCallbacks()) {
$batch = $this->fresh();
collect($this->options['catch'])->each(function ($handler) use ($batch, $e) {
$this->invokeHandlerCallback($handler, $batch, $e);
});
}
if ($counts->allJobsHaveRanExactlyOnce() && $this->hasFinallyCallbacks()) {
$batch = $this->fresh();
collect($this->options['finally'])->each(function ($handler) use ($batch, $e) {
$this->invokeHandlerCallback($handler, $batch, $e);
});
}
}
/**
* Increment the failed jobs for the batch.
*
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function incrementFailedJobs(string $jobId)
{
return $this->repository->incrementFailedJobs($this->id, $jobId);
}
/**
* Determine if the batch has "catch" callbacks.
*
* @return bool
*/
public function hasCatchCallbacks()
{
return isset($this->options['catch']) && ! empty($this->options['catch']);
}
/**
* Determine if the batch has "finally" callbacks.
*
* @return bool
*/
public function hasFinallyCallbacks()
{
return isset($this->options['finally']) && ! empty($this->options['finally']);
}
/**
* Cancel the batch.
*
* @return void
*/
public function cancel()
{
$this->repository->cancel($this->id);
}
/**
* Determine if the batch has been cancelled.
*
* @return bool
*/
public function canceled()
{
return $this->cancelled();
}
/**
* Determine if the batch has been cancelled.
*
* @return bool
*/
public function cancelled()
{
return ! is_null($this->cancelledAt);
}
/**
* Delete the batch from storage.
*
* @return void
*/
public function delete()
{
$this->repository->delete($this->id);
}
/**
* Invoke a batch callback handler.
*
* @param callable $handler
* @param \Illuminate\Bus\Batch $batch
* @param \Throwable|null $e
* @return void
*/
protected function invokeHandlerCallback($handler, Batch $batch, Throwable $e = null)
{
try {
return $handler($batch, $e);
} catch (Throwable $e) {
if (function_exists('report')) {
report($e);
}
}
}
/**
* Convert the batch to an array.
*
* @return array
*/
public function toArray()
{
return [
'id' => $this->id,
'name' => $this->name,
'totalJobs' => $this->totalJobs,
'pendingJobs' => $this->pendingJobs,
'processedJobs' => $this->processedJobs(),
'progress' => $this->progress(),
'failedJobs' => $this->failedJobs,
'options' => $this->options,
'createdAt' => $this->createdAt,
'cancelledAt' => $this->cancelledAt,
'finishedAt' => $this->finishedAt,
];
}
/**
* Get the JSON serializable representation of the object.
*
* @return array
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
/**
* Dynamically access the batch's "options" via properties.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->options[$key] ?? null;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Illuminate\Bus;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Queue\Factory as QueueFactory;
class BatchFactory
{
/**
* The queue factory implementation.
*
* @var \Illuminate\Contracts\Queue\Factory
*/
protected $queue;
/**
* Create a new batch factory instance.
*
* @param \Illuminate\Contracts\Queue\Factory $queue
* @return void
*/
public function __construct(QueueFactory $queue)
{
$this->queue = $queue;
}
/**
* Create a new batch instance.
*
* @param \Illuminate\Bus\BatchRepository $repository
* @param string $id
* @param string $name
* @param int $totalJobs
* @param int $pendingJobs
* @param int $failedJobs
* @param array $failedJobIds
* @param array $options
* @param \Carbon\CarbonImmutable $createdAt
* @param \Carbon\CarbonImmutable|null $cancelledAt
* @param \Carbon\CarbonImmutable|null $finishedAt
* @return \Illuminate\Bus\Batch
*/
public function make(BatchRepository $repository,
string $id,
string $name,
int $totalJobs,
int $pendingJobs,
int $failedJobs,
array $failedJobIds,
array $options,
CarbonImmutable $createdAt,
?CarbonImmutable $cancelledAt,
?CarbonImmutable $finishedAt)
{
return new Batch($this->queue, $repository, $id, $name, $totalJobs, $pendingJobs, $failedJobs, $failedJobIds, $options, $createdAt, $cancelledAt, $finishedAt);
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace Illuminate\Bus;
use Closure;
/**
* @method void rollBack()
*/
interface BatchRepository
{
/**
* Retrieve a list of batches.
*
* @param int $limit
* @param mixed $before
* @return \Illuminate\Bus\Batch[]
*/
public function get($limit, $before);
/**
* Retrieve information about an existing batch.
*
* @param string $batchId
* @return \Illuminate\Bus\Batch|null
*/
public function find(string $batchId);
/**
* Store a new pending batch.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return \Illuminate\Bus\Batch
*/
public function store(PendingBatch $batch);
/**
* Increment the total number of jobs within the batch.
*
* @param string $batchId
* @param int $amount
* @return void
*/
public function incrementTotalJobs(string $batchId, int $amount);
/**
* Decrement the total number of pending jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function decrementPendingJobs(string $batchId, string $jobId);
/**
* Increment the total number of failed jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function incrementFailedJobs(string $batchId, string $jobId);
/**
* Mark the batch that has the given ID as finished.
*
* @param string $batchId
* @return void
*/
public function markAsFinished(string $batchId);
/**
* Cancel the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function cancel(string $batchId);
/**
* Delete the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function delete(string $batchId);
/**
* Execute the given Closure within a storage specific transaction.
*
* @param \Closure $callback
* @return mixed
*/
public function transaction(Closure $callback);
}

View File

@ -0,0 +1,108 @@
<?php
namespace Illuminate\Bus;
use Carbon\CarbonImmutable;
use Illuminate\Container\Container;
use Illuminate\Support\Str;
use Illuminate\Support\Testing\Fakes\BatchFake;
trait Batchable
{
/**
* The batch ID (if applicable).
*
* @var string
*/
public $batchId;
/**
* The fake batch, if applicable.
*
* @var \Illuminate\Support\Testing\Fakes\BatchFake
*/
private $fakeBatch;
/**
* Get the batch instance for the job, if applicable.
*
* @return \Illuminate\Bus\Batch|null
*/
public function batch()
{
if ($this->fakeBatch) {
return $this->fakeBatch;
}
if ($this->batchId) {
return Container::getInstance()->make(BatchRepository::class)?->find($this->batchId);
}
}
/**
* Determine if the batch is still active and processing.
*
* @return bool
*/
public function batching()
{
$batch = $this->batch();
return $batch && ! $batch->cancelled();
}
/**
* Set the batch ID on the job.
*
* @param string $batchId
* @return $this
*/
public function withBatchId(string $batchId)
{
$this->batchId = $batchId;
return $this;
}
/**
* Indicate that the job should use a fake batch.
*
* @param string $id
* @param string $name
* @param int $totalJobs
* @param int $pendingJobs
* @param int $failedJobs
* @param array $failedJobIds
* @param array $options
* @param \Carbon\CarbonImmutable|null $createdAt
* @param \Carbon\CarbonImmutable|null $cancelledAt
* @param \Carbon\CarbonImmutable|null $finishedAt
* @return array{0: $this, 1: \Illuminate\Support\Testing\Fakes\BatchFake}
*/
public function withFakeBatch(string $id = '',
string $name = '',
int $totalJobs = 0,
int $pendingJobs = 0,
int $failedJobs = 0,
array $failedJobIds = [],
array $options = [],
?CarbonImmutable $createdAt = null,
?CarbonImmutable $cancelledAt = null,
?CarbonImmutable $finishedAt = null)
{
$this->fakeBatch = new BatchFake(
empty($id) ? (string) Str::uuid() : $id,
$name,
$totalJobs,
$pendingJobs,
$failedJobs,
$failedJobIds,
$options,
$createdAt ?? CarbonImmutable::now(),
$cancelledAt,
$finishedAt,
);
return [$this, $this->fakeBatch];
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace Illuminate\Bus;
use Aws\DynamoDb\DynamoDbClient;
use Illuminate\Contracts\Bus\Dispatcher as DispatcherContract;
use Illuminate\Contracts\Bus\QueueingDispatcher as QueueingDispatcherContract;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
class BusServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton(Dispatcher::class, function ($app) {
return new Dispatcher($app, function ($connection = null) use ($app) {
return $app[QueueFactoryContract::class]->connection($connection);
});
});
$this->registerBatchServices();
$this->app->alias(
Dispatcher::class, DispatcherContract::class
);
$this->app->alias(
Dispatcher::class, QueueingDispatcherContract::class
);
}
/**
* Register the batch handling services.
*
* @return void
*/
protected function registerBatchServices()
{
$this->app->singleton(BatchRepository::class, function ($app) {
$driver = $app->config->get('queue.batching.driver', 'database');
return $driver === 'dynamodb'
? $app->make(DynamoBatchRepository::class)
: $app->make(DatabaseBatchRepository::class);
});
$this->app->singleton(DatabaseBatchRepository::class, function ($app) {
return new DatabaseBatchRepository(
$app->make(BatchFactory::class),
$app->make('db')->connection($app->config->get('queue.batching.database')),
$app->config->get('queue.batching.table', 'job_batches')
);
});
$this->app->singleton(DynamoBatchRepository::class, function ($app) {
$config = $app->config->get('queue.batching');
$dynamoConfig = [
'region' => $config['region'],
'version' => 'latest',
'endpoint' => $config['endpoint'] ?? null,
];
if (! empty($config['key']) && ! empty($config['secret'])) {
$dynamoConfig['credentials'] = Arr::only(
$config,
['key', 'secret', 'token']
);
}
return new DynamoBatchRepository(
$app->make(BatchFactory::class),
new DynamoDbClient($dynamoConfig),
$app->config->get('app.name'),
$app->config->get('queue.batching.table', 'job_batches'),
ttl: $app->config->get('queue.batching.ttl', null),
ttlAttribute: $app->config->get('queue.batching.ttl_attribute', 'ttl'),
);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
Dispatcher::class,
DispatcherContract::class,
QueueingDispatcherContract::class,
BatchRepository::class,
DatabaseBatchRepository::class,
];
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace Illuminate\Bus;
use Illuminate\Container\Container;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Collection;
use Throwable;
class ChainedBatch implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable;
/**
* The collection of batched jobs.
*
* @var \Illuminate\Support\Collection
*/
public Collection $jobs;
/**
* The name of the batch.
*
* @var string
*/
public string $name;
/**
* The batch options.
*
* @var array
*/
public array $options;
/**
* Create a new chained batch instance.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return void
*/
public function __construct(PendingBatch $batch)
{
$this->jobs = static::prepareNestedBatches($batch->jobs);
$this->name = $batch->name;
$this->options = $batch->options;
}
/**
* Prepare any nested batches within the given collection of jobs.
*
* @param \Illuminate\Support\Collection $jobs
* @return \Illuminate\Support\Collection
*/
public static function prepareNestedBatches(Collection $jobs): Collection
{
return $jobs->map(fn ($job) => match (true) {
is_array($job) => static::prepareNestedBatches(collect($job))->all(),
$job instanceof Collection => static::prepareNestedBatches($job),
$job instanceof PendingBatch => new ChainedBatch($job),
default => $job,
});
}
/**
* Handle the job.
*
* @return void
*/
public function handle()
{
$this->attachRemainderOfChainToEndOfBatch(
$this->toPendingBatch()
)->dispatch();
}
/**
* Convert the chained batch instance into a pending batch.
*
* @return \Illuminate\Bus\PendingBatch
*/
public function toPendingBatch()
{
$batch = Container::getInstance()->make(Dispatcher::class)->batch($this->jobs);
$batch->name = $this->name;
$batch->options = $this->options;
if ($this->queue) {
$batch->onQueue($this->queue);
}
if ($this->connection) {
$batch->onConnection($this->connection);
}
foreach ($this->chainCatchCallbacks ?? [] as $callback) {
$batch->catch(function (Batch $batch, ?Throwable $exception) use ($callback) {
if (! $batch->allowsFailures()) {
$callback($exception);
}
});
}
return $batch;
}
/**
* Move the remainder of the chain to a "finally" batch callback.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return \Illuminate\Bus\PendingBatch
*/
protected function attachRemainderOfChainToEndOfBatch(PendingBatch $batch)
{
if (! empty($this->chained)) {
$next = unserialize(array_shift($this->chained));
$next->chained = $this->chained;
$next->onConnection($next->connection ?: $this->chainConnection);
$next->onQueue($next->queue ?: $this->chainQueue);
$next->chainConnection = $this->chainConnection;
$next->chainQueue = $this->chainQueue;
$next->chainCatchCallbacks = $this->chainCatchCallbacks;
$batch->finally(function (Batch $batch) use ($next) {
if (! $batch->cancelled()) {
Container::getInstance()->make(Dispatcher::class)->dispatch($next);
}
});
$this->chained = [];
}
return $batch;
}
}

View File

@ -0,0 +1,403 @@
<?php
namespace Illuminate\Bus;
use Carbon\CarbonImmutable;
use Closure;
use DateTimeInterface;
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\PostgresConnection;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Str;
class DatabaseBatchRepository implements PrunableBatchRepository
{
/**
* The batch factory instance.
*
* @var \Illuminate\Bus\BatchFactory
*/
protected $factory;
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* The database table to use to store batch information.
*
* @var string
*/
protected $table;
/**
* Create a new batch repository instance.
*
* @param \Illuminate\Bus\BatchFactory $factory
* @param \Illuminate\Database\Connection $connection
* @param string $table
*/
public function __construct(BatchFactory $factory, Connection $connection, string $table)
{
$this->factory = $factory;
$this->connection = $connection;
$this->table = $table;
}
/**
* Retrieve a list of batches.
*
* @param int $limit
* @param mixed $before
* @return \Illuminate\Bus\Batch[]
*/
public function get($limit = 50, $before = null)
{
return $this->connection->table($this->table)
->orderByDesc('id')
->take($limit)
->when($before, fn ($q) => $q->where('id', '<', $before))
->get()
->map(function ($batch) {
return $this->toBatch($batch);
})
->all();
}
/**
* Retrieve information about an existing batch.
*
* @param string $batchId
* @return \Illuminate\Bus\Batch|null
*/
public function find(string $batchId)
{
$batch = $this->connection->table($this->table)
->useWritePdo()
->where('id', $batchId)
->first();
if ($batch) {
return $this->toBatch($batch);
}
}
/**
* Store a new pending batch.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return \Illuminate\Bus\Batch
*/
public function store(PendingBatch $batch)
{
$id = (string) Str::orderedUuid();
$this->connection->table($this->table)->insert([
'id' => $id,
'name' => $batch->name,
'total_jobs' => 0,
'pending_jobs' => 0,
'failed_jobs' => 0,
'failed_job_ids' => '[]',
'options' => $this->serialize($batch->options),
'created_at' => time(),
'cancelled_at' => null,
'finished_at' => null,
]);
return $this->find($id);
}
/**
* Increment the total number of jobs within the batch.
*
* @param string $batchId
* @param int $amount
* @return void
*/
public function incrementTotalJobs(string $batchId, int $amount)
{
$this->connection->table($this->table)->where('id', $batchId)->update([
'total_jobs' => new Expression('total_jobs + '.$amount),
'pending_jobs' => new Expression('pending_jobs + '.$amount),
'finished_at' => null,
]);
}
/**
* Decrement the total number of pending jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function decrementPendingJobs(string $batchId, string $jobId)
{
$values = $this->updateAtomicValues($batchId, function ($batch) use ($jobId) {
return [
'pending_jobs' => $batch->pending_jobs - 1,
'failed_jobs' => $batch->failed_jobs,
'failed_job_ids' => json_encode(array_values(array_diff((array) json_decode($batch->failed_job_ids, true), [$jobId]))),
];
});
return new UpdatedBatchJobCounts(
$values['pending_jobs'],
$values['failed_jobs']
);
}
/**
* Increment the total number of failed jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function incrementFailedJobs(string $batchId, string $jobId)
{
$values = $this->updateAtomicValues($batchId, function ($batch) use ($jobId) {
return [
'pending_jobs' => $batch->pending_jobs,
'failed_jobs' => $batch->failed_jobs + 1,
'failed_job_ids' => json_encode(array_values(array_unique(array_merge((array) json_decode($batch->failed_job_ids, true), [$jobId])))),
];
});
return new UpdatedBatchJobCounts(
$values['pending_jobs'],
$values['failed_jobs']
);
}
/**
* Update an atomic value within the batch.
*
* @param string $batchId
* @param \Closure $callback
* @return int|null
*/
protected function updateAtomicValues(string $batchId, Closure $callback)
{
return $this->connection->transaction(function () use ($batchId, $callback) {
$batch = $this->connection->table($this->table)->where('id', $batchId)
->lockForUpdate()
->first();
return is_null($batch) ? [] : tap($callback($batch), function ($values) use ($batchId) {
$this->connection->table($this->table)->where('id', $batchId)->update($values);
});
});
}
/**
* Mark the batch that has the given ID as finished.
*
* @param string $batchId
* @return void
*/
public function markAsFinished(string $batchId)
{
$this->connection->table($this->table)->where('id', $batchId)->update([
'finished_at' => time(),
]);
}
/**
* Cancel the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function cancel(string $batchId)
{
$this->connection->table($this->table)->where('id', $batchId)->update([
'cancelled_at' => time(),
'finished_at' => time(),
]);
}
/**
* Delete the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function delete(string $batchId)
{
$this->connection->table($this->table)->where('id', $batchId)->delete();
}
/**
* Prune all of the entries older than the given date.
*
* @param \DateTimeInterface $before
* @return int
*/
public function prune(DateTimeInterface $before)
{
$query = $this->connection->table($this->table)
->whereNotNull('finished_at')
->where('finished_at', '<', $before->getTimestamp());
$totalDeleted = 0;
do {
$deleted = $query->take(1000)->delete();
$totalDeleted += $deleted;
} while ($deleted !== 0);
return $totalDeleted;
}
/**
* Prune all of the unfinished entries older than the given date.
*
* @param \DateTimeInterface $before
* @return int
*/
public function pruneUnfinished(DateTimeInterface $before)
{
$query = $this->connection->table($this->table)
->whereNull('finished_at')
->where('created_at', '<', $before->getTimestamp());
$totalDeleted = 0;
do {
$deleted = $query->take(1000)->delete();
$totalDeleted += $deleted;
} while ($deleted !== 0);
return $totalDeleted;
}
/**
* Prune all of the cancelled entries older than the given date.
*
* @param \DateTimeInterface $before
* @return int
*/
public function pruneCancelled(DateTimeInterface $before)
{
$query = $this->connection->table($this->table)
->whereNotNull('cancelled_at')
->where('created_at', '<', $before->getTimestamp());
$totalDeleted = 0;
do {
$deleted = $query->take(1000)->delete();
$totalDeleted += $deleted;
} while ($deleted !== 0);
return $totalDeleted;
}
/**
* Execute the given Closure within a storage specific transaction.
*
* @param \Closure $callback
* @return mixed
*/
public function transaction(Closure $callback)
{
return $this->connection->transaction(fn () => $callback());
}
/**
* Rollback the last database transaction for the connection.
*
* @return void
*/
public function rollBack()
{
$this->connection->rollBack();
}
/**
* Serialize the given value.
*
* @param mixed $value
* @return string
*/
protected function serialize($value)
{
$serialized = serialize($value);
return $this->connection instanceof PostgresConnection
? base64_encode($serialized)
: $serialized;
}
/**
* Unserialize the given value.
*
* @param string $serialized
* @return mixed
*/
protected function unserialize($serialized)
{
if ($this->connection instanceof PostgresConnection &&
! Str::contains($serialized, [':', ';'])) {
$serialized = base64_decode($serialized);
}
try {
return unserialize($serialized);
} catch (ModelNotFoundException) {
return [];
}
}
/**
* Convert the given raw batch to a Batch object.
*
* @param object $batch
* @return \Illuminate\Bus\Batch
*/
protected function toBatch($batch)
{
return $this->factory->make(
$this,
$batch->id,
$batch->name,
(int) $batch->total_jobs,
(int) $batch->pending_jobs,
(int) $batch->failed_jobs,
(array) json_decode($batch->failed_job_ids, true),
$this->unserialize($batch->options),
CarbonImmutable::createFromTimestamp($batch->created_at),
$batch->cancelled_at ? CarbonImmutable::createFromTimestamp($batch->cancelled_at) : $batch->cancelled_at,
$batch->finished_at ? CarbonImmutable::createFromTimestamp($batch->finished_at) : $batch->finished_at
);
}
/**
* Get the underlying database connection.
*
* @return \Illuminate\Database\Connection
*/
public function getConnection()
{
return $this->connection;
}
/**
* Set the underlying database connection.
*
* @param \Illuminate\Database\Connection $connection
* @return void
*/
public function setConnection(Connection $connection)
{
$this->connection = $connection;
}
}

View File

@ -0,0 +1,296 @@
<?php
namespace Illuminate\Bus;
use Closure;
use Illuminate\Contracts\Bus\QueueingDispatcher;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Queue\Queue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\PendingChain;
use Illuminate\Pipeline\Pipeline;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Jobs\SyncJob;
use Illuminate\Support\Collection;
use RuntimeException;
class Dispatcher implements QueueingDispatcher
{
/**
* The container implementation.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The pipeline instance for the bus.
*
* @var \Illuminate\Pipeline\Pipeline
*/
protected $pipeline;
/**
* The pipes to send commands through before dispatching.
*
* @var array
*/
protected $pipes = [];
/**
* The command to handler mapping for non-self-handling events.
*
* @var array
*/
protected $handlers = [];
/**
* The queue resolver callback.
*
* @var \Closure|null
*/
protected $queueResolver;
/**
* Create a new command dispatcher instance.
*
* @param \Illuminate\Contracts\Container\Container $container
* @param \Closure|null $queueResolver
* @return void
*/
public function __construct(Container $container, Closure $queueResolver = null)
{
$this->container = $container;
$this->queueResolver = $queueResolver;
$this->pipeline = new Pipeline($container);
}
/**
* Dispatch a command to its appropriate handler.
*
* @param mixed $command
* @return mixed
*/
public function dispatch($command)
{
return $this->queueResolver && $this->commandShouldBeQueued($command)
? $this->dispatchToQueue($command)
: $this->dispatchNow($command);
}
/**
* Dispatch a command to its appropriate handler in the current process.
*
* Queueable jobs will be dispatched to the "sync" queue.
*
* @param mixed $command
* @param mixed $handler
* @return mixed
*/
public function dispatchSync($command, $handler = null)
{
if ($this->queueResolver &&
$this->commandShouldBeQueued($command) &&
method_exists($command, 'onConnection')) {
return $this->dispatchToQueue($command->onConnection('sync'));
}
return $this->dispatchNow($command, $handler);
}
/**
* Dispatch a command to its appropriate handler in the current process without using the synchronous queue.
*
* @param mixed $command
* @param mixed $handler
* @return mixed
*/
public function dispatchNow($command, $handler = null)
{
$uses = class_uses_recursive($command);
if (in_array(InteractsWithQueue::class, $uses) &&
in_array(Queueable::class, $uses) &&
! $command->job) {
$command->setJob(new SyncJob($this->container, json_encode([]), 'sync', 'sync'));
}
if ($handler || $handler = $this->getCommandHandler($command)) {
$callback = function ($command) use ($handler) {
$method = method_exists($handler, 'handle') ? 'handle' : '__invoke';
return $handler->{$method}($command);
};
} else {
$callback = function ($command) {
$method = method_exists($command, 'handle') ? 'handle' : '__invoke';
return $this->container->call([$command, $method]);
};
}
return $this->pipeline->send($command)->through($this->pipes)->then($callback);
}
/**
* Attempt to find the batch with the given ID.
*
* @param string $batchId
* @return \Illuminate\Bus\Batch|null
*/
public function findBatch(string $batchId)
{
return $this->container->make(BatchRepository::class)->find($batchId);
}
/**
* Create a new batch of queueable jobs.
*
* @param \Illuminate\Support\Collection|array|mixed $jobs
* @return \Illuminate\Bus\PendingBatch
*/
public function batch($jobs)
{
return new PendingBatch($this->container, Collection::wrap($jobs));
}
/**
* Create a new chain of queueable jobs.
*
* @param \Illuminate\Support\Collection|array $jobs
* @return \Illuminate\Foundation\Bus\PendingChain
*/
public function chain($jobs)
{
$jobs = Collection::wrap($jobs);
$jobs = ChainedBatch::prepareNestedBatches($jobs);
return new PendingChain($jobs->shift(), $jobs->toArray());
}
/**
* Determine if the given command has a handler.
*
* @param mixed $command
* @return bool
*/
public function hasCommandHandler($command)
{
return array_key_exists(get_class($command), $this->handlers);
}
/**
* Retrieve the handler for a command.
*
* @param mixed $command
* @return bool|mixed
*/
public function getCommandHandler($command)
{
if ($this->hasCommandHandler($command)) {
return $this->container->make($this->handlers[get_class($command)]);
}
return false;
}
/**
* Determine if the given command should be queued.
*
* @param mixed $command
* @return bool
*/
protected function commandShouldBeQueued($command)
{
return $command instanceof ShouldQueue;
}
/**
* Dispatch a command to its appropriate handler behind a queue.
*
* @param mixed $command
* @return mixed
*
* @throws \RuntimeException
*/
public function dispatchToQueue($command)
{
$connection = $command->connection ?? null;
$queue = call_user_func($this->queueResolver, $connection);
if (! $queue instanceof Queue) {
throw new RuntimeException('Queue resolver did not return a Queue implementation.');
}
if (method_exists($command, 'queue')) {
return $command->queue($queue, $command);
}
return $this->pushCommandToQueue($queue, $command);
}
/**
* Push the command onto the given queue instance.
*
* @param \Illuminate\Contracts\Queue\Queue $queue
* @param mixed $command
* @return mixed
*/
protected function pushCommandToQueue($queue, $command)
{
if (isset($command->queue, $command->delay)) {
return $queue->laterOn($command->queue, $command->delay, $command);
}
if (isset($command->queue)) {
return $queue->pushOn($command->queue, $command);
}
if (isset($command->delay)) {
return $queue->later($command->delay, $command);
}
return $queue->push($command);
}
/**
* Dispatch a command to its appropriate handler after the current process.
*
* @param mixed $command
* @param mixed $handler
* @return void
*/
public function dispatchAfterResponse($command, $handler = null)
{
$this->container->terminating(function () use ($command, $handler) {
$this->dispatchSync($command, $handler);
});
}
/**
* Set the pipes through which commands should be piped before dispatching.
*
* @param array $pipes
* @return $this
*/
public function pipeThrough(array $pipes)
{
$this->pipes = $pipes;
return $this;
}
/**
* Map a command to a handler.
*
* @param array $map
* @return $this
*/
public function map(array $map)
{
$this->handlers = array_merge($this->handlers, $map);
return $this;
}
}

View File

@ -0,0 +1,536 @@
<?php
namespace Illuminate\Bus;
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Marshaler;
use Carbon\CarbonImmutable;
use Closure;
use Illuminate\Support\Str;
class DynamoBatchRepository implements BatchRepository
{
/**
* The batch factory instance.
*
* @var \Illuminate\Bus\BatchFactory
*/
protected $factory;
/**
* The database connection instance.
*
* @var \Aws\DynamoDb\DynamoDbClient
*/
protected $dynamoDbClient;
/**
* The application name.
*
* @var string
*/
protected $applicationName;
/**
* The table to use to store batch information.
*
* @var string
*/
protected $table;
/**
* The time-to-live value for batch records.
*
* @var int
*/
protected $ttl;
/**
* The name of the time-to-live attribute for batch records.
*
* @var string
*/
protected $ttlAttribute;
/**
* The DynamoDB marshaler instance.
*
* @var \Aws\DynamoDb\Marshaler
*/
protected $marshaler;
/**
* Create a new batch repository instance.
*/
public function __construct(
BatchFactory $factory,
DynamoDbClient $dynamoDbClient,
string $applicationName,
string $table,
?int $ttl,
?string $ttlAttribute
) {
$this->factory = $factory;
$this->dynamoDbClient = $dynamoDbClient;
$this->applicationName = $applicationName;
$this->table = $table;
$this->ttl = $ttl;
$this->ttlAttribute = $ttlAttribute;
$this->marshaler = new Marshaler;
}
/**
* Retrieve a list of batches.
*
* @param int $limit
* @param mixed $before
* @return \Illuminate\Bus\Batch[]
*/
public function get($limit = 50, $before = null)
{
$condition = 'application = :application';
if ($before) {
$condition = 'application = :application AND id < :id';
}
$result = $this->dynamoDbClient->query([
'TableName' => $this->table,
'KeyConditionExpression' => $condition,
'ExpressionAttributeValues' => array_filter([
':application' => ['S' => $this->applicationName],
':id' => array_filter(['S' => $before]),
]),
'Limit' => $limit,
'ScanIndexForward' => false,
]);
return array_map(
fn ($b) => $this->toBatch($this->marshaler->unmarshalItem($b, mapAsObject: true)),
$result['Items']
);
}
/**
* Retrieve information about an existing batch.
*
* @param string $batchId
* @return \Illuminate\Bus\Batch|null
*/
public function find(string $batchId)
{
if ($batchId === '') {
return null;
}
$b = $this->dynamoDbClient->getItem([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
]);
if (! isset($b['Item'])) {
// If we didn't find it via a standard read, attempt consistent read...
$b = $this->dynamoDbClient->getItem([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'ConsistentRead' => true,
]);
if (! isset($b['Item'])) {
return null;
}
}
$batch = $this->marshaler->unmarshalItem($b['Item'], mapAsObject: true);
if ($batch) {
return $this->toBatch($batch);
}
}
/**
* Store a new pending batch.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return \Illuminate\Bus\Batch
*/
public function store(PendingBatch $batch)
{
$id = (string) Str::orderedUuid();
$batch = [
'id' => $id,
'name' => $batch->name,
'total_jobs' => 0,
'pending_jobs' => 0,
'failed_jobs' => 0,
'failed_job_ids' => [],
'options' => $this->serialize($batch->options ?? []),
'created_at' => time(),
'cancelled_at' => null,
'finished_at' => null,
];
if (! is_null($this->ttl)) {
$batch[$this->ttlAttribute] = time() + $this->ttl;
}
$this->dynamoDbClient->putItem([
'TableName' => $this->table,
'Item' => $this->marshaler->marshalItem(
array_merge(['application' => $this->applicationName], $batch)
),
]);
return $this->find($id);
}
/**
* Increment the total number of jobs within the batch.
*
* @param string $batchId
* @param int $amount
* @return void
*/
public function incrementTotalJobs(string $batchId, int $amount)
{
$update = 'SET total_jobs = total_jobs + :val, pending_jobs = pending_jobs + :val';
if ($this->ttl) {
$update = "SET total_jobs = total_jobs + :val, pending_jobs = pending_jobs + :val, #{$this->ttlAttribute} = :ttl";
}
$this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':val' => ['N' => "$amount"],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
'ReturnValues' => 'ALL_NEW',
]));
}
/**
* Decrement the total number of pending jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function decrementPendingJobs(string $batchId, string $jobId)
{
$update = 'SET pending_jobs = pending_jobs - :inc';
if ($this->ttl !== null) {
$update = "SET pending_jobs = pending_jobs - :inc, #{$this->ttlAttribute} = :ttl";
}
$batch = $this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':inc' => ['N' => '1'],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
'ReturnValues' => 'ALL_NEW',
]));
$values = $this->marshaler->unmarshalItem($batch['Attributes']);
return new UpdatedBatchJobCounts(
$values['pending_jobs'],
$values['failed_jobs']
);
}
/**
* Increment the total number of failed jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function incrementFailedJobs(string $batchId, string $jobId)
{
$update = 'SET failed_jobs = failed_jobs + :inc, failed_job_ids = list_append(failed_job_ids, :jobId)';
if ($this->ttl !== null) {
$update = "SET failed_jobs = failed_jobs + :inc, failed_job_ids = list_append(failed_job_ids, :jobId), #{$this->ttlAttribute} = :ttl";
}
$batch = $this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':jobId' => $this->marshaler->marshalValue([$jobId]),
':inc' => ['N' => '1'],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
'ReturnValues' => 'ALL_NEW',
]));
$values = $this->marshaler->unmarshalItem($batch['Attributes']);
return new UpdatedBatchJobCounts(
$values['pending_jobs'],
$values['failed_jobs']
);
}
/**
* Mark the batch that has the given ID as finished.
*
* @param string $batchId
* @return void
*/
public function markAsFinished(string $batchId)
{
$update = 'SET finished_at = :timestamp';
if ($this->ttl !== null) {
$update = "SET finished_at = :timestamp, #{$this->ttlAttribute} = :ttl";
}
$this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':timestamp' => ['N' => (string) time()],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
]));
}
/**
* Cancel the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function cancel(string $batchId)
{
$update = 'SET cancelled_at = :timestamp, finished_at = :timestamp';
if ($this->ttl !== null) {
$update = "SET cancelled_at = :timestamp, finished_at = :timestamp, #{$this->ttlAttribute} = :ttl";
}
$this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':timestamp' => ['N' => (string) time()],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
]));
}
/**
* Delete the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function delete(string $batchId)
{
$this->dynamoDbClient->deleteItem([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
]);
}
/**
* Execute the given Closure within a storage specific transaction.
*
* @param \Closure $callback
* @return mixed
*/
public function transaction(Closure $callback)
{
return $callback();
}
/**
* Rollback the last database transaction for the connection.
*
* @return void
*/
public function rollBack()
{
}
/**
* Convert the given raw batch to a Batch object.
*
* @param object $batch
* @return \Illuminate\Bus\Batch
*/
protected function toBatch($batch)
{
return $this->factory->make(
$this,
$batch->id,
$batch->name,
(int) $batch->total_jobs,
(int) $batch->pending_jobs,
(int) $batch->failed_jobs,
$batch->failed_job_ids,
$this->unserialize($batch->options) ?? [],
CarbonImmutable::createFromTimestamp($batch->created_at),
$batch->cancelled_at ? CarbonImmutable::createFromTimestamp($batch->cancelled_at) : $batch->cancelled_at,
$batch->finished_at ? CarbonImmutable::createFromTimestamp($batch->finished_at) : $batch->finished_at
);
}
/**
* Create the underlying DynamoDB table.
*
* @return void
*/
public function createAwsDynamoTable(): void
{
$definition = [
'TableName' => $this->table,
'AttributeDefinitions' => [
[
'AttributeName' => 'application',
'AttributeType' => 'S',
],
[
'AttributeName' => 'id',
'AttributeType' => 'S',
],
],
'KeySchema' => [
[
'AttributeName' => 'application',
'KeyType' => 'HASH',
],
[
'AttributeName' => 'id',
'KeyType' => 'RANGE',
],
],
'BillingMode' => 'PAY_PER_REQUEST',
];
$this->dynamoDbClient->createTable($definition);
if (! is_null($this->ttl)) {
$this->dynamoDbClient->updateTimeToLive([
'TableName' => $this->table,
'TimeToLiveSpecification' => [
'AttributeName' => $this->ttlAttribute,
'Enabled' => true,
],
]);
}
}
/**
* Delete the underlying DynamoDB table.
*/
public function deleteAwsDynamoTable(): void
{
$this->dynamoDbClient->deleteTable([
'TableName' => $this->table,
]);
}
/**
* Get the expiry time based on the configured time-to-live.
*
* @return string|null
*/
protected function getExpiryTime(): ?string
{
return is_null($this->ttl) ? null : (string) (time() + $this->ttl);
}
/**
* Get the expression attribute name for the time-to-live attribute.
*
* @return array
*/
protected function ttlExpressionAttributeName(): array
{
return is_null($this->ttl) ? [] : ["#{$this->ttlAttribute}" => $this->ttlAttribute];
}
/**
* Serialize the given value.
*
* @param mixed $value
* @return string
*/
protected function serialize($value)
{
return serialize($value);
}
/**
* Unserialize the given value.
*
* @param string $serialized
* @return mixed
*/
protected function unserialize($serialized)
{
return unserialize($serialized);
}
/**
* Get the underlying DynamoDB client instance.
*
* @return \Aws\DynamoDb\DynamoDbClient
*/
public function getDynamoClient(): DynamoDbClient
{
return $this->dynamoDbClient;
}
/**
* The the name of the table that contains the batch records.
*
* @return string
*/
public function getTable(): string
{
return $this->table;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Illuminate\Bus\Events;
use Illuminate\Bus\Batch;
class BatchDispatched
{
/**
* The batch instance.
*
* @var \Illuminate\Bus\Batch
*/
public $batch;
/**
* Create a new event instance.
*
* @param \Illuminate\Bus\Batch $batch
* @return void
*/
public function __construct(Batch $batch)
{
$this->batch = $batch;
}
}

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,417 @@
<?php
namespace Illuminate\Bus;
use Closure;
use Illuminate\Bus\Events\BatchDispatched;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Traits\Conditionable;
use Laravel\SerializableClosure\SerializableClosure;
use Throwable;
class PendingBatch
{
use Conditionable;
/**
* The IoC container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The batch name.
*
* @var string
*/
public $name = '';
/**
* The jobs that belong to the batch.
*
* @var \Illuminate\Support\Collection
*/
public $jobs;
/**
* The batch options.
*
* @var array
*/
public $options = [];
/**
* Create a new pending batch instance.
*
* @param \Illuminate\Contracts\Container\Container $container
* @param \Illuminate\Support\Collection $jobs
* @return void
*/
public function __construct(Container $container, Collection $jobs)
{
$this->container = $container;
$this->jobs = $jobs;
}
/**
* Add jobs to the batch.
*
* @param iterable|object|array $jobs
* @return $this
*/
public function add($jobs)
{
$jobs = is_iterable($jobs) ? $jobs : Arr::wrap($jobs);
foreach ($jobs as $job) {
$this->jobs->push($job);
}
return $this;
}
/**
* Add a callback to be executed when the batch is stored.
*
* @param callable $callback
* @return $this
*/
public function before($callback)
{
$this->options['before'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "before" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function beforeCallbacks()
{
return $this->options['before'] ?? [];
}
/**
* Add a callback to be executed after a job in the batch have executed successfully.
*
* @param callable $callback
* @return $this
*/
public function progress($callback)
{
$this->options['progress'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "progress" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function progressCallbacks()
{
return $this->options['progress'] ?? [];
}
/**
* Add a callback to be executed after all jobs in the batch have executed successfully.
*
* @param callable $callback
* @return $this
*/
public function then($callback)
{
$this->options['then'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "then" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function thenCallbacks()
{
return $this->options['then'] ?? [];
}
/**
* Add a callback to be executed after the first failing job in the batch.
*
* @param callable $callback
* @return $this
*/
public function catch($callback)
{
$this->options['catch'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "catch" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function catchCallbacks()
{
return $this->options['catch'] ?? [];
}
/**
* Add a callback to be executed after the batch has finished executing.
*
* @param callable $callback
* @return $this
*/
public function finally($callback)
{
$this->options['finally'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "finally" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function finallyCallbacks()
{
return $this->options['finally'] ?? [];
}
/**
* Indicate that the batch should not be cancelled when a job within the batch fails.
*
* @param bool $allowFailures
* @return $this
*/
public function allowFailures($allowFailures = true)
{
$this->options['allowFailures'] = $allowFailures;
return $this;
}
/**
* Determine if the pending batch allows jobs to fail without cancelling the batch.
*
* @return bool
*/
public function allowsFailures()
{
return Arr::get($this->options, 'allowFailures', false) === true;
}
/**
* Set the name for the batch.
*
* @param string $name
* @return $this
*/
public function name(string $name)
{
$this->name = $name;
return $this;
}
/**
* Specify the queue connection that the batched jobs should run on.
*
* @param string $connection
* @return $this
*/
public function onConnection(string $connection)
{
$this->options['connection'] = $connection;
return $this;
}
/**
* Get the connection used by the pending batch.
*
* @return string|null
*/
public function connection()
{
return $this->options['connection'] ?? null;
}
/**
* Specify the queue that the batched jobs should run on.
*
* @param string $queue
* @return $this
*/
public function onQueue(string $queue)
{
$this->options['queue'] = $queue;
return $this;
}
/**
* Get the queue used by the pending batch.
*
* @return string|null
*/
public function queue()
{
return $this->options['queue'] ?? null;
}
/**
* Add additional data into the batch's options array.
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function withOption(string $key, $value)
{
$this->options[$key] = $value;
return $this;
}
/**
* Dispatch the batch.
*
* @return \Illuminate\Bus\Batch
*
* @throws \Throwable
*/
public function dispatch()
{
$repository = $this->container->make(BatchRepository::class);
try {
$batch = $this->store($repository);
$batch = $batch->add($this->jobs);
} catch (Throwable $e) {
if (isset($batch)) {
$repository->delete($batch->id);
}
throw $e;
}
$this->container->make(EventDispatcher::class)->dispatch(
new BatchDispatched($batch)
);
return $batch;
}
/**
* Dispatch the batch after the response is sent to the browser.
*
* @return \Illuminate\Bus\Batch
*/
public function dispatchAfterResponse()
{
$repository = $this->container->make(BatchRepository::class);
$batch = $this->store($repository);
if ($batch) {
$this->container->terminating(function () use ($batch) {
$this->dispatchExistingBatch($batch);
});
}
return $batch;
}
/**
* Dispatch an existing batch.
*
* @param \Illuminate\Bus\Batch $batch
* @return void
*
* @throws \Throwable
*/
protected function dispatchExistingBatch($batch)
{
try {
$batch = $batch->add($this->jobs);
} catch (Throwable $e) {
if (isset($batch)) {
$batch->delete();
}
throw $e;
}
$this->container->make(EventDispatcher::class)->dispatch(
new BatchDispatched($batch)
);
}
/**
* Dispatch the batch if the given truth test passes.
*
* @param bool|\Closure $boolean
* @return \Illuminate\Bus\Batch|null
*/
public function dispatchIf($boolean)
{
return value($boolean) ? $this->dispatch() : null;
}
/**
* Dispatch the batch unless the given truth test passes.
*
* @param bool|\Closure $boolean
* @return \Illuminate\Bus\Batch|null
*/
public function dispatchUnless($boolean)
{
return ! value($boolean) ? $this->dispatch() : null;
}
/**
* Store the batch using the given repository.
*
* @param \Illuminate\Bus\BatchRepository $repository
* @return \Illuminate\Bus\Batch
*/
protected function store($repository)
{
$batch = $repository->store($this);
collect($this->beforeCallbacks())->each(function ($handler) use ($batch) {
try {
return $handler($batch);
} catch (Throwable $e) {
if (function_exists('report')) {
report($e);
}
}
});
return $batch;
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Illuminate\Bus;
use DateTimeInterface;
interface PrunableBatchRepository extends BatchRepository
{
/**
* Prune all of the entries older than the given date.
*
* @param \DateTimeInterface $before
* @return int
*/
public function prune(DateTimeInterface $before);
}

View File

@ -0,0 +1,276 @@
<?php
namespace Illuminate\Bus;
use Closure;
use Illuminate\Queue\CallQueuedClosure;
use Illuminate\Support\Arr;
use RuntimeException;
trait Queueable
{
/**
* The name of the connection the job should be sent to.
*
* @var string|null
*/
public $connection;
/**
* The name of the queue the job should be sent to.
*
* @var string|null
*/
public $queue;
/**
* The name of the connection the chain should be sent to.
*
* @var string|null
*/
public $chainConnection;
/**
* The name of the queue the chain should be sent to.
*
* @var string|null
*/
public $chainQueue;
/**
* The callbacks to be executed on chain failure.
*
* @var array|null
*/
public $chainCatchCallbacks;
/**
* The number of seconds before the job should be made available.
*
* @var \DateTimeInterface|\DateInterval|array|int|null
*/
public $delay;
/**
* Indicates whether the job should be dispatched after all database transactions have committed.
*
* @var bool|null
*/
public $afterCommit;
/**
* The middleware the job should be dispatched through.
*
* @var array
*/
public $middleware = [];
/**
* The jobs that should run if this job is successful.
*
* @var array
*/
public $chained = [];
/**
* Set the desired connection for the job.
*
* @param string|null $connection
* @return $this
*/
public function onConnection($connection)
{
$this->connection = $connection;
return $this;
}
/**
* Set the desired queue for the job.
*
* @param string|null $queue
* @return $this
*/
public function onQueue($queue)
{
$this->queue = $queue;
return $this;
}
/**
* Set the desired connection for the chain.
*
* @param string|null $connection
* @return $this
*/
public function allOnConnection($connection)
{
$this->chainConnection = $connection;
$this->connection = $connection;
return $this;
}
/**
* Set the desired queue for the chain.
*
* @param string|null $queue
* @return $this
*/
public function allOnQueue($queue)
{
$this->chainQueue = $queue;
$this->queue = $queue;
return $this;
}
/**
* Set the desired delay in seconds for the job.
*
* @param \DateTimeInterface|\DateInterval|array|int|null $delay
* @return $this
*/
public function delay($delay)
{
$this->delay = $delay;
return $this;
}
/**
* Indicate that the job should be dispatched after all database transactions have committed.
*
* @return $this
*/
public function afterCommit()
{
$this->afterCommit = true;
return $this;
}
/**
* Indicate that the job should not wait until database transactions have been committed before dispatching.
*
* @return $this
*/
public function beforeCommit()
{
$this->afterCommit = false;
return $this;
}
/**
* Specify the middleware the job should be dispatched through.
*
* @param array|object $middleware
* @return $this
*/
public function through($middleware)
{
$this->middleware = Arr::wrap($middleware);
return $this;
}
/**
* Set the jobs that should run if this job is successful.
*
* @param array $chain
* @return $this
*/
public function chain($chain)
{
$this->chained = collect($chain)->map(function ($job) {
return $this->serializeJob($job);
})->all();
return $this;
}
/**
* Prepend a job to the current chain so that it is run after the currently running job.
*
* @param mixed $job
* @return $this
*/
public function prependToChain($job)
{
$this->chained = Arr::prepend($this->chained, $this->serializeJob($job));
return $this;
}
/**
* Append a job to the end of the current chain.
*
* @param mixed $job
* @return $this
*/
public function appendToChain($job)
{
$this->chained = array_merge($this->chained, [$this->serializeJob($job)]);
return $this;
}
/**
* Serialize a job for queuing.
*
* @param mixed $job
* @return string
*
* @throws \RuntimeException
*/
protected function serializeJob($job)
{
if ($job instanceof Closure) {
if (! class_exists(CallQueuedClosure::class)) {
throw new RuntimeException(
'To enable support for closure jobs, please install the illuminate/queue package.'
);
}
$job = CallQueuedClosure::create($job);
}
return serialize($job);
}
/**
* Dispatch the next job on the chain.
*
* @return void
*/
public function dispatchNextJobInChain()
{
if (! empty($this->chained)) {
dispatch(tap(unserialize(array_shift($this->chained)), function ($next) {
$next->chained = $this->chained;
$next->onConnection($next->connection ?: $this->chainConnection);
$next->onQueue($next->queue ?: $this->chainQueue);
$next->chainConnection = $this->chainConnection;
$next->chainQueue = $this->chainQueue;
$next->chainCatchCallbacks = $this->chainCatchCallbacks;
}));
}
}
/**
* Invoke all of the chain's failed job callbacks.
*
* @param \Throwable $e
* @return void
*/
public function invokeChainCatchCallbacks($e)
{
collect($this->chainCatchCallbacks)->each(function ($callback) use ($e) {
$callback($e);
});
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Illuminate\Bus;
use Illuminate\Contracts\Cache\Repository as Cache;
class UniqueLock
{
/**
* The cache repository implementation.
*
* @var \Illuminate\Contracts\Cache\Repository
*/
protected $cache;
/**
* Create a new unique lock manager instance.
*
* @param \Illuminate\Contracts\Cache\Repository $cache
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
/**
* Attempt to acquire a lock for the given job.
*
* @param mixed $job
* @return bool
*/
public function acquire($job)
{
$uniqueFor = method_exists($job, 'uniqueFor')
? $job->uniqueFor()
: ($job->uniqueFor ?? 0);
$cache = method_exists($job, 'uniqueVia')
? $job->uniqueVia()
: $this->cache;
return (bool) $cache->lock($this->getKey($job), $uniqueFor)->get();
}
/**
* Release the lock for the given job.
*
* @param mixed $job
* @return void
*/
public function release($job)
{
$cache = method_exists($job, 'uniqueVia')
? $job->uniqueVia()
: $this->cache;
$cache->lock($this->getKey($job))->forceRelease();
}
/**
* Generate the lock key for the given job.
*
* @param mixed $job
* @return string
*/
protected function getKey($job)
{
$uniqueId = method_exists($job, 'uniqueId')
? $job->uniqueId()
: ($job->uniqueId ?? '');
return 'laravel_unique_job:'.get_class($job).$uniqueId;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Illuminate\Bus;
class UpdatedBatchJobCounts
{
/**
* The number of pending jobs remaining for the batch.
*
* @var int
*/
public $pendingJobs;
/**
* The number of failed jobs that belong to the batch.
*
* @var int
*/
public $failedJobs;
/**
* Create a new batch job counts object.
*
* @param int $pendingJobs
* @param int $failedJobs
* @return void
*/
public function __construct(int $pendingJobs = 0, int $failedJobs = 0)
{
$this->pendingJobs = $pendingJobs;
$this->failedJobs = $failedJobs;
}
/**
* Determine if all jobs have run exactly once.
*
* @return bool
*/
public function allJobsHaveRanExactlyOnce()
{
return ($this->pendingJobs - $this->failedJobs) === 0;
}
}

View File

@ -0,0 +1,40 @@
{
"name": "illuminate/bus",
"description": "The Illuminate Bus 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.1",
"illuminate/collections": "^10.0",
"illuminate/contracts": "^10.0",
"illuminate/pipeline": "^10.0",
"illuminate/support": "^10.0"
},
"autoload": {
"psr-4": {
"Illuminate\\Bus\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "10.x-dev"
}
},
"suggest": {
"illuminate/queue": "Required to use closures when chaining jobs (^7.0)."
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@ -0,0 +1,126 @@
<?php
namespace Illuminate\Cache;
class ApcStore extends TaggableStore
{
use RetrievesMultipleKeys;
/**
* The APC wrapper instance.
*
* @var \Illuminate\Cache\ApcWrapper
*/
protected $apc;
/**
* A string that should be prepended to keys.
*
* @var string
*/
protected $prefix;
/**
* Create a new APC store.
*
* @param \Illuminate\Cache\ApcWrapper $apc
* @param string $prefix
* @return void
*/
public function __construct(ApcWrapper $apc, $prefix = '')
{
$this->apc = $apc;
$this->prefix = $prefix;
}
/**
* Retrieve an item from the cache by key.
*
* @param string|array $key
* @return mixed
*/
public function get($key)
{
return $this->apc->get($this->prefix.$key);
}
/**
* Store an item in the cache for a given number of seconds.
*
* @param string $key
* @param mixed $value
* @param int $seconds
* @return bool
*/
public function put($key, $value, $seconds)
{
return $this->apc->put($this->prefix.$key, $value, $seconds);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int|bool
*/
public function increment($key, $value = 1)
{
return $this->apc->increment($this->prefix.$key, $value);
}
/**
* Decrement the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int|bool
*/
public function decrement($key, $value = 1)
{
return $this->apc->decrement($this->prefix.$key, $value);
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return bool
*/
public function forever($key, $value)
{
return $this->put($key, $value, 0);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return bool
*/
public function forget($key)
{
return $this->apc->delete($this->prefix.$key);
}
/**
* Remove all items from the cache.
*
* @return bool
*/
public function flush()
{
return $this->apc->flush();
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace Illuminate\Cache;
class ApcWrapper
{
/**
* Indicates if APCu is supported.
*
* @var bool
*/
protected $apcu = false;
/**
* Create a new APC wrapper instance.
*
* @return void
*/
public function __construct()
{
$this->apcu = function_exists('apcu_fetch');
}
/**
* Get an item from the cache.
*
* @param string $key
* @return mixed
*/
public function get($key)
{
$fetchedValue = $this->apcu ? apcu_fetch($key, $success) : apc_fetch($key, $success);
return $success ? $fetchedValue : null;
}
/**
* Store an item in the cache.
*
* @param string $key
* @param mixed $value
* @param int $seconds
* @return array|bool
*/
public function put($key, $value, $seconds)
{
return $this->apcu ? apcu_store($key, $value, $seconds) : apc_store($key, $value, $seconds);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int|bool
*/
public function increment($key, $value)
{
return $this->apcu ? apcu_inc($key, $value) : apc_inc($key, $value);
}
/**
* Decrement the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int|bool
*/
public function decrement($key, $value)
{
return $this->apcu ? apcu_dec($key, $value) : apc_dec($key, $value);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return bool
*/
public function delete($key)
{
return $this->apcu ? apcu_delete($key) : apc_delete($key);
}
/**
* Remove all items from the cache.
*
* @return bool
*/
public function flush()
{
return $this->apcu ? apcu_clear_cache() : apc_clear_cache('user');
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace Illuminate\Cache;
use Illuminate\Support\Carbon;
class ArrayLock extends Lock
{
/**
* The parent array cache store.
*
* @var \Illuminate\Cache\ArrayStore
*/
protected $store;
/**
* Create a new lock instance.
*
* @param \Illuminate\Cache\ArrayStore $store
* @param string $name
* @param int $seconds
* @param string|null $owner
* @return void
*/
public function __construct($store, $name, $seconds, $owner = null)
{
parent::__construct($name, $seconds, $owner);
$this->store = $store;
}
/**
* Attempt to acquire the lock.
*
* @return bool
*/
public function acquire()
{
$expiration = $this->store->locks[$this->name]['expiresAt'] ?? Carbon::now()->addSecond();
if ($this->exists() && $expiration->isFuture()) {
return false;
}
$this->store->locks[$this->name] = [
'owner' => $this->owner,
'expiresAt' => $this->seconds === 0 ? null : Carbon::now()->addSeconds($this->seconds),
];
return true;
}
/**
* Determine if the current lock exists.
*
* @return bool
*/
protected function exists()
{
return isset($this->store->locks[$this->name]);
}
/**
* Release the lock.
*
* @return bool
*/
public function release()
{
if (! $this->exists()) {
return false;
}
if (! $this->isOwnedByCurrentProcess()) {
return false;
}
$this->forceRelease();
return true;
}
/**
* Returns the owner value written into the driver for this lock.
*
* @return string
*/
protected function getCurrentOwner()
{
if (! $this->exists()) {
return null;
}
return $this->store->locks[$this->name]['owner'];
}
/**
* Releases this lock in disregard of ownership.
*
* @return void
*/
public function forceRelease()
{
unset($this->store->locks[$this->name]);
}
}

View File

@ -0,0 +1,219 @@
<?php
namespace Illuminate\Cache;
use Illuminate\Contracts\Cache\LockProvider;
use Illuminate\Support\Carbon;
use Illuminate\Support\InteractsWithTime;
class ArrayStore extends TaggableStore implements LockProvider
{
use InteractsWithTime, RetrievesMultipleKeys;
/**
* The array of stored values.
*
* @var array
*/
protected $storage = [];
/**
* The array of locks.
*
* @var array
*/
public $locks = [];
/**
* Indicates if values are serialized within the store.
*
* @var bool
*/
protected $serializesValues;
/**
* Create a new Array store.
*
* @param bool $serializesValues
* @return void
*/
public function __construct($serializesValues = false)
{
$this->serializesValues = $serializesValues;
}
/**
* Retrieve an item from the cache by key.
*
* @param string|array $key
* @return mixed
*/
public function get($key)
{
if (! isset($this->storage[$key])) {
return;
}
$item = $this->storage[$key];
$expiresAt = $item['expiresAt'] ?? 0;
if ($expiresAt !== 0 && (Carbon::now()->getPreciseTimestamp(3) / 1000) >= $expiresAt) {
$this->forget($key);
return;
}
return $this->serializesValues ? unserialize($item['value']) : $item['value'];
}
/**
* Store an item in the cache for a given number of seconds.
*
* @param string $key
* @param mixed $value
* @param int $seconds
* @return bool
*/
public function put($key, $value, $seconds)
{
$this->storage[$key] = [
'value' => $this->serializesValues ? serialize($value) : $value,
'expiresAt' => $this->calculateExpiration($seconds),
];
return true;
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int
*/
public function increment($key, $value = 1)
{
if (! is_null($existing = $this->get($key))) {
return tap(((int) $existing) + $value, function ($incremented) use ($key) {
$value = $this->serializesValues ? serialize($incremented) : $incremented;
$this->storage[$key]['value'] = $value;
});
}
$this->forever($key, $value);
return $value;
}
/**
* Decrement the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int
*/
public function decrement($key, $value = 1)
{
return $this->increment($key, $value * -1);
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return bool
*/
public function forever($key, $value)
{
return $this->put($key, $value, 0);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return bool
*/
public function forget($key)
{
if (array_key_exists($key, $this->storage)) {
unset($this->storage[$key]);
return true;
}
return false;
}
/**
* Remove all items from the cache.
*
* @return bool
*/
public function flush()
{
$this->storage = [];
return true;
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return '';
}
/**
* Get the expiration time of the key.
*
* @param int $seconds
* @return float
*/
protected function calculateExpiration($seconds)
{
return $this->toTimestamp($seconds);
}
/**
* Get the UNIX timestamp, with milliseconds, for the given number of seconds in the future.
*
* @param int $seconds
* @return float
*/
protected function toTimestamp($seconds)
{
return $seconds > 0 ? (Carbon::now()->getPreciseTimestamp(3) / 1000) + $seconds : 0;
}
/**
* Get a lock instance.
*
* @param string $name
* @param int $seconds
* @param string|null $owner
* @return \Illuminate\Contracts\Cache\Lock
*/
public function lock($name, $seconds = 0, $owner = null)
{
return new ArrayLock($this, $name, $seconds, $owner);
}
/**
* Restore a lock instance using the owner identifier.
*
* @param string $name
* @param string $owner
* @return \Illuminate\Contracts\Cache\Lock
*/
public function restoreLock($name, $owner)
{
return $this->lock($name, 0, $owner);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Illuminate\Cache;
class CacheLock extends Lock
{
/**
* The cache store implementation.
*
* @var \Illuminate\Contracts\Cache\Store
*/
protected $store;
/**
* Create a new lock instance.
*
* @param \Illuminate\Contracts\Cache\Store $store
* @param string $name
* @param int $seconds
* @param string|null $owner
* @return void
*/
public function __construct($store, $name, $seconds, $owner = null)
{
parent::__construct($name, $seconds, $owner);
$this->store = $store;
}
/**
* Attempt to acquire the lock.
*
* @return bool
*/
public function acquire()
{
if (method_exists($this->store, 'add') && $this->seconds > 0) {
return $this->store->add(
$this->name, $this->owner, $this->seconds
);
}
if (! is_null($this->store->get($this->name))) {
return false;
}
return ($this->seconds > 0)
? $this->store->put($this->name, $this->owner, $this->seconds)
: $this->store->forever($this->name, $this->owner);
}
/**
* Release the lock.
*
* @return bool
*/
public function release()
{
if ($this->isOwnedByCurrentProcess()) {
return $this->store->forget($this->name);
}
return false;
}
/**
* Releases this lock regardless of ownership.
*
* @return void
*/
public function forceRelease()
{
$this->store->forget($this->name);
}
/**
* Returns the owner value written into the driver for this lock.
*
* @return mixed
*/
protected function getCurrentOwner()
{
return $this->store->get($this->name);
}
}

View File

@ -0,0 +1,431 @@
<?php
namespace Illuminate\Cache;
use Aws\DynamoDb\DynamoDbClient;
use Closure;
use Illuminate\Contracts\Cache\Factory as FactoryContract;
use Illuminate\Contracts\Cache\Store;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Support\Arr;
use InvalidArgumentException;
/**
* @mixin \Illuminate\Cache\Repository
* @mixin \Illuminate\Contracts\Cache\LockProvider
*/
class CacheManager implements FactoryContract
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The array of resolved cache stores.
*
* @var array
*/
protected $stores = [];
/**
* The registered custom driver creators.
*
* @var array
*/
protected $customCreators = [];
/**
* Create a new Cache manager instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
/**
* Get a cache store instance by name, wrapped in a repository.
*
* @param string|null $name
* @return \Illuminate\Contracts\Cache\Repository
*/
public function store($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->stores[$name] ??= $this->resolve($name);
}
/**
* Get a cache driver instance.
*
* @param string|null $driver
* @return \Illuminate\Contracts\Cache\Repository
*/
public function driver($driver = null)
{
return $this->store($driver);
}
/**
* Resolve the given store.
*
* @param string $name
* @return \Illuminate\Contracts\Cache\Repository
*
* @throws \InvalidArgumentException
*/
public function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Cache store [{$name}] is not defined.");
}
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($config);
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($config);
}
throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
}
/**
* Call a custom driver creator.
*
* @param array $config
* @return mixed
*/
protected function callCustomCreator(array $config)
{
return $this->customCreators[$config['driver']]($this->app, $config);
}
/**
* Create an instance of the APC cache driver.
*
* @param array $config
* @return \Illuminate\Cache\Repository
*/
protected function createApcDriver(array $config)
{
$prefix = $this->getPrefix($config);
return $this->repository(new ApcStore(new ApcWrapper, $prefix));
}
/**
* Create an instance of the array cache driver.
*
* @param array $config
* @return \Illuminate\Cache\Repository
*/
protected function createArrayDriver(array $config)
{
return $this->repository(new ArrayStore($config['serialize'] ?? false));
}
/**
* Create an instance of the file cache driver.
*
* @param array $config
* @return \Illuminate\Cache\Repository
*/
protected function createFileDriver(array $config)
{
return $this->repository(
(new FileStore($this->app['files'], $config['path'], $config['permission'] ?? null))
->setLockDirectory($config['lock_path'] ?? null)
);
}
/**
* Create an instance of the Memcached cache driver.
*
* @param array $config
* @return \Illuminate\Cache\Repository
*/
protected function createMemcachedDriver(array $config)
{
$prefix = $this->getPrefix($config);
$memcached = $this->app['memcached.connector']->connect(
$config['servers'],
$config['persistent_id'] ?? null,
$config['options'] ?? [],
array_filter($config['sasl'] ?? [])
);
return $this->repository(new MemcachedStore($memcached, $prefix));
}
/**
* Create an instance of the Null cache driver.
*
* @return \Illuminate\Cache\Repository
*/
protected function createNullDriver()
{
return $this->repository(new NullStore);
}
/**
* Create an instance of the Redis cache driver.
*
* @param array $config
* @return \Illuminate\Cache\Repository
*/
protected function createRedisDriver(array $config)
{
$redis = $this->app['redis'];
$connection = $config['connection'] ?? 'default';
$store = new RedisStore($redis, $this->getPrefix($config), $connection);
return $this->repository(
$store->setLockConnection($config['lock_connection'] ?? $connection)
);
}
/**
* Create an instance of the database cache driver.
*
* @param array $config
* @return \Illuminate\Cache\Repository
*/
protected function createDatabaseDriver(array $config)
{
$connection = $this->app['db']->connection($config['connection'] ?? null);
$store = new DatabaseStore(
$connection,
$config['table'],
$this->getPrefix($config),
$config['lock_table'] ?? 'cache_locks',
$config['lock_lottery'] ?? [2, 100],
$config['lock_timeout'] ?? 86400,
);
return $this->repository($store->setLockConnection(
$this->app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null)
));
}
/**
* Create an instance of the DynamoDB cache driver.
*
* @param array $config
* @return \Illuminate\Cache\Repository
*/
protected function createDynamodbDriver(array $config)
{
$client = $this->newDynamodbClient($config);
return $this->repository(
new DynamoDbStore(
$client,
$config['table'],
$config['attributes']['key'] ?? 'key',
$config['attributes']['value'] ?? 'value',
$config['attributes']['expiration'] ?? 'expires_at',
$this->getPrefix($config)
)
);
}
/**
* Create new DynamoDb Client instance.
*
* @return \Aws\DynamoDb\DynamoDbClient
*/
protected function newDynamodbClient(array $config)
{
$dynamoConfig = [
'region' => $config['region'],
'version' => 'latest',
'endpoint' => $config['endpoint'] ?? null,
];
if (! empty($config['key']) && ! empty($config['secret'])) {
$dynamoConfig['credentials'] = Arr::only(
$config, ['key', 'secret']
);
}
if (! empty($config['token'])) {
$dynamoConfig['credentials']['token'] = $config['token'];
}
return new DynamoDbClient($dynamoConfig);
}
/**
* Create a new cache repository with the given implementation.
*
* @param \Illuminate\Contracts\Cache\Store $store
* @return \Illuminate\Cache\Repository
*/
public function repository(Store $store)
{
return tap(new Repository($store), function ($repository) {
$this->setEventDispatcher($repository);
});
}
/**
* Set the event dispatcher on the given repository instance.
*
* @param \Illuminate\Cache\Repository $repository
* @return void
*/
protected function setEventDispatcher(Repository $repository)
{
if (! $this->app->bound(DispatcherContract::class)) {
return;
}
$repository->setEventDispatcher(
$this->app[DispatcherContract::class]
);
}
/**
* Re-set the event dispatcher on all resolved cache repositories.
*
* @return void
*/
public function refreshEventDispatcher()
{
array_map([$this, 'setEventDispatcher'], $this->stores);
}
/**
* Get the cache prefix.
*
* @param array $config
* @return string
*/
protected function getPrefix(array $config)
{
return $config['prefix'] ?? $this->app['config']['cache.prefix'];
}
/**
* Get the cache connection configuration.
*
* @param string $name
* @return array|null
*/
protected function getConfig($name)
{
if (! is_null($name) && $name !== 'null') {
return $this->app['config']["cache.stores.{$name}"];
}
return ['driver' => 'null'];
}
/**
* Get the default cache driver name.
*
* @return string
*/
public function getDefaultDriver()
{
return $this->app['config']['cache.default'];
}
/**
* Set the default cache driver name.
*
* @param string $name
* @return void
*/
public function setDefaultDriver($name)
{
$this->app['config']['cache.default'] = $name;
}
/**
* Unset the given driver instances.
*
* @param array|string|null $name
* @return $this
*/
public function forgetDriver($name = null)
{
$name ??= $this->getDefaultDriver();
foreach ((array) $name as $cacheName) {
if (isset($this->stores[$cacheName])) {
unset($this->stores[$cacheName]);
}
}
return $this;
}
/**
* Disconnect the given driver and remove from local cache.
*
* @param string|null $name
* @return void
*/
public function purge($name = null)
{
$name ??= $this->getDefaultDriver();
unset($this->stores[$name]);
}
/**
* Register a custom driver creator Closure.
*
* @param string $driver
* @param \Closure $callback
* @return $this
*/
public function extend($driver, Closure $callback)
{
$this->customCreators[$driver] = $callback->bindTo($this, $this);
return $this;
}
/**
* Set the application instance used by the manager.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return $this
*/
public function setApplication($app)
{
$this->app = $app;
return $this;
}
/**
* Dynamically call the default driver instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->store()->$method(...$parameters);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Illuminate\Cache;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
use Symfony\Component\Cache\Adapter\Psr16Adapter;
class CacheServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('cache', function ($app) {
return new CacheManager($app);
});
$this->app->singleton('cache.store', function ($app) {
return $app['cache']->driver();
});
$this->app->singleton('cache.psr6', function ($app) {
return new Psr16Adapter($app['cache.store']);
});
$this->app->singleton('memcached.connector', function () {
return new MemcachedConnector;
});
$this->app->singleton(RateLimiter::class, function ($app) {
return new RateLimiter($app->make('cache')->driver(
$app['config']->get('cache.limiter')
));
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
'cache', 'cache.store', 'cache.psr6', 'memcached.connector', RateLimiter::class,
];
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Illuminate\Cache\Console;
use Illuminate\Console\MigrationGeneratorCommand;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'cache:table')]
class CacheTableCommand extends MigrationGeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'cache:table';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a migration for the cache database table';
/**
* Get the migration table name.
*
* @return string
*/
protected function migrationTableName()
{
return 'cache';
}
/**
* Get the path to the migration stub file.
*
* @return string
*/
protected function migrationStubFile()
{
return __DIR__.'/stubs/cache.stub';
}
}

Some files were not shown because too many files have changed in this diff Show More