Merge branch '11.x'

# Conflicts:
#	composer.json
This commit is contained in:
Dries Vints 2024-04-16 16:29:42 +02:00
commit 63797d30aa
No known key found for this signature in database
67 changed files with 2497 additions and 167 deletions

View File

@ -4,23 +4,6 @@ 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.
*
@ -29,7 +12,7 @@ class ApcWrapper
*/
public function get($key)
{
$fetchedValue = $this->apcu ? apcu_fetch($key, $success) : apc_fetch($key, $success);
$fetchedValue = apcu_fetch($key, $success);
return $success ? $fetchedValue : null;
}
@ -44,7 +27,7 @@ class ApcWrapper
*/
public function put($key, $value, $seconds)
{
return $this->apcu ? apcu_store($key, $value, $seconds) : apc_store($key, $value, $seconds);
return apcu_store($key, $value, $seconds);
}
/**
@ -56,7 +39,7 @@ class ApcWrapper
*/
public function increment($key, $value)
{
return $this->apcu ? apcu_inc($key, $value) : apc_inc($key, $value);
return apcu_inc($key, $value);
}
/**
@ -68,7 +51,7 @@ class ApcWrapper
*/
public function decrement($key, $value)
{
return $this->apcu ? apcu_dec($key, $value) : apc_dec($key, $value);
return apcu_dec($key, $value);
}
/**
@ -79,7 +62,7 @@ class ApcWrapper
*/
public function delete($key)
{
return $this->apcu ? apcu_delete($key) : apc_delete($key);
return apcu_delete($key);
}
/**
@ -89,6 +72,6 @@ class ApcWrapper
*/
public function flush()
{
return $this->apcu ? apcu_clear_cache() : apc_clear_cache('user');
return apcu_clear_cache();
}
}

View File

@ -288,8 +288,10 @@ class CacheManager implements FactoryContract
*/
public function repository(Store $store, array $config = [])
{
return tap(new Repository($store, Arr::only($config, ['store'])), function ($repository) {
$this->setEventDispatcher($repository);
return tap(new Repository($store, Arr::only($config, ['store'])), function ($repository) use ($config) {
if ($config['events'] ?? true) {
$this->setEventDispatcher($repository);
}
});
}

View File

@ -51,22 +51,24 @@ class Limit
* Create a new rate limit.
*
* @param int $maxAttempts
* @param int $decaySeconds
* @return static
*/
public static function perSecond($maxAttempts)
public static function perSecond($maxAttempts, $decaySeconds = 1)
{
return new static('', $maxAttempts, 1);
return new static('', $maxAttempts, $decaySeconds);
}
/**
* Create a new rate limit.
*
* @param int $maxAttempts
* @param int $decayMinutes
* @return static
*/
public static function perMinute($maxAttempts)
public static function perMinute($maxAttempts, $decayMinutes = 1)
{
return new static('', $maxAttempts, 60);
return new static('', $maxAttempts, 60 * $decayMinutes);
}
/**

View File

@ -1570,6 +1570,28 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable
});
}
/**
* Throttle the values, releasing them at most once per the given seconds.
*
* @return static<TKey, TValue>
*/
public function throttle(float $seconds)
{
return new static(function () use ($seconds) {
$microseconds = $seconds * 1_000_000;
foreach ($this as $key => $value) {
$fetchedAt = $this->preciseNow();
yield $key => $value;
$sleep = $microseconds - ($this->preciseNow() - $fetchedAt);
$this->usleep((int) $sleep);
}
});
}
/**
* Flatten a multi-dimensional associative array with dots.
*
@ -1781,4 +1803,32 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable
? Carbon::now()->timestamp
: time();
}
/**
* Get the precise current time.
*
* @return float
*/
protected function preciseNow()
{
return class_exists(Carbon::class)
? Carbon::now()->getPreciseTimestamp()
: microtime(true) * 1_000_000;
}
/**
* Sleep for the given amount of microseconds.
*
* @return void
*/
protected function usleep(int $microseconds)
{
if ($microseconds <= 0) {
return;
}
class_exists(Sleep::class)
? Sleep::usleep($microseconds)
: usleep($microseconds);
}
}

View File

@ -2,6 +2,7 @@
namespace Illuminate\Support\Traits;
use BackedEnum;
use CachingIterator;
use Closure;
use Exception;
@ -410,6 +411,10 @@ trait EnumeratesValues
*/
public function mapInto($class)
{
if (is_subclass_of($class, BackedEnum::class)) {
return $this->map(fn ($value, $key) => $class::from($value));
}
return $this->map(fn ($value, $key) => new $class($value, $key));
}

View File

@ -11,6 +11,7 @@ use Laravel\Prompts\Prompt;
use Laravel\Prompts\SearchPrompt;
use Laravel\Prompts\SelectPrompt;
use Laravel\Prompts\SuggestPrompt;
use Laravel\Prompts\TextareaPrompt;
use Laravel\Prompts\TextPrompt;
use stdClass;
use Symfony\Component\Console\Input\InputInterface;
@ -39,6 +40,12 @@ trait ConfiguresPrompts
$prompt->validate
));
TextareaPrompt::fallbackUsing(fn (TextareaPrompt $prompt) => $this->promptUntilValid(
fn () => $this->components->ask($prompt->label, $prompt->default ?: null, multiline: true) ?? '',
$prompt->required,
$prompt->validate
));
PasswordPrompt::fallbackUsing(fn (PasswordPrompt $prompt) => $this->promptUntilValid(
fn () => $this->components->secret($prompt->label) ?? '',
$prompt->required,
@ -52,28 +59,16 @@ trait ConfiguresPrompts
));
SelectPrompt::fallbackUsing(fn (SelectPrompt $prompt) => $this->promptUntilValid(
fn () => $this->components->choice($prompt->label, $prompt->options, $prompt->default),
fn () => $this->selectFallback($prompt->label, $prompt->options, $prompt->default),
false,
$prompt->validate
));
MultiSelectPrompt::fallbackUsing(function (MultiSelectPrompt $prompt) {
if ($prompt->default !== []) {
return $this->promptUntilValid(
fn () => $this->components->choice($prompt->label, $prompt->options, implode(',', $prompt->default), multiple: true),
$prompt->required,
$prompt->validate
);
}
return $this->promptUntilValid(
fn () => collect($this->components->choice($prompt->label, ['' => 'None', ...$prompt->options], 'None', multiple: true))
->reject('')
->all(),
$prompt->required,
$prompt->validate
);
});
MultiSelectPrompt::fallbackUsing(fn (MultiSelectPrompt $prompt) => $this->promptUntilValid(
fn () => $this->multiselectFallback($prompt->label, $prompt->options, $prompt->default, $prompt->required),
$prompt->required,
$prompt->validate
));
SuggestPrompt::fallbackUsing(fn (SuggestPrompt $prompt) => $this->promptUntilValid(
fn () => $this->components->askWithCompletion($prompt->label, $prompt->options, $prompt->default ?: null) ?? '',
@ -87,7 +82,7 @@ trait ConfiguresPrompts
$options = ($prompt->options)($query);
return $this->components->choice($prompt->label, $options);
return $this->selectFallback($prompt->label, $options);
},
false,
$prompt->validate
@ -99,21 +94,7 @@ trait ConfiguresPrompts
$options = ($prompt->options)($query);
if ($prompt->required === false) {
if (array_is_list($options)) {
return collect($this->components->choice($prompt->label, ['None', ...$options], 'None', multiple: true))
->reject('None')
->values()
->all();
}
return collect($this->components->choice($prompt->label, ['' => 'None', ...$options], '', multiple: true))
->reject('')
->values()
->all();
}
return $this->components->choice($prompt->label, $options, multiple: true);
return $this->multiselectFallback($prompt->label, $options, required: $prompt->required);
},
$prompt->required,
$prompt->validate
@ -192,7 +173,7 @@ trait ConfiguresPrompts
/**
* Get the validator instance that should be used to validate prompts.
*
* @param string $value
* @param mixed $field
* @param mixed $value
* @param mixed $rules
* @param array $messages
@ -238,4 +219,61 @@ trait ConfiguresPrompts
{
Prompt::setOutput($this->output);
}
/**
* Select fallback.
*
* @param string $label
* @param array $options
* @param string|int|null $default
* @return string|int
*/
private function selectFallback($label, $options, $default = null)
{
$answer = $this->components->choice($label, $options, $default);
if (! array_is_list($options) && $answer === (string) (int) $answer) {
return (int) $answer;
}
return $answer;
}
/**
* Multi-select fallback.
*
* @param string $label
* @param array $options
* @param array $default
* @param bool|string $required
* @return array
*/
private function multiselectFallback($label, $options, $default = [], $required = false)
{
$default = $default !== [] ? implode(',', $default) : null;
if ($required === false && ! $this->laravel->runningUnitTests()) {
$options = array_is_list($options)
? ['None', ...$options]
: ['' => 'None'] + $options;
if ($default === null) {
$default = 'None';
}
}
$answers = $this->components->choice($label, $options, $default, null, true);
if (! array_is_list($options)) {
$answers = array_map(fn ($value) => $value === (string) (int) $value ? (int) $value : $value, $answers);
}
if ($required === false) {
return array_is_list($options)
? array_values(array_filter($answers, fn ($value) => $value !== 'None'))
: array_filter($answers, fn ($value) => $value !== '');
}
return $answers;
}
}

View File

@ -154,19 +154,23 @@ class Schedule
*/
public function job($job, $queue = null, $connection = null)
{
$instance = is_string($job)
? Container::getInstance()->make($job)
: $job;
$jobName = $job;
$name = method_exists($instance, 'displayName')
? $instance->displayName()
: $instance::class;
if (! is_string($job)) {
$jobName = method_exists($job, 'displayName')
? $job->displayName()
: $job::class;
}
return $this->call(function () use ($instance, $queue, $connection) {
$instance instanceof ShouldQueue
? $this->dispatchToQueue($instance, $queue ?? $instance->queue, $connection ?? $instance->connection)
: $this->dispatchNow($instance);
})->name($name);
return $this->call(function () use ($job, $queue, $connection) {
$job = is_string($job) ? Container::getInstance()->make($job) : $job;
if ($job instanceof ShouldQueue) {
$this->dispatchToQueue($job, $queue ?? $job->queue, $connection ?? $job->connection);
} else {
$this->dispatchNow($job);
}
})->name($jobName);
}
/**

View File

@ -2,6 +2,8 @@
namespace Illuminate\Console\View\Components;
use Symfony\Component\Console\Question\Question;
class Ask extends Component
{
/**
@ -11,8 +13,13 @@ class Ask extends Component
* @param string $default
* @return mixed
*/
public function render($question, $default = null)
public function render($question, $default = null, $multiline = false)
{
return $this->usingQuestionHelper(fn () => $this->output->ask($question, $default));
return $this->usingQuestionHelper(
fn () => $this->output->askQuestion(
(new Question($question, $default))
->setMultiline($multiline)
)
);
}
}

View File

@ -20,10 +20,29 @@ class Choice extends Component
{
return $this->usingQuestionHelper(
fn () => $this->output->askQuestion(
(new ChoiceQuestion($question, $choices, $default))
$this->getChoiceQuestion($question, $choices, $default)
->setMaxAttempts($attempts)
->setMultiselect($multiple)
),
);
}
/**
* Get a ChoiceQuestion instance that handles array keys like Prompts.
*
* @param string $question
* @param array $choices
* @param mixed $default
* @return \Symfony\Component\Console\Question\ChoiceQuestion
*/
protected function getChoiceQuestion($question, $choices, $default)
{
return new class($question, $choices, $default) extends ChoiceQuestion
{
protected function isAssoc(array $array): bool
{
return ! array_is_list($array);
}
};
}
}

View File

@ -62,6 +62,10 @@ class FreshCommand extends Command
$database = $this->input->getOption('database');
if (! is_null($database)) {
$this->migrator->setConnection($database);
}
if ($this->migrator->repositoryExists()) {
$this->newLine();

View File

@ -17,6 +17,7 @@ use Illuminate\Database\RecordsNotFoundException;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use ReflectionClass;
@ -137,6 +138,13 @@ class Builder implements BuilderContract
*/
protected $removedScopes = [];
/**
* The callbacks that should be invoked after retrieving data from the database.
*
* @var array
*/
protected $afterQueryCallbacks = [];
/**
* Create a new Eloquent query builder instance.
*
@ -723,7 +731,9 @@ class Builder implements BuilderContract
$models = $builder->eagerLoadRelations($models);
}
return $builder->getModel()->newCollection($models);
return $this->applyAfterQueryCallbacks(
$builder->getModel()->newCollection($models)
);
}
/**
@ -852,6 +862,34 @@ class Builder implements BuilderContract
return str_contains($name, '.') && str_starts_with($name, $relation.'.');
}
/**
* Register a closure to be invoked after the query is executed.
*
* @param \Closure $callback
* @return $this
*/
public function afterQuery(Closure $callback)
{
$this->afterQueryCallbacks[] = $callback;
return $this;
}
/**
* Invoke the "after query" modification callbacks.
*
* @param mixed $result
* @return mixed
*/
public function applyAfterQueryCallbacks($result)
{
foreach ($this->afterQueryCallbacks as $afterQueryCallback) {
$result = $afterQueryCallback($result) ?: $result;
}
return $result;
}
/**
* Get a lazy collection for the given query.
*
@ -860,8 +898,10 @@ class Builder implements BuilderContract
public function cursor()
{
return $this->applyScopes()->query->cursor()->map(function ($record) {
return $this->newModelInstance()->newFromBuilder($record);
});
$model = $this->newModelInstance()->newFromBuilder($record);
return $this->applyAfterQueryCallbacks($this->newModelInstance()->newCollection([$model]))->first();
})->reject(fn ($model) => is_null($model));
}
/**
@ -900,9 +940,11 @@ class Builder implements BuilderContract
return $results;
}
return $results->map(function ($value) use ($column) {
return $this->model->newFromBuilder([$column => $value])->{$column};
});
return $this->applyAfterQueryCallbacks(
$results->map(function ($value) use ($column) {
return $this->model->newFromBuilder([$column => $value])->{$column};
})
);
}
/**

View File

@ -2,6 +2,7 @@
namespace Illuminate\Database\Eloquent\Relations;
use BackedEnum;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
@ -375,7 +376,9 @@ class BelongsTo extends Relation
*/
protected function getForeignKeyFrom(Model $model)
{
return $model->{$this->foreignKey};
$foreignKey = $model->{$this->foreignKey};
return $foreignKey instanceof BackedEnum ? $foreignKey->value : $foreignKey;
}
/**

View File

@ -883,7 +883,9 @@ class BelongsToMany extends Relation
$models = $builder->eagerLoadRelations($models);
}
return $this->related->newCollection($models);
return $this->query->applyAfterQueryCallbacks(
$this->related->newCollection($models)
);
}
/**

View File

@ -505,7 +505,9 @@ class HasManyThrough extends Relation
$models = $builder->eagerLoadRelations($models);
}
return $this->related->newCollection($models);
return $this->query->applyAfterQueryCallbacks(
$this->related->newCollection($models)
);
}
/**

View File

@ -208,6 +208,13 @@ class Builder implements BuilderContract
*/
public $beforeQueryCallbacks = [];
/**
* The callbacks that should be invoked after retrieving data from the database.
*
* @var array
*/
protected $afterQueryCallbacks = [];
/**
* All of the available clause operators.
*
@ -1215,7 +1222,7 @@ class Builder implements BuilderContract
$values = Arr::flatten($values);
foreach ($values as &$value) {
$value = (int) $value;
$value = (int) ($value instanceof BackedEnum ? $value->value : $value);
}
$this->wheres[] = compact('type', 'column', 'values', 'boolean');
@ -2770,6 +2777,34 @@ class Builder implements BuilderContract
$this->beforeQueryCallbacks = [];
}
/**
* Register a closure to be invoked after the query is executed.
*
* @param \Closure $callback
* @return $this
*/
public function afterQuery(Closure $callback)
{
$this->afterQueryCallbacks[] = $callback;
return $this;
}
/**
* Invoke the "after query" modification callbacks.
*
* @param mixed $result
* @return mixed
*/
public function applyAfterQueryCallbacks($result)
{
foreach ($this->afterQueryCallbacks as $afterQueryCallback) {
$result = $afterQueryCallback($result) ?: $result;
}
return $result;
}
/**
* Get the SQL representation of the query.
*
@ -2884,9 +2919,9 @@ class Builder implements BuilderContract
return $this->processor->processSelect($this, $this->runSelect());
}));
return isset($this->groupLimit)
? $this->withoutGroupLimitKeys($items)
: $items;
return $this->applyAfterQueryCallbacks(
isset($this->groupLimit) ? $this->withoutGroupLimitKeys($items) : $items
);
}
/**
@ -3114,11 +3149,13 @@ class Builder implements BuilderContract
$this->columns = ['*'];
}
return new LazyCollection(function () {
return (new LazyCollection(function () {
yield from $this->connection->cursor(
$this->toSql(), $this->getBindings(), ! $this->useWritePdo
);
});
}))->map(function ($item) {
return $this->applyAfterQueryCallbacks(collect([$item]))->first();
})->reject(fn ($item) => is_null($item));
}
/**
@ -3167,9 +3204,11 @@ class Builder implements BuilderContract
$key = $this->stripTableForPluck($key);
return is_array($queryResult[0])
return $this->applyAfterQueryCallbacks(
is_array($queryResult[0])
? $this->pluckFromArrayColumn($queryResult, $column, $key)
: $this->pluckFromObjectColumn($queryResult, $column, $key);
: $this->pluckFromObjectColumn($queryResult, $column, $key)
);
}
/**

View File

@ -125,7 +125,7 @@ class SqlServerGrammar extends Grammar
.'join sys.schemas as scm on obj.schema_id = scm.schema_id '
.'left join sys.default_constraints def on col.default_object_id = def.object_id and col.object_id = def.parent_object_id '
."left join sys.extended_properties as prop on obj.object_id = prop.major_id and col.column_id = prop.minor_id and prop.name = 'MS_Description' "
.'left join sys.computed_columns as com on col.column_id = com.column_id '
.'left join sys.computed_columns as com on col.column_id = com.column_id and col.object_id = com.object_id '
."where obj.type in ('U', 'V') and obj.name = %s and scm.name = %s "
.'order by col.column_id',
$this->quoteString($table),

View File

@ -163,10 +163,19 @@ class Encrypter implements EncrypterContract, StringEncrypter
$tag = empty($payload['tag']) ? null : base64_decode($payload['tag'])
);
$foundValidMac = false;
// Here we will decrypt the value. If we are able to successfully decrypt it
// we will then unserialize it and return it out to the caller. If we are
// unable to decrypt this value we will throw out an exception message.
foreach ($this->getAllKeys() as $key) {
if (
$this->shouldValidateMac() &&
! ($foundValidMac = $foundValidMac || $this->validMacForKey($payload, $key))
) {
continue;
}
$decrypted = \openssl_decrypt(
$payload['value'], strtolower($this->cipher), $key, 0, $iv, $tag ?? ''
);
@ -176,7 +185,11 @@ class Encrypter implements EncrypterContract, StringEncrypter
}
}
if ($decrypted === false) {
if ($this->shouldValidateMac() && ! $foundValidMac) {
throw new DecryptException('The MAC is invalid.');
}
if (($decrypted ?? false) === false) {
throw new DecryptException('Could not decrypt the data.');
}
@ -232,10 +245,6 @@ class Encrypter implements EncrypterContract, StringEncrypter
throw new DecryptException('The payload is invalid.');
}
if (! self::$supportedCiphers[strtolower($this->cipher)]['aead'] && ! $this->validMac($payload)) {
throw new DecryptException('The MAC is invalid.');
}
return $payload;
}
@ -265,24 +274,28 @@ class Encrypter implements EncrypterContract, StringEncrypter
}
/**
* Determine if the MAC for the given payload is valid.
* Determine if the MAC for the given payload is valid for the primary key.
*
* @param array $payload
* @return bool
*/
protected function validMac(array $payload)
{
foreach ($this->getAllKeys() as $key) {
$valid = hash_equals(
$this->hash($payload['iv'], $payload['value'], $key), $payload['mac']
);
return $this->validMacForKey($payload, $this->key);
}
if ($valid === true) {
return true;
}
}
return false;
/**
* Determine if the MAC is valid for the given payload and key.
*
* @param array $payload
* @param string $key
* @return bool
*/
protected function validMacForKey($payload, $key)
{
return hash_equals(
$this->hash($payload['iv'], $payload['value'], $key), $payload['mac']
);
}
/**
@ -302,6 +315,16 @@ class Encrypter implements EncrypterContract, StringEncrypter
}
}
/**
* Determine if we should validate the MAC while decrypting.
*
* @return bool
*/
protected function shouldValidateMac()
{
return ! self::$supportedCiphers[strtolower($this->cipher)]['aead'];
}
/**
* Get the encryption key that the encrypter is currently using.
*

View File

@ -108,7 +108,7 @@ class ApplicationBuilder
}
/**
* Register the braodcasting services for the application.
* Register the broadcasting services for the application.
*
* @param string $channels
* @param array $attributes

View File

@ -33,7 +33,7 @@ class EventServiceProvider extends ServiceProvider
protected $observers = [];
/**
* Indiates if events should be discovered.
* Indicates if events should be discovered.
*
* @var bool
*/

View File

@ -4,6 +4,8 @@ namespace Illuminate\Foundation\Testing\Concerns;
use Closure;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Support\Facades\Exceptions;
use Illuminate\Support\Testing\Fakes\ExceptionHandlerFake;
use Illuminate\Testing\Assert;
use Illuminate\Validation\ValidationException;
use Symfony\Component\Console\Application as ConsoleApplication;
@ -27,7 +29,11 @@ trait InteractsWithExceptionHandling
protected function withExceptionHandling()
{
if ($this->originalExceptionHandler) {
$this->app->instance(ExceptionHandler::class, $this->originalExceptionHandler);
$currentExceptionHandler = app(ExceptionHandler::class);
$currentExceptionHandler instanceof ExceptionHandlerFake
? $currentExceptionHandler->setHandler($this->originalExceptionHandler)
: $this->app->instance(ExceptionHandler::class, $this->originalExceptionHandler);
}
return $this;
@ -63,10 +69,14 @@ trait InteractsWithExceptionHandling
protected function withoutExceptionHandling(array $except = [])
{
if ($this->originalExceptionHandler == null) {
$this->originalExceptionHandler = app(ExceptionHandler::class);
$currentExceptionHandler = app(ExceptionHandler::class);
$this->originalExceptionHandler = $currentExceptionHandler instanceof ExceptionHandlerFake
? $currentExceptionHandler->handler()
: $currentExceptionHandler;
}
$this->app->instance(ExceptionHandler::class, new class($this->originalExceptionHandler, $except) implements ExceptionHandler
$exceptionHandler = new class($this->originalExceptionHandler, $except) implements ExceptionHandler, WithoutExceptionHandlingHandler
{
protected $except;
protected $originalHandler;
@ -145,7 +155,13 @@ trait InteractsWithExceptionHandling
{
(new ConsoleApplication)->renderThrowable($e, $output);
}
});
};
$currentExceptionHandler = app(ExceptionHandler::class);
$currentExceptionHandler instanceof ExceptionHandlerFake
? $currentExceptionHandler->setHandler($exceptionHandler)
: $this->app->instance(ExceptionHandler::class, $exceptionHandler);
return $this;
}

View File

@ -0,0 +1,8 @@
<?php
namespace Illuminate\Foundation\Testing\Concerns;
interface WithoutExceptionHandlingHandler
{
//
}

View File

@ -298,7 +298,7 @@ if (! function_exists('context')) {
* @param mixed $default
* @return mixed|\Illuminate\Log\Context\Repository
*/
function context($key, $default = null)
function context($key = null, $default = null)
{
return match (true) {
is_null($key) => app(ContextRepository::class),

View File

@ -764,6 +764,8 @@ class PendingRequest
* @param string $url
* @param array|string|null $query
* @return \Illuminate\Http\Client\Response
*
* @throws \Illuminate\Http\Client\ConnectionException
*/
public function get(string $url, $query = null)
{
@ -778,6 +780,8 @@ class PendingRequest
* @param string $url
* @param array|string|null $query
* @return \Illuminate\Http\Client\Response
*
* @throws \Illuminate\Http\Client\ConnectionException
*/
public function head(string $url, $query = null)
{
@ -792,6 +796,8 @@ class PendingRequest
* @param string $url
* @param array $data
* @return \Illuminate\Http\Client\Response
*
* @throws \Illuminate\Http\Client\ConnectionException
*/
public function post(string $url, $data = [])
{
@ -806,6 +812,8 @@ class PendingRequest
* @param string $url
* @param array $data
* @return \Illuminate\Http\Client\Response
*
* @throws \Illuminate\Http\Client\ConnectionException
*/
public function patch(string $url, $data = [])
{
@ -820,6 +828,8 @@ class PendingRequest
* @param string $url
* @param array $data
* @return \Illuminate\Http\Client\Response
*
* @throws \Illuminate\Http\Client\ConnectionException
*/
public function put(string $url, $data = [])
{
@ -834,6 +844,8 @@ class PendingRequest
* @param string $url
* @param array $data
* @return \Illuminate\Http\Client\Response
*
* @throws \Illuminate\Http\Client\ConnectionException
*/
public function delete(string $url, $data = [])
{
@ -870,6 +882,7 @@ class PendingRequest
* @return \Illuminate\Http\Client\Response
*
* @throws \Exception
* @throws \Illuminate\Http\Client\ConnectionException
*/
public function send(string $method, string $url, array $options = [])
{

View File

@ -94,9 +94,9 @@ class SesTransport extends AbstractTransport implements Stringable
}
/**
* Extract the SES list managenent options, if applicable.
* Extract the SES list management options, if applicable.
*
* @param \Illuminate\Mail\SentMessage $message
* @param \Symfony\Component\Mailer\SentMessage $message
* @return array|null
*/
protected function listManagementOptions(SentMessage $message)

View File

@ -14,6 +14,6 @@ class SyncConnector implements ConnectorInterface
*/
public function connect(array $config)
{
return new SyncQueue;
return new SyncQueue($config['after_commit'] ?? null);
}
}

View File

@ -12,6 +12,17 @@ use Throwable;
class SyncQueue extends Queue implements QueueContract
{
/**
* Create a new sync queue instance.
*
* @param bool $dispatchAfterCommit
* @return void
*/
public function __construct($dispatchAfterCommit = false)
{
$this->dispatchAfterCommit = $dispatchAfterCommit;
}
/**
* Get the size of the queue.
*

View File

@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException;
use Illuminate\Support\Reflector;
use Illuminate\Support\Str;
use ReflectionEnum;
class ImplicitRouteBinding
{
@ -74,6 +75,7 @@ class ImplicitRouteBinding
* @return \Illuminate\Routing\Route
*
* @throws \Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException
* @throws \ReflectionException
*/
protected static function resolveBackedEnumsForRoute($route, $parameters)
{
@ -86,7 +88,12 @@ class ImplicitRouteBinding
$backedEnumClass = $parameter->getType()?->getName();
$backedEnum = $backedEnumClass::tryFrom((string) $parameterValue);
$reflectionEnum = new ReflectionEnum($backedEnumClass);
match ($reflectionEnum->getBackingType()->getName()) {
'int' => $backedEnum = collect($backedEnumClass::cases())->first(fn ($case) => (string) $case->value === (string) $parameterValue),
'string' => $backedEnum = $backedEnumClass::tryFrom((string) $parameterValue),
};
if (is_null($backedEnum)) {
throw new BackedEnumCaseNotFoundException($backedEnumClass, $parameterValue);

View File

@ -28,7 +28,7 @@ class RouteSignatureParameters
return match (true) {
! empty($conditions['subClass']) => array_filter($parameters, fn ($p) => Reflector::isParameterSubclassOf($p, $conditions['subClass'])),
! empty($conditions['backedEnum']) => array_filter($parameters, fn ($p) => Reflector::isParameterBackedEnumWithStringBackingType($p)),
! empty($conditions['backedEnum']) => array_filter($parameters, fn ($p) => Reflector::isParameterBackedEnumWithValidBackingType($p)),
default => $parameters,
};
}

View File

@ -0,0 +1,68 @@
<?php
namespace Illuminate\Support\Facades;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Support\Arr;
use Illuminate\Support\Testing\Fakes\ExceptionHandlerFake;
/**
* @method static void register()
* @method static \Illuminate\Foundation\Exceptions\ReportableHandler reportable(callable $reportUsing)
* @method static \Illuminate\Foundation\Exceptions\Handler renderable(callable $renderUsing)
* @method static \Illuminate\Foundation\Exceptions\Handler map(\Closure|string $from, \Closure|string|null $to = null)
* @method static \Illuminate\Foundation\Exceptions\Handler dontReport(array|string $exceptions)
* @method static \Illuminate\Foundation\Exceptions\Handler ignore(array|string $exceptions)
* @method static \Illuminate\Foundation\Exceptions\Handler dontFlash(array|string $attributes)
* @method static \Illuminate\Foundation\Exceptions\Handler level(string $type, void $level)
* @method static void report(\Throwable $e)
* @method static bool shouldReport(\Throwable $e)
* @method static \Illuminate\Foundation\Exceptions\Handler throttleUsing(callable $throttleUsing)
* @method static \Illuminate\Foundation\Exceptions\Handler stopIgnoring(array|string $exceptions)
* @method static \Illuminate\Foundation\Exceptions\Handler buildContextUsing(\Closure $contextCallback)
* @method static \Symfony\Component\HttpFoundation\Response render(\Illuminate\Http\Request $request, \Throwable $e)
* @method static \Illuminate\Foundation\Exceptions\Handler respondUsing(callable $callback)
* @method static \Illuminate\Foundation\Exceptions\Handler shouldRenderJsonWhen(callable $callback)
* @method static \Illuminate\Foundation\Exceptions\Handler dontReportDuplicates()
* @method static \Illuminate\Contracts\Debug\ExceptionHandler handler()
* @method static void assertNothingReported()
* @method static void assertReported(\Closure|string $exception)
* @method static void assertReportedCount(int $count)
* @method static void assertNotReported(\Closure|string $exception)
* @method static void renderForConsole(\Symfony\Component\Console\Output\OutputInterface $output, \Throwable $e)
* @method static \Illuminate\Support\Testing\Fakes\ExceptionHandlerFake throwFirstReported()
* @method static \Illuminate\Support\Testing\Fakes\ExceptionHandlerFake setHandler(\Illuminate\Contracts\Debug\ExceptionHandler $handler)
*
* @see \Illuminate\Foundation\Exceptions\Handler
* @see \Illuminate\Contracts\Debug\ExceptionHandler
* @see \Illuminate\Support\Testing\Fakes\ExceptionHandlerFake
*/
class Exceptions extends Facade
{
/**
* Replace the bound instance with a fake.
*
* @param array<int, class-string<\Throwable>>|class-string<\Throwable> $exceptions
* @return \Illuminate\Support\Testing\Fakes\ExceptionHandlerFake
*/
public static function fake(array|string $exceptions = [])
{
$exceptionHandler = static::isFake()
? static::getFacadeRoot()->handler()
: static::getFacadeRoot();
return tap(new ExceptionHandlerFake($exceptionHandler, Arr::wrap($exceptions)), function ($fake) {
static::swap($fake);
});
}
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return ExceptionHandler::class;
}
}

View File

@ -64,7 +64,7 @@ abstract class MultipleInstanceManager
abstract public function getInstanceConfig($name);
/**
* Get an instance instance by name.
* Get an instance by name.
*
* @param string|null $name
* @return mixed

View File

@ -141,12 +141,12 @@ class Reflector
}
/**
* Determine if the parameter's type is a Backed Enum with a string backing type.
* Determine if the parameter's type is a Backed Enum with a string or int backing type.
*
* @param \ReflectionParameter $parameter
* @return bool
*/
public static function isParameterBackedEnumWithStringBackingType($parameter)
public static function isParameterBackedEnumWithValidBackingType($parameter)
{
if (! $parameter->getType() instanceof ReflectionNamedType) {
return false;
@ -162,7 +162,7 @@ class Reflector
$reflectionBackedEnum = new ReflectionEnum($backedEnumClass);
return $reflectionBackedEnum->isBacked()
&& $reflectionBackedEnum->getBackingType()->getName() == 'string';
&& in_array($reflectionBackedEnum->getBackingType()->getName(), ['int', 'string']);
}
return false;

View File

@ -0,0 +1,275 @@
<?php
namespace Illuminate\Support\Testing\Fakes;
use Closure;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Foundation\Testing\Concerns\WithoutExceptionHandlingHandler;
use Illuminate\Support\Traits\ForwardsCalls;
use Illuminate\Support\Traits\ReflectsClosures;
use Illuminate\Testing\Assert;
use PHPUnit\Framework\Assert as PHPUnit;
use PHPUnit\Framework\ExpectationFailedException;
use Throwable;
/**
* @mixin \Illuminate\Foundation\Exceptions\Handler
*/
class ExceptionHandlerFake implements ExceptionHandler, Fake
{
use ForwardsCalls, ReflectsClosures;
/**
* All of the exceptions that have been reported.
*
* @var array<int, \Throwable>
*/
protected $reported = [];
/**
* If the fake should throw exceptions when they are reported.
*
* @var bool
*/
protected $throwOnReport = false;
/**
* Create a new exception handler fake.
*
* @param \Illuminate\Contracts\Debug\ExceptionHandler $handler
* @param array<int, class-string<\Throwable>> $exceptions
* @return void
*/
public function __construct(
protected ExceptionHandler $handler,
protected array $exceptions = [],
) {
//
}
/**
* Get the underlying handler implementation.
*
* @return \Illuminate\Contracts\Debug\ExceptionHandler
*/
public function handler()
{
return $this->handler;
}
/**
* Assert if an exception of the given type has been reported.
*
* @param \Closure|string $exception
* @return void
*/
public function assertReported(Closure|string $exception)
{
$message = sprintf(
'The expected [%s] exception was not reported.',
is_string($exception) ? $exception : $this->firstClosureParameterType($exception)
);
if (is_string($exception)) {
Assert::assertTrue(
in_array($exception, array_map('get_class', $this->reported), true),
$message,
);
return;
}
Assert::assertTrue(
collect($this->reported)->contains(
fn (Throwable $e) => $this->firstClosureParameterType($exception) === get_class($e)
&& $exception($e) === true,
), $message,
);
}
/**
* Assert the number of exceptions that have been reported.
*
* @param int $count
* @return void
*/
public function assertReportedCount(int $count)
{
$total = collect($this->reported)->count();
PHPUnit::assertSame(
$count, $total,
"The total number of exceptions reported was {$total} instead of {$count}."
);
}
/**
* Assert if an exception of the given type has not been reported.
*
* @param \Closure|string $exception
* @return void
*/
public function assertNotReported(Closure|string $exception)
{
try {
$this->assertReported($exception);
} catch (ExpectationFailedException $e) {
return;
}
throw new ExpectationFailedException(sprintf(
'The expected [%s] exception was not reported.',
is_string($exception) ? $exception : $this->firstClosureParameterType($exception)
));
}
/**
* Assert nothing has been reported.
*
* @return void
*/
public function assertNothingReported()
{
Assert::assertEmpty(
$this->reported,
sprintf(
'The following exceptions were reported: %s.',
implode(', ', array_map('get_class', $this->reported)),
),
);
}
/**
* Report or log an exception.
*
* @param \Throwable $e
* @return void
*/
public function report($e)
{
if (! $this->isFakedException($e)) {
$this->handler->report($e);
return;
}
if (! $this->shouldReport($e)) {
return;
}
$this->reported[] = $e;
if ($this->throwOnReport) {
throw $e;
}
}
/**
* Determine if the given exception is faked.
*
* @param \Throwable $e
* @return bool
*/
protected function isFakedException(Throwable $e)
{
return count($this->exceptions) === 0 || in_array(get_class($e), $this->exceptions, true);
}
/**
* Determine if the exception should be reported.
*
* @param \Throwable $e
* @return bool
*/
public function shouldReport($e)
{
return $this->runningWithoutExceptionHandling() || $this->handler->shouldReport($e);
}
/**
* Determine if the handler is running without exception handling.
*
* @return bool
*/
protected function runningWithoutExceptionHandling()
{
return $this->handler instanceof WithoutExceptionHandlingHandler;
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Throwable $e
* @return \Symfony\Component\HttpFoundation\Response
*/
public function render($request, $e)
{
return $this->handler->render($request, $e);
}
/**
* Render an exception to the console.
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param \Throwable $e
* @return void
*/
public function renderForConsole($output, Throwable $e)
{
$this->handler->renderForConsole($output, $e);
}
/**
* Throw exceptions when they are reported.
*
* @return $this
*/
public function throwOnReport()
{
$this->throwOnReport = true;
return $this;
}
/**
* Throw the first reported exception.
*
* @return $this
*
* @throws \Throwable
*/
public function throwFirstReported()
{
foreach ($this->reported as $e) {
throw $e;
}
return $this;
}
/**
* Set the "original" handler that should be used by the fake.
*
* @param \Illuminate\Contracts\Debug\ExceptionHandler $handler
* @return $this
*/
public function setHandler(ExceptionHandler $handler)
{
$this->handler = $handler;
return $this;
}
/**
* Handle dynamic method calls to the mailer.
*
* @param string $method
* @param array<string, mixed> $parameters
* @return mixed
*/
public function __call(string $method, array $parameters)
{
return $this->forwardCallTo($this->handler, $method, $parameters);
}
}

View File

@ -137,6 +137,7 @@ return [
'required_array_keys' => 'The :attribute field must contain entries for: :values.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_if_accepted' => 'The :attribute field is required when :other is accepted.',
'required_if_declined' => 'The :attribute field is required when :other is declined.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values are present.',

View File

@ -609,6 +609,22 @@ trait ReplacesAttributes
return str_replace([':other'], $parameters, $message);
}
/**
* Replace all place-holders for the required_if_declined rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
public function replaceRequiredIfDeclined($message, $attribute, $rule, $parameters)
{
$parameters[0] = $this->getDisplayableAttribute($parameters[0]);
return str_replace([':other'], $parameters, $message);
}
/**
* Replace all place-holders for the required_unless rule.
*

View File

@ -1961,6 +1961,25 @@ trait ValidatesAttributes
return true;
}
/**
* Validate that an attribute exists when another attribute was "declined".
*
* @param string $attribute
* @param mixed $value
* @param mixed $parameters
* @return bool
*/
public function validateRequiredIfDeclined($attribute, $value, $parameters)
{
$this->requireParameterCount(1, $parameters, 'required_if_declined');
if ($this->validateDeclined($parameters[0], $this->getValue($parameters[0]))) {
return $this->validateRequired($attribute, $value);
}
return true;
}
/**
* Validate that an attribute does not exist or is an empty string.
*

View File

@ -221,6 +221,7 @@ class Validator implements ValidatorContract
'Required',
'RequiredIf',
'RequiredIfAccepted',
'RequiredIfDeclined',
'RequiredUnless',
'RequiredWith',
'RequiredWithAll',
@ -252,6 +253,7 @@ class Validator implements ValidatorContract
'DeclinedIf',
'RequiredIf',
'RequiredIfAccepted',
'RequiredIfDeclined',
'RequiredUnless',
'RequiredWith',
'RequiredWithAll',

View File

@ -502,8 +502,7 @@ class ComponentAttributeBag implements ArrayAccess, IteratorAggregate, JsonSeria
}
if ($value === true) {
// Exception for Alpine...
$value = $key === 'x-data' ? '' : $key;
$value = $key === 'x-data' || str_starts_with($key, 'wire:') ? '' : $key;
}
$string .= ' '.$key.'="'.str_replace('"', '\\"', trim($value)).'"';

View File

@ -88,7 +88,7 @@ class InvokableComponentVariable implements DeferringDisplayableValue, IteratorA
/**
* Resolve the variable as a string.
*
* @return mixed
* @return string
*/
public function __toString()
{

View File

@ -277,6 +277,36 @@ class CacheManagerTest extends TestCase
$cacheManager->store('alien_store');
}
public function testMakesRepositoryWithoutDispatcherWhenEventsDisabled()
{
$userConfig = [
'cache' => [
'stores' => [
'my_store' => [
'driver' => 'array',
],
'my_store_without_events' => [
'driver' => 'array',
'events' => false,
],
],
],
];
$app = $this->getApp($userConfig);
$app->bind(Dispatcher::class, fn () => new Event);
$cacheManager = new CacheManager($app);
// The repository will have an event dispatcher
$repo = $cacheManager->store('my_store');
$this->assertNotNull($repo->getEventDispatcher());
// This repository will not have an event dispatcher as 'with_events' is false
$repoWithoutEvents = $cacheManager->store('my_store_without_events');
$this->assertNull($repoWithoutEvents->getEventDispatcher());
}
protected function getApp(array $userConfig)
{
$app = new Container;

View File

@ -18,10 +18,18 @@ class LimitTest extends TestCase
$this->assertSame(1, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);
$limit = Limit::perSecond(3, 5);
$this->assertSame(5, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);
$limit = Limit::perMinute(3);
$this->assertSame(60, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);
$limit = Limit::perMinute(3, 4);
$this->assertSame(240, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);
$limit = Limit::perMinutes(2, 3);
$this->assertSame(120, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);
@ -30,10 +38,18 @@ class LimitTest extends TestCase
$this->assertSame(3600, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);
$limit = Limit::perHour(3, 2);
$this->assertSame(7200, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);
$limit = Limit::perDay(3);
$this->assertSame(86400, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);
$limit = Limit::perDay(3, 5);
$this->assertSame(432000, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);
$limit = new GlobalLimit(3);
$this->assertSame(60, $limit->decaySeconds);
$this->assertSame(3, $limit->maxAttempts);

View File

@ -56,6 +56,9 @@ class RepositoryTest extends TestCase
$this->assertNull(
$this->repository->get('a.b.c')
);
$this->assertNull($this->repository->get('x.y.z'));
$this->assertNull($this->repository->get('.'));
}
public function testGetBooleanValue()
@ -178,10 +181,13 @@ class RepositoryTest extends TestCase
{
$this->assertSame('aaa', $this->repository->get('array.0'));
$this->assertSame('zzz', $this->repository->get('array.1'));
$this->repository->prepend('array', 'xxx');
$this->assertSame('xxx', $this->repository->get('array.0'));
$this->assertSame('aaa', $this->repository->get('array.1'));
$this->assertSame('zzz', $this->repository->get('array.2'));
$this->assertNull($this->repository->get('array.3'));
$this->assertCount(3, $this->repository->get('array'));
}
public function testPush()
@ -192,6 +198,8 @@ class RepositoryTest extends TestCase
$this->assertSame('aaa', $this->repository->get('array.0'));
$this->assertSame('zzz', $this->repository->get('array.1'));
$this->assertSame('xxx', $this->repository->get('array.2'));
$this->assertCount(3, $this->repository->get('array'));
}
public function testPrependWithNewKey()
@ -213,8 +221,21 @@ class RepositoryTest extends TestCase
public function testOffsetExists()
{
$data = [
'foo' => 'bar',
'null_value' => null,
'empty_string' => '',
'numeric_value' => 123,
];
$this->repository->set($data);
$this->assertTrue(isset($this->repository['foo']));
$this->assertFalse(isset($this->repository['not-exist']));
$this->assertTrue(isset($this->repository['null_value']));
$this->assertTrue(isset($this->repository['empty_string']));
$this->assertTrue(isset($this->repository['numeric_value']));
$this->assertFalse(isset($this->repository[-1]));
$this->assertFalse(isset($this->repository['non_numeric']));
}
public function testOffsetGet()
@ -234,6 +255,18 @@ class RepositoryTest extends TestCase
$this->repository['key'] = 'value';
$this->assertSame('value', $this->repository['key']);
$this->repository['key'] = 'new_value';
$this->assertSame('new_value', $this->repository['key']);
$this->repository['new_key'] = null;
$this->assertNull($this->repository['new_key']);
$this->repository[''] = 'value';
$this->assertSame('value', $this->repository['']);
$this->repository[123] = '123';
$this->assertSame('123', $this->repository[123]);
}
public function testOffsetUnset()
@ -259,7 +292,7 @@ class RepositoryTest extends TestCase
public function testItGetsAsString(): void
{
$this->assertSame(
$this->repository->string('a.b'), 'c'
'c', $this->repository->string('a.b')
);
}

View File

@ -0,0 +1,128 @@
<?php
namespace Illuminate\Tests\Console;
use Illuminate\Console\Application;
use Illuminate\Console\Command;
use Illuminate\Console\OutputStyle;
use Illuminate\Console\View\Components\Factory;
use Laravel\Prompts\Prompt;
use Mockery as m;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use function Laravel\Prompts\multiselect;
use function Laravel\Prompts\select;
class ConfiguresPromptsTest extends TestCase
{
protected function tearDown(): void
{
m::close();
}
#[DataProvider('selectDataProvider')]
public function testSelectFallback($prompt, $expectedOptions, $expectedDefault, $return, $expectedReturn)
{
Prompt::fallbackWhen(true);
$command = new class($prompt) extends Command
{
public $answer;
public function __construct(protected $prompt)
{
parent::__construct();
}
public function handle()
{
$this->answer = ($this->prompt)();
}
};
$this->runCommand($command, fn ($components) => $components
->expects('choice')
->with('Test', $expectedOptions, $expectedDefault)
->andReturn($return)
);
$this->assertSame($expectedReturn, $command->answer);
}
public static function selectDataProvider()
{
return [
'list with no default' => [fn () => select('Test', ['a', 'b', 'c']), ['a', 'b', 'c'], null, 'b', 'b'],
'numeric keys with no default' => [fn () => select('Test', [1 => 'a', 2 => 'b', 3 => 'c']), [1 => 'a', 2 => 'b', 3 => 'c'], null, '2', 2],
'assoc with no default' => [fn () => select('Test', ['a' => 'A', 'b' => 'B', 'c' => 'C']), ['a' => 'A', 'b' => 'B', 'c' => 'C'], null, 'b', 'b'],
'list with default' => [fn () => select('Test', ['a', 'b', 'c'], 'b'), ['a', 'b', 'c'], 'b', 'b', 'b'],
'numeric keys with default' => [fn () => select('Test', [1 => 'a', 2 => 'b', 3 => 'c'], 2), [1 => 'a', 2 => 'b', 3 => 'c'], 2, '2', 2],
'assoc with default' => [fn () => select('Test', ['a' => 'A', 'b' => 'B', 'c' => 'C'], 'b'), ['a' => 'A', 'b' => 'B', 'c' => 'C'], 'b', 'b', 'b'],
];
}
#[DataProvider('multiselectDataProvider')]
public function testMultiselectFallback($prompt, $expectedOptions, $expectedDefault, $return, $expectedReturn)
{
Prompt::fallbackWhen(true);
$command = new class($prompt) extends Command
{
public $answer;
public function __construct(protected $prompt)
{
parent::__construct();
}
public function handle()
{
$this->answer = ($this->prompt)();
}
};
$this->runCommand($command, fn ($components) => $components
->expects('choice')
->with('Test', $expectedOptions, $expectedDefault, null, true)
->andReturn($return)
);
$this->assertSame($expectedReturn, $command->answer);
}
public static function multiselectDataProvider()
{
return [
'list with no default' => [fn () => multiselect('Test', ['a', 'b', 'c']), ['None', 'a', 'b', 'c'], 'None', ['None'], []],
'numeric keys with no default' => [fn () => multiselect('Test', [1 => 'a', 2 => 'b', 3 => 'c']), ['' => 'None', 1 => 'a', 2 => 'b', 3 => 'c'], 'None', [''], []],
'assoc with no default' => [fn () => multiselect('Test', ['a' => 'A', 'b' => 'B', 'c' => 'C']), ['' => 'None', 'a' => 'A', 'b' => 'B', 'c' => 'C'], 'None', [''], []],
'list with default' => [fn () => multiselect('Test', ['a', 'b', 'c'], ['b', 'c']), ['None', 'a', 'b', 'c'], 'b,c', ['b', 'c'], ['b', 'c']],
'numeric keys with default' => [fn () => multiselect('Test', [1 => 'a', 2 => 'b', 3 => 'c'], [2, 3]), ['' => 'None', 1 => 'a', 2 => 'b', 3 => 'c'], '2,3', ['2', '3'], [2, 3]],
'assoc with default' => [fn () => multiselect('Test', ['a' => 'A', 'b' => 'B', 'c' => 'C'], ['b', 'c']), ['' => 'None', 'a' => 'A', 'b' => 'B', 'c' => 'C'], 'b,c', ['b', 'c'], ['b', 'c']],
'required list with no default' => [fn () => multiselect('Test', ['a', 'b', 'c'], required: true), ['a', 'b', 'c'], null, ['b', 'c'], ['b', 'c']],
'required numeric keys with no default' => [fn () => multiselect('Test', [1 => 'a', 2 => 'b', 3 => 'c'], required: true), [1 => 'a', 2 => 'b', 3 => 'c'], null, ['2', '3'], [2, 3]],
'required assoc with no default' => [fn () => multiselect('Test', ['a' => 'A', 'b' => 'B', 'c' => 'C'], required: true), ['a' => 'A', 'b' => 'B', 'c' => 'C'], null, ['b', 'c'], ['b', 'c']],
'required list with default' => [fn () => multiselect('Test', ['a', 'b', 'c'], ['b', 'c'], required: true), ['a', 'b', 'c'], 'b,c', ['b', 'c'], ['b', 'c']],
'required numeric keys with default' => [fn () => multiselect('Test', [1 => 'a', 2 => 'b', 3 => 'c'], [2, 3], required: true), [1 => 'a', 2 => 'b', 3 => 'c'], '2,3', ['2', '3'], [2, 3]],
'required assoc with default' => [fn () => multiselect('Test', ['a' => 'A', 'b' => 'B', 'c' => 'C'], ['b', 'c'], required: true), ['a' => 'A', 'b' => 'B', 'c' => 'C'], 'b,c', ['b', 'c'], ['b', 'c']],
];
}
protected function runCommand($command, $expectations)
{
$command->setLaravel($application = m::mock(Application::class));
$application->shouldReceive('make')->withArgs(fn ($abstract) => $abstract === OutputStyle::class)->andReturn($outputStyle = m::mock(OutputStyle::class));
$application->shouldReceive('make')->withArgs(fn ($abstract) => $abstract === Factory::class)->andReturn($factory = m::mock(Factory::class));
$application->shouldReceive('runningUnitTests')->andReturn(false);
$application->shouldReceive('call')->with([$command, 'handle'])->andReturnUsing(fn ($callback) => call_user_func($callback));
$outputStyle->shouldReceive('newLinesWritten')->andReturn(1);
$expectations($factory);
$command->run(new ArrayInput([]), new NullOutput);
}
}

View File

@ -8,7 +8,4 @@ use Illuminate\Contracts\Queue\ShouldQueue;
final class JobToTestWithSchedule implements ShouldQueue
{
public function __invoke(): void
{
}
}

View File

@ -36,11 +36,12 @@ final class ScheduleTest extends TestCase
}
#[DataProvider('jobHonoursDisplayNameIfMethodExistsProvider')]
public function testJobHonoursDisplayNameIfMethodExists(string|object $job, string $jobName): void
public function testJobHonoursDisplayNameIfMethodExists(object $job, string $jobName): void
{
$schedule = new Schedule();
$scheduledJob = $schedule->job($job);
self::assertSame($jobName, $scheduledJob->description);
self::assertFalse($this->container->resolved(JobToTestWithSchedule::class));
}
public static function jobHonoursDisplayNameIfMethodExistsProvider(): array
@ -54,9 +55,16 @@ final class ScheduleTest extends TestCase
};
return [
[JobToTestWithSchedule::class, JobToTestWithSchedule::class],
[new JobToTestWithSchedule, JobToTestWithSchedule::class],
[$job, 'testJob-123'],
];
}
public function testJobIsNotInstantiatedIfSuppliedAsClassname(): void
{
$schedule = new Schedule();
$scheduledJob = $schedule->job(JobToTestWithSchedule::class);
self::assertSame(JobToTestWithSchedule::class, $scheduledJob->description);
self::assertFalse($this->container->resolved(JobToTestWithSchedule::class));
}
}

View File

@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Tests\Database\Fixtures\Enums\Bar;
use Mockery as m;
use PHPUnit\Framework\TestCase;
@ -85,6 +86,16 @@ class DatabaseEloquentBelongsToTest extends TestCase
$relation->addEagerConstraints($models);
}
public function testIdsInEagerConstraintsCanBeBackedEnum()
{
$relation = $this->getRelation();
$relation->getRelated()->shouldReceive('getKeyName')->andReturn('id');
$relation->getRelated()->shouldReceive('getKeyType')->andReturn('int');
$relation->getQuery()->shouldReceive('whereIntegerInRaw')->once()->with('relation.id', [5, 'foreign.value']);
$models = [new EloquentBelongsToModelStub, new EloquentBelongsToModelStubWithBackedEnumCast];
$relation->addEagerConstraints($models);
}
public function testRelationIsProperlyInitialized()
{
$relation = $this->getRelation();
@ -119,6 +130,15 @@ class DatabaseEloquentBelongsToTest extends TestCase
}
};
$result4 = new class extends Model
{
protected $casts = [
'id' => Bar::class,
];
protected $attributes = ['id' => 5];
};
$model1 = new EloquentBelongsToModelStub;
$model1->foreign_key = 1;
$model2 = new EloquentBelongsToModelStub;
@ -131,11 +151,18 @@ class DatabaseEloquentBelongsToTest extends TestCase
return '3';
}
};
$models = $relation->match([$model1, $model2, $model3], new Collection([$result1, $result2, $result3]), 'foo');
$model4 = new EloquentBelongsToModelStub;
$model4->foreign_key = 5;
$models = $relation->match(
[$model1, $model2, $model3, $model4],
new Collection([$result1, $result2, $result3, $result4]),
'foo'
);
$this->assertEquals(1, $models[0]->foo->getAttribute('id'));
$this->assertEquals(2, $models[1]->foo->getAttribute('id'));
$this->assertSame('3', (string) $models[2]->foo->getAttribute('id'));
$this->assertEquals(5, $models[3]->foo->getAttribute('id')->value);
}
public function testAssociateMethodSetsForeignKeyOnModel()
@ -403,3 +430,14 @@ class MissingEloquentBelongsToModelStub extends Model
{
public $foreign_key;
}
class EloquentBelongsToModelStubWithBackedEnumCast extends Model
{
protected $casts = [
'foreign_key' => Bar::class,
];
public $attributes = [
'foreign_key' => 5,
];
}

View File

@ -24,6 +24,7 @@ use Illuminate\Pagination\AbstractPaginator as Paginator;
use Illuminate\Pagination\Cursor;
use Illuminate\Pagination\CursorPaginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Tests\Database\Fixtures\Enums\Bar;
use InvalidArgumentException;
use Mockery as m;
use PHPUnit\Framework\TestCase;
@ -1042,8 +1043,10 @@ class DatabaseQueryBuilderTest extends TestCase
public function testWhereIntegerInRaw()
{
$builder = $this->getBuilder();
$builder->select('*')->from('users')->whereIntegerInRaw('id', ['1a', 2]);
$this->assertSame('select * from "users" where "id" in (1, 2)', $builder->toSql());
$builder->select('*')->from('users')->whereIntegerInRaw('id', [
'1a', 2, Bar::FOO,
]);
$this->assertSame('select * from "users" where "id" in (1, 2, 5)', $builder->toSql());
$this->assertEquals([], $builder->getBindings());
$builder = $this->getBuilder();
@ -1051,8 +1054,9 @@ class DatabaseQueryBuilderTest extends TestCase
['id' => '1a'],
['id' => 2],
['any' => '3'],
['id' => Bar::FOO],
]);
$this->assertSame('select * from "users" where "id" in (1, 2, 3)', $builder->toSql());
$this->assertSame('select * from "users" where "id" in (1, 2, 3, 5)', $builder->toSql());
$this->assertEquals([], $builder->getBindings());
}
@ -2479,19 +2483,19 @@ class DatabaseQueryBuilderTest extends TestCase
public function testJoinsWithMultipleNestedJoins()
{
$builder = $this->getBuilder();
$builder->select('users.id', 'contacts.id', 'contact_types.id', 'countrys.id', 'planets.id')->from('users')->leftJoin('contacts', function ($j) {
$builder->select('users.id', 'contacts.id', 'contact_types.id', 'countries.id', 'planets.id')->from('users')->leftJoin('contacts', function ($j) {
$j->on('users.id', 'contacts.id')
->join('contact_types', 'contacts.contact_type_id', '=', 'contact_types.id')
->leftJoin('countrys', function ($q) {
$q->on('contacts.country', '=', 'countrys.country')
->leftJoin('countries', function ($q) {
$q->on('contacts.country', '=', 'countries.country')
->join('planets', function ($q) {
$q->on('countrys.planet_id', '=', 'planet.id')
$q->on('countries.planet_id', '=', 'planet.id')
->where('planet.is_settled', '=', 1)
->where('planet.population', '>=', 10000);
});
});
});
$this->assertSame('select "users"."id", "contacts"."id", "contact_types"."id", "countrys"."id", "planets"."id" from "users" left join ("contacts" inner join "contact_types" on "contacts"."contact_type_id" = "contact_types"."id" left join ("countrys" inner join "planets" on "countrys"."planet_id" = "planet"."id" and "planet"."is_settled" = ? and "planet"."population" >= ?) on "contacts"."country" = "countrys"."country") on "users"."id" = "contacts"."id"', $builder->toSql());
$this->assertSame('select "users"."id", "contacts"."id", "contact_types"."id", "countries"."id", "planets"."id" from "users" left join ("contacts" inner join "contact_types" on "contacts"."contact_type_id" = "contact_types"."id" left join ("countries" inner join "planets" on "countries"."planet_id" = "planet"."id" and "planet"."is_settled" = ? and "planet"."population" >= ?) on "contacts"."country" = "countries"."country") on "users"."id" = "contacts"."id"', $builder->toSql());
$this->assertEquals(['1', 10000], $builder->getBindings());
}
@ -2502,16 +2506,16 @@ class DatabaseQueryBuilderTest extends TestCase
$j->on('users.id', 'contacts.id')
->join('contact_types', 'contacts.contact_type_id', '=', 'contact_types.id')
->whereExists(function ($q) {
$q->select('*')->from('countrys')
->whereColumn('contacts.country', '=', 'countrys.country')
$q->select('*')->from('countries')
->whereColumn('contacts.country', '=', 'countries.country')
->join('planets', function ($q) {
$q->on('countrys.planet_id', '=', 'planet.id')
$q->on('countries.planet_id', '=', 'planet.id')
->where('planet.is_settled', '=', 1);
})
->where('planet.population', '>=', 10000);
});
});
$this->assertSame('select "users"."id", "contacts"."id", "contact_types"."id" from "users" left join ("contacts" inner join "contact_types" on "contacts"."contact_type_id" = "contact_types"."id") on "users"."id" = "contacts"."id" and exists (select * from "countrys" inner join "planets" on "countrys"."planet_id" = "planet"."id" and "planet"."is_settled" = ? where "contacts"."country" = "countrys"."country" and "planet"."population" >= ?)', $builder->toSql());
$this->assertSame('select "users"."id", "contacts"."id", "contact_types"."id" from "users" left join ("contacts" inner join "contact_types" on "contacts"."contact_type_id" = "contact_types"."id") on "users"."id" = "contacts"."id" and exists (select * from "countries" inner join "planets" on "countries"."planet_id" = "planet"."id" and "planet"."is_settled" = ? where "contacts"."country" = "countries"."country" and "planet"."population" >= ?)', $builder->toSql());
$this->assertEquals(['1', 10000], $builder->getBindings());
}

View File

@ -74,8 +74,8 @@ class DatabaseTransactionsManagerTest extends TestCase
$executedAdminTransactions = $manager->commit('admin', 2, 1);
$this->assertCount(1, $manager->getPendingTransactions()); // One pending "admin" transaction left...
$this->assertCount(2, $executedTransactions); // Two committed tranasctions on "default"
$this->assertCount(0, $executedAdminTransactions); // Zero executed committed tranasctions on "default"
$this->assertCount(2, $executedTransactions); // Two committed transactions on "default"
$this->assertCount(0, $executedAdminTransactions); // Zero executed committed transactions on "default"
// Level 2 "admin" callback has been staged...
$this->assertSame('admin', $manager->getCommittedTransactions()[0]->connection);

View File

@ -0,0 +1,8 @@
<?php
namespace Illuminate\Tests\Database\Fixtures\Enums;
enum Bar: int
{
case FOO = 5;
}

View File

@ -6,11 +6,24 @@ use Carbon\CarbonInterval;
use Illuminate\Database\Connection;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use PDO;
use PHPUnit\Framework\TestCase;
class QueryDurationThresholdTest extends TestCase
{
/**
* @var \Illuminate\Support\Carbon
*/
protected $now;
protected function tearDown(): void
{
Carbon::setTestNow(null);
parent::tearDown();
}
public function testItCanHandleReachingADurationThresholdInTheDb()
{
$connection = new Connection(new PDO('sqlite::memory:'));
@ -46,10 +59,12 @@ class QueryDurationThresholdTest extends TestCase
public function testItIsOnlyCalledOnceWhenGivenDateTime()
{
Carbon::setTestNow($this->now = Carbon::create(2017, 6, 27, 13, 14, 15, 'UTC'));
$connection = new Connection(new PDO('sqlite::memory:'));
$connection->setEventDispatcher(new Dispatcher());
$called = 0;
$connection->whenQueryingForLongerThan(now()->addMilliseconds(1), function () use (&$called) {
$connection->whenQueryingForLongerThan($this->now->addMilliseconds(1), function () use (&$called) {
$called++;
});

View File

@ -38,6 +38,18 @@ class EncrypterTest extends TestCase
$this->assertSame('foo', $decrypted);
}
public function testItValidatesMacOnPerKeyBasis()
{
// Payload created with (key: str_repeat('b', 16)) but will
// "successfully" decrypt with (key: str_repeat('a', 16)), however it
// outputs a random binary string as it is not the correct key.
$encrypted = 'eyJpdiI6Ilg0dFM5TVRibEFqZW54c3lQdWJoVVE9PSIsInZhbHVlIjoiRGJpa2p2ZHI3eUs0dUtRakJneUhUUT09IiwibWFjIjoiMjBjZWYxODdhNThhOTk4MTk1NTc0YTE1MDgzODU1OWE0ZmQ4MDc5ZjMxYThkOGM1ZmM1MzlmYzBkYTBjMWI1ZiIsInRhZyI6IiJ9';
$new = new Encrypter(str_repeat('a', 16));
$new->previousKeys([str_repeat('b', 16)]);
$this->assertSame('foo', $new->decryptString($encrypted));
}
public function testEncryptionUsingBase64EncodedKey()
{
$e = new Encrypter(random_bytes(16));

View File

@ -180,7 +180,7 @@ class FoundationDocsCommandTest extends TestCase
$this->artisan('docs');
}
public function testItBubblesUpNonProcessInterruptExceptionsInAskStratgies()
public function testItBubblesUpNonProcessInterruptExceptionsInAskStrategies()
{
putenv('ARTISAN_DOCS_ASK_STRATEGY='.__DIR__.'/fixtures/process-failure-strategy.php');

View File

@ -0,0 +1,199 @@
<?php
namespace Illuminate\Tests\Integration\Console;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel;
use Orchestra\Testbench\TestCase;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\multiselect;
use function Laravel\Prompts\password;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;
use function Laravel\Prompts\textarea;
class PromptsAssertionTest extends TestCase
{
public function testAssertionForTextPrompt()
{
$this->app[Kernel::class]->registerCommand(
new class extends Command
{
protected $signature = 'test:text';
public function handle()
{
$name = text('What is your name?', 'John');
$this->line($name);
}
}
);
$this
->artisan('test:text')
->expectsQuestion('What is your name?', 'Jane')
->expectsOutput('Jane');
}
public function testAssertionForTextareaPrompt()
{
$this->app[Kernel::class]->registerCommand(
new class extends Command
{
protected $signature = 'test:textarea';
public function handle()
{
$name = textarea('What is your name?', 'John');
$this->line($name);
}
}
);
$this
->artisan('test:textarea')
->expectsQuestion('What is your name?', 'Jane')
->expectsOutput('Jane');
}
public function testAssertionForPasswordPrompt()
{
$this->app[Kernel::class]->registerCommand(
new class extends Command
{
protected $signature = 'test:password';
public function handle()
{
$name = password('What is your password?');
$this->line($name);
}
}
);
$this
->artisan('test:password')
->expectsQuestion('What is your password?', 'secret')
->expectsOutput('secret');
}
public function testAssertionForConfirmPrompt()
{
$this->app[Kernel::class]->registerCommand(
new class extends Command
{
protected $signature = 'test:confirm';
public function handle()
{
$confirmed = confirm('Is your name John?');
if ($confirmed) {
$this->line('Your name is John.');
} else {
$this->line('Your name is not John.');
}
}
}
);
$this
->artisan('test:confirm')
->expectsConfirmation('Is your name John?', 'no')
->expectsOutput('Your name is not John.');
$this
->artisan('test:confirm')
->expectsConfirmation('Is your name John?', 'yes')
->expectsOutput('Your name is John.');
}
public function testAssertionForSelectPrompt()
{
$this->app[Kernel::class]->registerCommand(
new class extends Command
{
protected $signature = 'test:select';
public function handle()
{
$name = select(
label: 'What is your name?',
options: ['John', 'Jane']
);
$this->line("Your name is $name.");
}
}
);
$this
->artisan('test:select')
->expectsChoice('What is your name?', 'Jane', ['John', 'Jane'])
->expectsOutput('Your name is Jane.');
}
public function testAssertionForRequiredMultiselectPrompt()
{
$this->app[Kernel::class]->registerCommand(
new class extends Command
{
protected $signature = 'test:multiselect';
public function handle()
{
$names = multiselect(
label: 'Which names do you like?',
options: ['John', 'Jane', 'Sally', 'Jack'],
required: true
);
$this->line(sprintf('You like %s.', implode(', ', $names)));
}
}
);
$this
->artisan('test:multiselect')
->expectsChoice('Which names do you like?', ['John', 'Jane'], ['John', 'Jane', 'Sally', 'Jack'])
->expectsOutput('You like John, Jane.');
}
public function testAssertionForOptionalMultiselectPrompt()
{
$this->app[Kernel::class]->registerCommand(
new class extends Command
{
protected $signature = 'test:multiselect';
public function handle()
{
$names = multiselect(
label: 'Which names do you like?',
options: ['John', 'Jane', 'Sally', 'Jack'],
);
if (empty($names)) {
$this->line('You like nobody.');
} else {
$this->line(sprintf('You like %s.', implode(', ', $names)));
}
}
}
);
$this
->artisan('test:multiselect')
->expectsChoice('Which names do you like?', ['John', 'Jane'], ['John', 'Jane', 'Sally', 'Jack'])
->expectsOutput('You like John, Jane.');
$this
->artisan('test:multiselect')
->expectsChoice('Which names do you like?', ['None'], ['John', 'Jane', 'Sally', 'Jack'])
->expectsOutput('You like nobody.');
}
}

View File

@ -0,0 +1,387 @@
<?php
namespace Illuminate\Tests\Integration\Database;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;
class AfterQueryTest extends DatabaseTestCase
{
protected function afterRefreshingDatabase()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->integer('team_id')->nullable();
});
Schema::create('teams', function (Blueprint $table) {
$table->increments('id');
$table->integer('owner_id');
});
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
});
Schema::create('users_posts', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->integer('post_id');
$table->timestamps();
});
}
public function testAfterQueryOnEloquentBuilder()
{
AfterQueryUser::create();
AfterQueryUser::create();
$afterQueryIds = collect();
$users = AfterQueryUser::query()
->afterQuery(function (Collection $users) use ($afterQueryIds) {
$afterQueryIds->push(...$users->pluck('id')->all());
foreach ($users as $user) {
$this->assertInstanceOf(AfterQueryUser::class, $user);
}
})
->get();
$this->assertCount(2, $users);
$this->assertEqualsCanonicalizing($afterQueryIds->toArray(), $users->pluck('id')->toArray());
}
public function testAfterQueryOnBaseBuilder()
{
AfterQueryUser::create();
AfterQueryUser::create();
$afterQueryIds = collect();
$users = AfterQueryUser::query()
->toBase()
->afterQuery(function (Collection $users) use ($afterQueryIds) {
$afterQueryIds->push(...$users->pluck('id')->all());
foreach ($users as $user) {
$this->assertNotInstanceOf(AfterQueryUser::class, $user);
}
})
->get();
$this->assertCount(2, $users);
$this->assertEqualsCanonicalizing($afterQueryIds->toArray(), $users->pluck('id')->toArray());
}
public function testAfterQueryOnEloquentCursor()
{
AfterQueryUser::create();
AfterQueryUser::create();
$afterQueryIds = collect();
$users = AfterQueryUser::query()
->afterQuery(function (Collection $users) use ($afterQueryIds) {
$afterQueryIds->push(...$users->pluck('id')->all());
foreach ($users as $user) {
$this->assertInstanceOf(AfterQueryUser::class, $user);
}
})
->cursor();
$this->assertCount(2, $users);
$this->assertEqualsCanonicalizing($afterQueryIds->toArray(), $users->pluck('id')->toArray());
}
public function testAfterQueryOnBaseBuilderCursor()
{
AfterQueryUser::create();
AfterQueryUser::create();
$afterQueryIds = collect();
$users = AfterQueryUser::query()
->toBase()
->afterQuery(function (Collection $users) use ($afterQueryIds) {
$afterQueryIds->push(...$users->pluck('id')->all());
foreach ($users as $user) {
$this->assertNotInstanceOf(AfterQueryUser::class, $user);
}
})
->cursor();
$this->assertCount(2, $users);
$this->assertEqualsCanonicalizing($afterQueryIds->toArray(), $users->pluck('id')->toArray());
}
public function testAfterQueryOnEloquentPluck()
{
AfterQueryUser::create();
AfterQueryUser::create();
$afterQueryIds = collect();
$userIds = AfterQueryUser::query()
->afterQuery(function (Collection $userIds) use ($afterQueryIds) {
$afterQueryIds->push(...$userIds->all());
foreach ($userIds as $userId) {
$this->assertIsInt($userId);
}
})
->pluck('id');
$this->assertCount(2, $userIds);
$this->assertEqualsCanonicalizing($afterQueryIds->toArray(), $userIds->toArray());
}
public function testAfterQueryOnBaseBuilderPluck()
{
AfterQueryUser::create();
AfterQueryUser::create();
$afterQueryIds = collect();
$userIds = AfterQueryUser::query()
->toBase()
->afterQuery(function (Collection $userIds) use ($afterQueryIds) {
$afterQueryIds->push(...$userIds->all());
foreach ($userIds as $userId) {
$this->assertIsInt((int) $userId);
}
})
->pluck('id');
$this->assertCount(2, $userIds);
$this->assertEqualsCanonicalizing($afterQueryIds->toArray(), $userIds->toArray());
}
public function testAfterQueryHookOnBelongsToManyRelationship()
{
$user = AfterQueryUser::create();
$firstPost = AfterQueryPost::create();
$secondPost = AfterQueryPost::create();
$user->posts()->attach($firstPost);
$user->posts()->attach($secondPost);
$afterQueryIds = collect();
$posts = $user->posts()
->afterQuery(function (Collection $posts) use ($afterQueryIds) {
$afterQueryIds->push(...$posts->pluck('id')->all());
foreach ($posts as $post) {
$this->assertInstanceOf(AfterQueryPost::class, $post);
}
})
->get();
$this->assertCount(2, $posts);
$this->assertEqualsCanonicalizing($afterQueryIds->toArray(), $posts->pluck('id')->toArray());
}
public function testAfterQueryHookOnHasManyThroughRelationship()
{
$user = AfterQueryUser::create();
$team = AfterQueryTeam::create(['owner_id' => $user->id]);
AfterQueryUser::create(['team_id' => $team->id]);
AfterQueryUser::create(['team_id' => $team->id]);
$afterQueryIds = collect();
$teamMates = $user->teamMates()
->afterQuery(function (Collection $teamMates) use ($afterQueryIds) {
$afterQueryIds->push(...$teamMates->pluck('id')->all());
foreach ($teamMates as $teamMate) {
$this->assertInstanceOf(AfterQueryUser::class, $teamMate);
}
})
->get();
$this->assertCount(2, $teamMates);
$this->assertEqualsCanonicalizing($afterQueryIds->toArray(), $teamMates->pluck('id')->toArray());
}
public function testAfterQueryOnEloquentBuilderCanAlterReturnedResult()
{
$firstUser = AfterQueryUser::create();
$secondUser = AfterQueryUser::create();
$users = AfterQueryUser::query()
->afterQuery(function () {
return collect(['foo', 'bar']);
})
->get();
$this->assertEquals(collect(['foo', 'bar']), $users);
$users = AfterQueryUser::query()
->afterQuery(function () {
return collect(['foo', 'bar']);
})
->pluck('id');
$this->assertEquals(collect(['foo', 'bar']), $users);
$users = AfterQueryUser::query()
->afterQuery(function ($users) use ($firstUser) {
return $users->first()->is($firstUser) ? collect(['foo', 'bar']) : collect(['bar', 'foo']);
})
->cursor();
$this->assertEquals(collect(['foo', 'bar']), $users->collect());
$users = AfterQueryUser::query()
->afterQuery(function ($users) use ($firstUser) {
return $users->where('id', '!=', $firstUser->id);
})
->cursor();
$this->assertEquals([$secondUser->id], $users->collect()->pluck('id')->all());
$firstPost = AfterQueryPost::create();
$secondPost = AfterQueryPost::create();
$firstUser->posts()->attach($firstPost);
$firstUser->posts()->attach($secondPost);
$posts = $firstUser->posts()
->afterQuery(function () {
return collect(['foo', 'bar']);
})
->get();
$this->assertEquals(collect(['foo', 'bar']), $posts);
$user = AfterQueryUser::create();
$team = AfterQueryTeam::create(['owner_id' => $user->id]);
AfterQueryUser::create(['team_id' => $team->id]);
AfterQueryUser::create(['team_id' => $team->id]);
$teamMates = $user->teamMates()
->afterQuery(function () {
return collect(['foo', 'bar']);
})
->get();
$this->assertEquals(collect(['foo', 'bar']), $teamMates);
}
public function testAfterQueryOnBaseBuilderCanAlterReturnedResult()
{
$firstUser = AfterQueryUser::create();
$secondUser = AfterQueryUser::create();
$users = AfterQueryUser::query()
->toBase()
->afterQuery(function () {
return collect(['foo', 'bar']);
})
->get();
$this->assertEquals(collect(['foo', 'bar']), $users);
$users = AfterQueryUser::query()
->toBase()
->afterQuery(function () {
return collect(['foo', 'bar']);
})
->pluck('id');
$this->assertEquals(collect(['foo', 'bar']), $users);
$users = AfterQueryUser::query()
->toBase()
->afterQuery(function ($users) use ($firstUser) {
return ((int) $users->first()->id) === $firstUser->id ? collect(['foo', 'bar']) : collect(['bar', 'foo']);
})
->cursor();
$this->assertEquals(collect(['foo', 'bar']), $users->collect());
$users = AfterQueryUser::query()
->toBase()
->afterQuery(function ($users) use ($firstUser) {
return $users->where('id', '!=', $firstUser->id);
})
->cursor();
$this->assertEquals([$secondUser->id], $users->collect()->pluck('id')->all());
$firstPost = AfterQueryPost::create();
$secondPost = AfterQueryPost::create();
$firstUser->posts()->attach($firstPost);
$firstUser->posts()->attach($secondPost);
$posts = $firstUser->posts()
->toBase()
->afterQuery(function () {
return collect(['foo', 'bar']);
})
->get();
$this->assertEquals(collect(['foo', 'bar']), $posts);
$user = AfterQueryUser::create();
$team = AfterQueryTeam::create(['owner_id' => $user->id]);
AfterQueryUser::create(['team_id' => $team->id]);
AfterQueryUser::create(['team_id' => $team->id]);
$teamMates = $user->teamMates()
->toBase()
->afterQuery(function () {
return collect(['foo', 'bar']);
})
->get();
$this->assertEquals(collect(['foo', 'bar']), $teamMates);
}
}
class AfterQueryUser extends Model
{
protected $table = 'users';
protected $guarded = [];
public $timestamps = false;
public function teamMates()
{
return $this->hasManyThrough(self::class, AfterQueryTeam::class, 'owner_id', 'team_id');
}
public function posts()
{
return $this->belongsToMany(AfterQueryPost::class, 'users_posts', 'user_id', 'post_id')->withTimestamps();
}
}
class AfterQueryTeam extends Model
{
protected $table = 'teams';
protected $guarded = [];
public $timestamps = false;
public function members()
{
return $this->hasMany(AfterQueryUser::class, 'team_id');
}
}
class AfterQueryPost extends Model
{
protected $table = 'posts';
protected $guarded = [];
public $timestamps = false;
}

View File

@ -21,6 +21,7 @@ class DatabaseSqlServerSchemaBuilderTest extends SqlServerTestCase
protected function destroyDatabaseMigrations()
{
Schema::drop('users');
Schema::dropIfExists('computed');
DB::statement('drop view if exists users_view');
}
@ -64,4 +65,12 @@ class DatabaseSqlServerSchemaBuilderTest extends SqlServerTestCase
{
$this->assertSame([], Schema::getViews());
}
public function testComputedColumnsListing()
{
DB::statement('create table dbo.computed (id int identity (1,1) not null, computed as id + 1)');
$userColumns = Schema::getColumns('users');
$this->assertNull($userColumns[1]['generation']);
}
}

View File

@ -105,7 +105,7 @@ class ShouldDispatchAfterCommitEventTest extends TestCase
$this->assertTrue(AnotherShouldDispatchAfterCommitTestEvent::$ran);
}
public function testItOnlyDispatchesNestedTransactionsEventsAfterTheRootTransactionIsCommitedDifferentOrder()
public function testItOnlyDispatchesNestedTransactionsEventsAfterTheRootTransactionIsCommittedDifferentOrder()
{
Event::listen(ShouldDispatchAfterCommitTestEvent::class, ShouldDispatchAfterCommitListener::class);
Event::listen(AnotherShouldDispatchAfterCommitTestEvent::class, AnotherShouldDispatchAfterCommitListener::class);

View File

@ -17,7 +17,7 @@ class RoutingServiceProviderTest extends TestCase
$response = $this->withoutExceptionHandling()->get('test-route?'.http_build_query([
'sent' => 'sent-data',
'overridden' => 'overriden-sent-data',
'overridden' => 'overridden-sent-data',
]));
$response->assertOk();
@ -48,7 +48,7 @@ class RoutingServiceProviderTest extends TestCase
$response = $this->getJson('test-route?'.http_build_query([
'sent' => 'sent-data',
'overridden' => 'overriden-sent-data',
'overridden' => 'overridden-sent-data',
]), [
'content-type' => 'application/json',
]);
@ -57,7 +57,7 @@ class RoutingServiceProviderTest extends TestCase
$response->assertExactJson([
'json-data' => 'json-data',
'merged' => 'replaced-merged-data',
'overridden' => 'overriden-merged-data',
'overridden' => 'overridden-merged-data',
'request-data' => 'request-data',
]);
}
@ -70,14 +70,14 @@ class RoutingServiceProviderTest extends TestCase
$response = $this->getJson('test-route?'.http_build_query([
'sent' => 'sent-data',
'overridden' => 'overriden-sent-data',
'overridden' => 'overridden-sent-data',
]));
$response->assertOk();
$response->assertExactJson([
'json-data' => 'json-data',
'merged' => 'replaced-merged-data',
'overridden' => 'overriden-merged-data',
'overridden' => 'overridden-merged-data',
'request-data' => 'request-data',
]);
}
@ -90,14 +90,14 @@ class RoutingServiceProviderTest extends TestCase
$response = $this->post('test-route', [
'sent' => 'sent-data',
'overridden' => 'overriden-sent-data',
'overridden' => 'overridden-sent-data',
]);
$response->assertOk();
$response->assertExactJson([
'sent' => 'sent-data',
'merged' => 'replaced-merged-data',
'overridden' => 'overriden-merged-data',
'overridden' => 'overridden-merged-data',
'request-data' => 'request-data',
]);
}
@ -110,7 +110,7 @@ class RoutingServiceProviderTest extends TestCase
$response = $this->postJson('test-route', [
'sent' => 'sent-data',
'overridden' => 'overriden-sent-data',
'overridden' => 'overridden-sent-data',
]);
$response->assertOk();
@ -118,7 +118,7 @@ class RoutingServiceProviderTest extends TestCase
'json-data' => 'json-data',
'sent' => 'sent-data',
'merged' => 'replaced-merged-data',
'overridden' => 'overriden-merged-data',
'overridden' => 'overridden-merged-data',
'request-data' => 'request-data',
]);
}
@ -144,7 +144,7 @@ class MergeDataMiddleware
$request->merge(['merged' => 'replaced-merged-data']);
$request->merge(['overridden' => 'overriden-merged-data']);
$request->merge(['overridden' => 'overridden-merged-data']);
$request->request->set('request-data', 'request-data');

View File

@ -305,7 +305,7 @@ class ThrottleRequestsTest extends TestCase
public function testItFallbacksToUserAccessorWhenThereIsNoNamedLimiterWhenAuthenticated()
{
$user = UserWithAcessor::make();
$user = UserWithAccessor::make();
Carbon::setTestNow(Carbon::create(2018, 1, 1, 0, 0, 0));
@ -333,7 +333,7 @@ class ThrottleRequestsTest extends TestCase
}
}
class UserWithAcessor extends User
class UserWithAccessor extends User
{
public function getRateLimitingAttribute(): int
{

View File

@ -0,0 +1,594 @@
<?php
namespace Illuminate\Tests\Integration\Support;
use ErrorException;
use Exception;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler;
use Illuminate\Support\Facades\Exceptions;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Testing\Fakes\ExceptionHandlerFake;
use InvalidArgumentException;
use Orchestra\Testbench\TestCase;
use PHPUnit\Framework\ExpectationFailedException;
use RuntimeException;
use Throwable;
class ExceptionsFacadeTest extends TestCase
{
public function testFakeAssertReported()
{
Exceptions::fake();
Exceptions::report(new RuntimeException('test 1'));
report(new RuntimeException('test 2'));
Exceptions::assertReported(RuntimeException::class);
Exceptions::assertReported(fn (RuntimeException $e) => $e->getMessage() === 'test 1');
Exceptions::assertReported(fn (RuntimeException $e) => $e->getMessage() === 'test 2');
Exceptions::assertReportedCount(2);
}
public function testFakeAssertReportedCount()
{
Exceptions::fake();
Exceptions::report(new RuntimeException('test 1'));
report(new RuntimeException('test 2'));
Exceptions::assertReportedCount(2);
}
public function testFakeAssertReportedCountMayFail()
{
Exceptions::fake();
Exceptions::report(new RuntimeException('test 1'));
report(new RuntimeException('test 2'));
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The total number of exceptions reported was 2 instead of 1.');
Exceptions::assertReportedCount(1);
}
public function testFakeAssertReportedWithFakedExceptions()
{
Exceptions::fake([
RuntimeException::class,
]);
Exceptions::report(new RuntimeException('test 1'));
report(new RuntimeException('test 2'));
report(new InvalidArgumentException('test 3'));
Exceptions::assertReported(RuntimeException::class);
Exceptions::assertReported(fn (RuntimeException $e) => $e->getMessage() === 'test 1');
Exceptions::assertReported(fn (RuntimeException $e) => $e->getMessage() === 'test 2');
Exceptions::assertNotReported(InvalidArgumentException::class);
Exceptions::assertReportedCount(2);
}
public function testFakeAssertReportedAsStringMayFail()
{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The expected [InvalidArgumentException] exception was not reported.');
Exceptions::fake();
Exceptions::report(new RuntimeException('test 1'));
Exceptions::assertReportedCount(1);
Exceptions::assertReported(InvalidArgumentException::class);
}
public function testFakeAssertReportedAsClosureMayFail()
{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The expected [InvalidArgumentException] exception was not reported.');
Exceptions::fake();
Exceptions::report(new RuntimeException('test 1'));
Exceptions::assertReportedCount(1);
Exceptions::assertReported(fn (InvalidArgumentException $e) => $e->getMessage() === 'test 2');
}
public function testFakeAssertReportedWithFakedExceptionsMayFail()
{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The expected [RuntimeException] exception was not reported.');
Exceptions::fake(InvalidArgumentException::class);
Exceptions::report(new InvalidArgumentException('test 1'));
report(new RuntimeException('test 2'));
Exceptions::assertReported(InvalidArgumentException::class);
Exceptions::assertReported(RuntimeException::class);
}
public function testFakeAssertNotReported()
{
Exceptions::fake();
Exceptions::report(new RuntimeException('test 1'));
report(new RuntimeException('test 2'));
Exceptions::assertNotReported(InvalidArgumentException::class);
Exceptions::assertNotReported(fn (InvalidArgumentException $e) => $e->getMessage() === 'test 1');
Exceptions::assertNotReported(fn (InvalidArgumentException $e) => $e->getMessage() === 'test 2');
Exceptions::assertNotReported(fn (InvalidArgumentException $e) => $e->getMessage() === 'test 3');
Exceptions::assertNotReported(fn (InvalidArgumentException $e) => $e->getMessage() === 'test 4');
Exceptions::assertReportedCount(2);
}
public function testFakeAssertNotReportedWithFakedExceptions()
{
Exceptions::fake([
InvalidArgumentException::class,
]);
report(new RuntimeException('test 2'));
Exceptions::assertNotReported(InvalidArgumentException::class);
Exceptions::assertNotReported(RuntimeException::class);
}
public function testFakeAssertNotReportedMayFail()
{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The expected [RuntimeException] exception was not reported.');
Exceptions::fake();
Exceptions::report(new RuntimeException('test 1'));
Exceptions::assertNotReported(RuntimeException::class);
}
public function testFakeAssertNotReportedAsClosureMayFail()
{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The expected [RuntimeException] exception was not reported.');
Exceptions::fake();
Exceptions::report(new RuntimeException('test 1'));
Exceptions::assertNotReported(fn (RuntimeException $e) => $e->getMessage() === 'test 1');
}
public function testResolvesExceptionHandler()
{
$this->assertInstanceOf(
ExceptionHandler::class,
Exceptions::getFacadeRoot()
);
}
public function testFakeAssertNothingReported()
{
Exceptions::fake();
Exceptions::assertNothingReported();
}
public function testFakeAssertNothingReportedWithFakedExceptions()
{
Exceptions::fake([
InvalidArgumentException::class,
]);
report(new RuntimeException('test 1'));
Exceptions::assertNothingReported();
}
public function testFakeAssertNothingReportedMayFail()
{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The following exceptions were reported: RuntimeException, RuntimeException, InvalidArgumentException.');
Exceptions::fake();
Exceptions::report(new RuntimeException('test 1'));
report(new RuntimeException('test 2'));
report(new InvalidArgumentException('test 3'));
Exceptions::assertNothingReported();
}
public function testFakeMethodReturnsExceptionHandlerFake()
{
$this->assertInstanceOf(ExceptionHandlerFake::class, $fake = Exceptions::fake());
$this->assertInstanceOf(ExceptionHandlerFake::class, Exceptions::getFacadeRoot());
$this->assertInstanceOf(Handler::class, $fake->handler());
$this->assertInstanceOf(ExceptionHandlerFake::class, $fake = Exceptions::fake());
$this->assertInstanceOf(ExceptionHandlerFake::class, Exceptions::getFacadeRoot());
$this->assertInstanceOf(Handler::class, $fake->handler());
}
public function testReportedExceptionsAreNotThrownByDefault()
{
report(new Exception('Test exception'));
$this->assertTrue(true);
}
public function testReportedExceptionsAreNotThrownByDefaultWithExceptionHandling()
{
Route::get('/', function () {
report(new Exception('Test exception'));
});
$this->get('/')->assertStatus(200);
}
public function testReportedExceptionsAreNotThrownByDefaultWithoutExceptionHandling()
{
$this->withoutExceptionHandling();
Route::get('/', function () {
report(new Exception('Test exception'));
});
$this->get('/')->assertStatus(200);
}
public function testThrowOnReport()
{
Exceptions::fake()->throwOnReport();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Test exception');
report(new Exception('Test exception'));
}
public function testThrowOnReportDoesNotThrowExceptionsThatShouldNotBeReported()
{
Exceptions::fake()->throwOnReport();
Route::get('/302', function () {
Validator::validate(['name' => ''], ['name' => 'required']);
});
$this->get('/302')->assertStatus(302);
Route::get('/404', function () {
throw new ModelNotFoundException();
});
$this->get('/404')->assertStatus(404);
$this->doesNotPerformAssertions();
Exceptions::assertReportedCount(0);
}
public function testThrowOnReportWithExceptionHandling()
{
Exceptions::fake()->throwOnReport();
Route::get('/', function () {
report(new Exception('Test exception'));
});
$this->expectException(Exception::class);
$this->expectExceptionMessage('Test exception');
$this->get('/');
}
public function testThrowOnReportWithoutExceptionHandling()
{
Exceptions::fake()->throwOnReport();
$this->withoutExceptionHandling();
Route::get('/', function () {
report(new Exception('Test exception'));
});
$this->expectException(Exception::class);
$this->expectExceptionMessage('Test exception');
$this->get('/');
}
public function testThrowOnReportRegardlessOfTheCallingOrderOfWithoutExceptionHandling()
{
Exceptions::fake()->throwOnReport();
$this
->withoutExceptionHandling()
->withExceptionHandling()
->withoutExceptionHandling();
Route::get('/', function () {
rescue(fn () => throw new Exception('Test exception'));
});
$this->expectException(Exception::class);
$this->expectExceptionMessage('Test exception');
$this->get('/');
}
public function testThrowOnReportRegardlessOfTheCallingOrderOfWithExceptionHandling()
{
Exceptions::fake()->throwOnReport();
$this->withoutExceptionHandling()
->withExceptionHandling()
->withoutExceptionHandling()
->withExceptionHandling();
Route::get('/', function () {
rescue(fn () => throw new Exception('Test exception'));
});
$this->expectException(Exception::class);
$this->expectExceptionMessage('Test exception');
$this->get('/');
}
public function testThrowOnReportWithFakedExceptions()
{
Exceptions::fake([InvalidArgumentException::class])->throwOnReport();
$this->expectException(InvalidArgumentException::class);
report(new Exception('Test exception'));
report(new RuntimeException('Test exception'));
report(new InvalidArgumentException('Test exception'));
}
public function testThrowOnReportWithFakedExceptionsFromFacade()
{
Exceptions::fake([InvalidArgumentException::class])->throwOnReport();
$this->expectException(InvalidArgumentException::class);
report(new Exception('Test exception'));
report(new RuntimeException('Test exception'));
Exceptions::assertReportedCount(0);
report(new InvalidArgumentException('Test exception'));
}
public function testThrowOnReporEvenWhenAppReportablesReturnFalse()
{
app(ExceptionHandler::class)->reportable(function (Throwable $e) {
return false;
});
Exceptions::fake()->throwOnReport();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Test exception');
report(new Exception('Test exception'));
}
public function testAppReportablesAreNotCalledIfExceptionIsNotFaked()
{
app(ExceptionHandler::class)->reportable(function (Throwable $e) {
throw new InvalidArgumentException($e->getMessage());
});
Exceptions::fake([RuntimeException::class, Exception::class]);
report(new Exception('My exception message'));
Exceptions::assertReported(Exception::class);
}
public function testThrowOnReportLeaveAppReportablesUntouched()
{
app(ExceptionHandler::class)->reportable(function (Throwable $e) {
throw new InvalidArgumentException($e->getMessage());
});
Exceptions::fake([RuntimeException::class])->throwOnReport();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('My exception message');
report(new Exception('My exception message'));
}
public function testThrowReportedExceptions()
{
Exceptions::fake();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Test exception');
report(new Exception('Test exception'));
Exceptions::throwFirstReported();
}
public function testThrowReportedExceptionsWithFakedExceptions()
{
Exceptions::fake([InvalidArgumentException::class]);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Test exception');
report(new RuntimeException('Test exception'));
report(new InvalidArgumentException('Test exception'));
Exceptions::throwFirstReported();
}
public function testThrowReportedExceptionsWhenThereIsNone()
{
Exceptions::fake();
Exceptions::throwFirstReported();
Exceptions::fake([InvalidArgumentException::class]);
report(new RuntimeException('Test exception'));
Exceptions::throwFirstReported();
$this->doesNotPerformAssertions();
}
public function testFakingExceptionsThatShouldNotBeReportedWithExceptionHandling()
{
Exceptions::fake();
Route::get('/302', function () {
Validator::validate(['name' => ''], ['name' => 'required']);
});
$this->get('/302')->assertStatus(302);
Route::get('/404', function () {
throw new ModelNotFoundException();
});
$this->get('/404')->assertStatus(404);
report(new ModelNotFoundException());
Exceptions::assertNothingReported();
}
public function testFakingExceptionsThatShouldNotBeReportedWithRescueAndWithoutExceptionHandling()
{
Exceptions::fake();
$this->withoutExceptionHandling();
Route::get('/validation', function () {
rescue(fn () => Validator::validate(['name' => ''], ['name' => 'required']));
});
$this->get('/validation')->assertStatus(200);
Route::get('/model', function () {
rescue(fn () => throw new ModelNotFoundException());
});
$this->get('/model')->assertStatus(200);
rescue(fn () => throw new ModelNotFoundException());
Exceptions::assertReportedCount(3);
}
public function testRescue()
{
Exceptions::fake();
rescue(fn () => throw new Exception('Test exception'));
Exceptions::assertReported(Exception::class);
}
public function testRescueWithoutReport()
{
Exceptions::fake();
rescue(fn () => throw new Exception('Test exception'), null, false);
Exceptions::assertNothingReported();
}
public function testFlowBetweenFakeAndTestExceptionHandling()
{
$this->assertInstanceOf(Handler::class, app(ExceptionHandler::class));
Exceptions::fake();
$this->assertInstanceOf(ExceptionHandlerFake::class, app(ExceptionHandler::class));
$this->assertInstanceOf(Handler::class, Exceptions::fake()->handler());
$this->assertFalse((new \ReflectionClass(Exceptions::fake()->handler()))->isAnonymous());
Exceptions::fake();
$this->assertInstanceOf(ExceptionHandlerFake::class, app(ExceptionHandler::class));
$this->assertInstanceOf(Handler::class, Exceptions::fake()->handler());
$this->assertFalse((new \ReflectionClass(Exceptions::fake()->handler()))->isAnonymous());
$this->withoutExceptionHandling();
$this->assertInstanceOf(ExceptionHandlerFake::class, app(ExceptionHandler::class));
$this->assertInstanceOf(ExceptionHandler::class, Exceptions::fake()->handler());
$this->assertTrue((new \ReflectionClass(Exceptions::fake()->handler()))->isAnonymous());
$this->withExceptionHandling();
$this->assertInstanceOf(ExceptionHandlerFake::class, app(ExceptionHandler::class));
$this->assertInstanceOf(ExceptionHandler::class, Exceptions::fake()->handler());
$this->assertFalse((new \ReflectionClass(Exceptions::fake()->handler()))->isAnonymous());
Exceptions::fake();
$this->assertInstanceOf(ExceptionHandlerFake::class, app(ExceptionHandler::class));
$this->assertInstanceOf(Handler::class, Exceptions::fake()->handler());
$this->assertFalse((new \ReflectionClass(Exceptions::fake()->handler()))->isAnonymous());
}
public function testFlowBetweenTestExceptionHandlingAndFake()
{
$this->withoutExceptionHandling();
$this->assertTrue((new \ReflectionClass(app(ExceptionHandler::class)))->isAnonymous());
Exceptions::fake();
$this->assertInstanceOf(ExceptionHandlerFake::class, app(ExceptionHandler::class));
$this->assertInstanceOf(ExceptionHandler::class, Exceptions::fake()->handler());
$this->assertTrue((new \ReflectionClass(Exceptions::fake()->handler()))->isAnonymous());
Exceptions::fake();
$this->assertInstanceOf(ExceptionHandlerFake::class, app(ExceptionHandler::class));
$this->assertInstanceOf(ExceptionHandler::class, Exceptions::fake()->handler());
$this->assertTrue((new \ReflectionClass(Exceptions::fake()->handler()))->isAnonymous());
$this->withExceptionHandling();
$this->assertInstanceOf(ExceptionHandlerFake::class, app(ExceptionHandler::class));
$this->assertInstanceOf(Handler::class, Exceptions::fake()->handler());
$this->assertFalse((new \ReflectionClass(Exceptions::fake()->handler()))->isAnonymous());
}
public function testWithDeprecationHandling()
{
Exceptions::fake();
Route::get('/', function () {
str_contains(null, null);
});
$this->get('/')->assertStatus(200);
Exceptions::assertNothingReported();
}
public function testWithoutDeprecationHandler()
{
Exceptions::fake();
$this->withoutDeprecationHandling();
Route::get('/', function () {
str_contains(null, null);
});
$this->get('/')->assertStatus(500);
Exceptions::assertReported(function (ErrorException $e) {
return $e->getMessage() === 'str_contains(): Passing null to parameter #1 ($haystack) of type string is deprecated';
});
Exceptions::assertReportedCount(1);
}
}

View File

@ -13,3 +13,9 @@ enum CategoryBackedEnum: string
case People = 'people';
case Fruits = 'fruits';
}
enum CategoryIntBackedEnum: int
{
case People = 1;
case Fruits = 2;
}

View File

@ -31,6 +31,24 @@ class ImplicitRouteBindingTest extends TestCase
$this->assertSame('fruits', $route->parameter('category')->value);
}
public function test_it_can_resolve_the_implicit_int_backed_enum_route_bindings_for_the_given_route()
{
$action = ['uses' => function (CategoryIntBackedEnum $category) {
return $category->value;
}];
$route = new Route('GET', '/test', $action);
$route->parameters = ['category' => '1'];
$route->prepareForSerialization();
$container = Container::getInstance();
ImplicitRouteBinding::resolveForRoute($container, $route);
$this->assertSame(1, $route->parameter('category')->value);
}
public function test_it_can_resolve_the_implicit_backed_enum_route_bindings_for_the_given_route_with_optional_parameter()
{
$action = ['uses' => function (?CategoryBackedEnum $category = null) {
@ -91,6 +109,29 @@ class ImplicitRouteBindingTest extends TestCase
ImplicitRouteBinding::resolveForRoute($container, $route);
}
public function test_implicit_int_backed_enum_internal_exception()
{
$action = ['uses' => function (CategoryIntBackedEnum $category) {
return $category->value;
}];
$route = new Route('GET', '/test', $action);
$route->parameters = ['category' => ' 00001.'];
$route->prepareForSerialization();
$container = Container::getInstance();
$this->expectException(BackedEnumCaseNotFoundException::class);
$this->expectExceptionMessage(sprintf(
'Case [%s] not found on Backed Enum [%s].',
' 00001.',
CategoryIntBackedEnum::class,
));
ImplicitRouteBinding::resolveForRoute($container, $route);
}
public function test_it_can_resolve_the_implicit_model_route_bindings_for_the_given_route()
{
$this->expectNotToPerformAssertions();

View File

@ -12,3 +12,9 @@ enum TestBackedEnum: int
case A = 1;
case B = 2;
}
enum TestStringBackedEnum: string
{
case A = 'A';
case B = 'B';
}

View File

@ -2961,6 +2961,32 @@ class SupportCollectionTest extends TestCase
$this->assertSame('second', $data->get(1)->value);
}
#[DataProvider('collectionClassProvider')]
public function testMapIntoWithIntBackedEnums($collection)
{
$data = new $collection([
1, 2,
]);
$data = $data->mapInto(TestBackedEnum::class);
$this->assertSame(TestBackedEnum::A, $data->get(0));
$this->assertSame(TestBackedEnum::B, $data->get(1));
}
#[DataProvider('collectionClassProvider')]
public function testMapIntoWithStringBackedEnums($collection)
{
$data = new $collection([
'A', 'B',
]);
$data = $data->mapInto(TestStringBackedEnum::class);
$this->assertSame(TestStringBackedEnum::A, $data->get(0));
$this->assertSame(TestStringBackedEnum::B, $data->get(1));
}
#[DataProvider('collectionClassProvider')]
public function testNth($collection)
{

View File

@ -7,6 +7,7 @@ use Illuminate\Support\Carbon;
use Illuminate\Support\ItemNotFoundException;
use Illuminate\Support\LazyCollection;
use Illuminate\Support\MultipleItemsFoundException;
use Illuminate\Support\Sleep;
use Mockery as m;
use PHPUnit\Framework\TestCase;
use stdClass;
@ -1370,6 +1371,25 @@ class SupportLazyCollectionIsLazyTest extends TestCase
});
}
public function testThrottleIsLazy()
{
Sleep::fake();
$this->assertDoesNotEnumerate(function ($collection) {
$collection->throttle(10);
});
$this->assertEnumerates(5, function ($collection) {
$collection->throttle(10)->take(5)->all();
});
$this->assertEnumeratesOnce(function ($collection) {
$collection->throttle(10)->all();
});
Sleep::fake(false);
}
public function testTimesIsLazy()
{
$data = LazyCollection::times(INF);

View File

@ -2,9 +2,12 @@
namespace Illuminate\Tests\Support;
use Carbon\CarbonInterval as Duration;
use Illuminate\Foundation\Testing\Wormhole;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Illuminate\Support\Sleep;
use InvalidArgumentException;
use Mockery as m;
use PHPUnit\Framework\TestCase;
@ -223,6 +226,58 @@ class SupportLazyCollectionTest extends TestCase
$this->assertSame([1, 2, 3, 4, 5], $tapped);
}
public function testThrottle()
{
Sleep::fake();
$data = LazyCollection::times(3)
->throttle(2)
->all();
Sleep::assertSlept(function (Duration $duration) {
$this->assertEqualsWithDelta(
2_000_000, $duration->totalMicroseconds, 1_000
);
return true;
}, times: 3);
$this->assertSame([1, 2, 3], $data);
Sleep::fake(false);
}
public function testThrottleAccountsForTimePassed()
{
Sleep::fake();
Carbon::setTestNow(now());
$data = LazyCollection::times(3)
->throttle(3)
->tapEach(function ($value, $index) {
if ($index == 1) {
// Travel in time...
(new Wormhole(1))->second();
}
})
->all();
Sleep::assertSlept(function (Duration $duration, int $index) {
$expectation = $index == 1 ? 2_000_000 : 3_000_000;
$this->assertEqualsWithDelta(
$expectation, $duration->totalMicroseconds, 1_000
);
return true;
}, times: 3);
$this->assertSame([1, 2, 3], $data);
Sleep::fake(false);
Carbon::setTestNow();
}
public function testUniqueDoubleEnumeration()
{
$data = LazyCollection::times(2)->unique();

View File

@ -37,6 +37,17 @@ class SupportStrTest extends TestCase
{
$this->assertSame('Jefferson Costella', Str::title('jefferson costella'));
$this->assertSame('Jefferson Costella', Str::title('jefFErson coSTella'));
$this->assertSame('', Str::title(''));
$this->assertSame('123 Laravel', Str::title('123 laravel'));
$this->assertSame('❤Laravel', Str::title('❤laravel'));
$this->assertSame('Laravel ❤', Str::title('laravel ❤'));
$this->assertSame('Laravel123', Str::title('laravel123'));
$this->assertSame('Laravel123', Str::title('Laravel123'));
$longString = 'lorem ipsum '.str_repeat('dolor sit amet ', 1000);
$expectedResult = 'Lorem Ipsum Dolor Sit Amet '.str_repeat('Dolor Sit Amet ', 999);
$this->assertSame($expectedResult, Str::title($longString));
}
public function testStringHeadline()

View File

@ -2447,6 +2447,22 @@ class ValidationValidatorTest extends TestCase
$this->assertSame('The foo field must be accepted when bar is true.', $v->messages()->first('foo'));
}
public function testValidateRequiredIfDeclined()
{
$trans = $this->getIlluminateArrayTranslator();
$v = new Validator($trans, ['foo' => 'yes', 'bar' => 'baz'], ['bar' => 'required_if_declined:foo']);
$this->assertTrue($v->passes());
$v = new Validator($trans, ['foo' => 'no', 'bar' => 'baz'], ['bar' => 'required_if_declined:foo']);
$this->assertTrue($v->passes());
$v = new Validator($trans, ['foo' => 'yes', 'bar' => ''], ['bar' => 'required_if_declined:foo']);
$this->assertTrue($v->passes());
$v = new Validator($trans, ['foo' => 'no', 'bar' => ''], ['bar' => 'required_if_declined:foo']);
$this->assertFalse($v->passes());
}
public function testValidateDeclined()
{
$trans = $this->getIlluminateArrayTranslator();

View File

@ -113,6 +113,17 @@ class ViewComponentAttributeBagTest extends TestCase
$this->assertSame('required="required" x-data=""', (string) $bag);
}
public function testItMakesAnExceptionForLivewireWireAttributes()
{
$bag = new ComponentAttributeBag([
'wire:loading' => true,
'wire:loading.remove' => true,
'wire:poll' => true,
]);
$this->assertSame('wire:loading="" wire:loading.remove="" wire:poll=""', (string) $bag);
}
public function testAttributeExistence()
{
$bag = new ComponentAttributeBag(['name' => 'test']);