diff --git a/CHANGELOG.md b/CHANGELOG.md
index 08670c2ba505..42ef61c3e274 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,25 @@
# Release Notes for 11.x
-## [Unreleased](https://github.com/laravel/framework/compare/v11.29.0...11.x)
+## [Unreleased](https://github.com/laravel/framework/compare/v11.30.0...11.x)
+
+## [v11.30.0](https://github.com/laravel/framework/compare/v11.29.0...v11.30.0) - 2024-10-30
+
+* Add `$bind` parameter to `Blade::directive` by [@hossein-zare](https://github.com/hossein-zare) in https://github.com/laravel/framework/pull/53279
+* [11.x] Fix `trans_choice()` when translation replacement include `|` separator by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/53331
+* [11.x] Allow the authorize method to accept Backed Enums directly by [@johanvanhelden](https://github.com/johanvanhelden) in https://github.com/laravel/framework/pull/53330
+* [11.x] use `exists()` instead of `count()` by [@browner12](https://github.com/browner12) in https://github.com/laravel/framework/pull/53328
+* [11.x] Docblock Improvements by [@mtlukaszczyk](https://github.com/mtlukaszczyk) in https://github.com/laravel/framework/pull/53325
+* Allow for custom Postgres operators to be added by [@boris-glumpler](https://github.com/boris-glumpler) in https://github.com/laravel/framework/pull/53324
+* [11.x] Support Optional Dimensions for `vector` Column Type by [@akr4m](https://github.com/akr4m) in https://github.com/laravel/framework/pull/53316
+* [11.x] Test Improvements by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/53306
+* [11.x] Added `dropColumnsIfExists`, `dropColumnIfExists` and `dropForeignIfExists` by [@eusonlito](https://github.com/eusonlito) in https://github.com/laravel/framework/pull/53305
+* [11.x] Provide an error message for PostTooLargeException by [@patrickomeara](https://github.com/patrickomeara) in https://github.com/laravel/framework/pull/53301
+* [11.x] Fix integrity constraint violation on failed_jobs_uuid_unique by [@bytestream](https://github.com/bytestream) in https://github.com/laravel/framework/pull/53264
+* Revert "[11.x] Added `dropColumnsIfExists`, `dropColumnIfExists` and `dropForeignIfExists`" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/53338
+* [11.x] Introduce `HasUniqueStringIds` by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/53280
+* [11.x] Refactor: check for contextual attribute before getting parameter class name by [@korkoshko](https://github.com/korkoshko) in https://github.com/laravel/framework/pull/53339
+* [11.x] Pick up existing views and markdowns when creating mails by [@kevinb1989](https://github.com/kevinb1989) in https://github.com/laravel/framework/pull/53308
+* [11.x] Add withoutDefer and withDefer testing helpers by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/53340
## [v11.29.0](https://github.com/laravel/framework/compare/v11.28.1...v11.29.0) - 2024-10-22
diff --git a/src/Illuminate/Bus/PendingBatch.php b/src/Illuminate/Bus/PendingBatch.php
index ad39b41923eb..53ebdd65296c 100644
--- a/src/Illuminate/Bus/PendingBatch.php
+++ b/src/Illuminate/Bus/PendingBatch.php
@@ -12,6 +12,8 @@
use Laravel\SerializableClosure\SerializableClosure;
use Throwable;
+use function Illuminate\Support\enum_value;
+
class PendingBatch
{
use Conditionable;
@@ -261,12 +263,12 @@ public function connection()
/**
* Specify the queue that the batched jobs should run on.
*
- * @param string $queue
+ * @param \BackedEnum|string|null $queue
* @return $this
*/
- public function onQueue(string $queue)
+ public function onQueue($queue)
{
- $this->options['queue'] = $queue;
+ $this->options['queue'] = enum_value($queue);
return $this;
}
diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php
index ce0342ec5010..e7ac09423d16 100644
--- a/src/Illuminate/Database/Concerns/ManagesTransactions.php
+++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php
@@ -10,11 +10,13 @@
trait ManagesTransactions
{
/**
+ * @template TReturn of mixed
+ *
* Execute a Closure within a transaction.
*
- * @param \Closure $callback
+ * @param (\Closure(static): TReturn) $callback
* @param int $attempts
- * @return mixed
+ * @return TReturn
*
* @throws \Throwable
*/
diff --git a/src/Illuminate/Database/Console/Migrations/RefreshCommand.php b/src/Illuminate/Database/Console/Migrations/RefreshCommand.php
index 3fd1083a7524..7d74f5b38c0c 100755
--- a/src/Illuminate/Database/Console/Migrations/RefreshCommand.php
+++ b/src/Illuminate/Database/Console/Migrations/RefreshCommand.php
@@ -38,7 +38,7 @@ public function handle()
{
if ($this->isProhibited() ||
! $this->confirmToProceed()) {
- return 1;
+ return Command::FAILURE;
}
// Next we'll gather some of the options so that we can have the right options
diff --git a/src/Illuminate/Database/Console/Migrations/ResetCommand.php b/src/Illuminate/Database/Console/Migrations/ResetCommand.php
index 695da444b1d1..85ccae9734e0 100755
--- a/src/Illuminate/Database/Console/Migrations/ResetCommand.php
+++ b/src/Illuminate/Database/Console/Migrations/ResetCommand.php
@@ -2,6 +2,7 @@
namespace Illuminate\Database\Console\Migrations;
+use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Console\Prohibitable;
use Illuminate\Database\Migrations\Migrator;
@@ -56,7 +57,7 @@ public function handle()
{
if ($this->isProhibited() ||
! $this->confirmToProceed()) {
- return 1;
+ return Command::FAILURE;
}
return $this->migrator->usingConnection($this->option('database'), function () {
diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php b/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php
index 85a810db5ee1..344f97338aa1 100644
--- a/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php
+++ b/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php
@@ -2,33 +2,14 @@
namespace Illuminate\Database\Eloquent\Concerns;
-use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Str;
trait HasUlids
{
- /**
- * Initialize the trait.
- *
- * @return void
- */
- public function initializeHasUlids()
- {
- $this->usesUniqueIds = true;
- }
+ use HasUniqueStringIds;
/**
- * Get the columns that should receive a unique identifier.
- *
- * @return array
- */
- public function uniqueIds()
- {
- return [$this->getKeyName()];
- }
-
- /**
- * Generate a new ULID for the model.
+ * Generate a new unique key for the model.
*
* @return string
*/
@@ -38,53 +19,13 @@ public function newUniqueId()
}
/**
- * Retrieve the model for a bound value.
+ * Determine if given key is valid.
*
- * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $query
* @param mixed $value
- * @param string|null $field
- * @return \Illuminate\Contracts\Database\Eloquent\Builder
- *
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- */
- public function resolveRouteBindingQuery($query, $value, $field = null)
- {
- if ($field && in_array($field, $this->uniqueIds()) && ! Str::isUlid($value)) {
- throw (new ModelNotFoundException)->setModel(get_class($this), $value);
- }
-
- if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! Str::isUlid($value)) {
- throw (new ModelNotFoundException)->setModel(get_class($this), $value);
- }
-
- return parent::resolveRouteBindingQuery($query, $value, $field);
- }
-
- /**
- * Get the auto-incrementing key type.
- *
- * @return string
- */
- public function getKeyType()
- {
- if (in_array($this->getKeyName(), $this->uniqueIds())) {
- return 'string';
- }
-
- return $this->keyType;
- }
-
- /**
- * Get the value indicating whether the IDs are incrementing.
- *
* @return bool
*/
- public function getIncrementing()
+ protected function isValidUniqueId($value): bool
{
- if (in_array($this->getKeyName(), $this->uniqueIds())) {
- return false;
- }
-
- return $this->incrementing;
+ return Str::isUlid($value);
}
}
diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasUniqueStringIds.php b/src/Illuminate/Database/Eloquent/Concerns/HasUniqueStringIds.php
new file mode 100644
index 000000000000..ae86c43042d5
--- /dev/null
+++ b/src/Illuminate/Database/Eloquent/Concerns/HasUniqueStringIds.php
@@ -0,0 +1,94 @@
+usesUniqueIds = true;
+ }
+
+ /**
+ * Get the columns that should receive a unique identifier.
+ *
+ * @return array
+ */
+ public function uniqueIds()
+ {
+ return [$this->getKeyName()];
+ }
+
+ /**
+ * Retrieve the model for a bound value.
+ *
+ * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $query
+ * @param mixed $value
+ * @param string|null $field
+ * @return \Illuminate\Contracts\Database\Eloquent\Builder
+ *
+ * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
+ */
+ public function resolveRouteBindingQuery($query, $value, $field = null)
+ {
+ if ($field && in_array($field, $this->uniqueIds()) && ! $this->isValidUniqueId($value)) {
+ throw (new ModelNotFoundException)->setModel(get_class($this), $value);
+ }
+
+ if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! $this->isValidUniqueId($value)) {
+ throw (new ModelNotFoundException)->setModel(get_class($this), $value);
+ }
+
+ return parent::resolveRouteBindingQuery($query, $value, $field);
+ }
+
+ /**
+ * Get the auto-incrementing key type.
+ *
+ * @return string
+ */
+ public function getKeyType()
+ {
+ if (in_array($this->getKeyName(), $this->uniqueIds())) {
+ return 'string';
+ }
+
+ return $this->keyType;
+ }
+
+ /**
+ * Get the value indicating whether the IDs are incrementing.
+ *
+ * @return bool
+ */
+ public function getIncrementing()
+ {
+ if (in_array($this->getKeyName(), $this->uniqueIds())) {
+ return false;
+ }
+
+ return $this->incrementing;
+ }
+}
diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php b/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php
index 55d1acfe770e..8d6c35ad89af 100644
--- a/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php
+++ b/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php
@@ -2,33 +2,14 @@
namespace Illuminate\Database\Eloquent\Concerns;
-use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Str;
trait HasUuids
{
- /**
- * Initialize the trait.
- *
- * @return void
- */
- public function initializeHasUuids()
- {
- $this->usesUniqueIds = true;
- }
+ use HasUniqueStringIds;
/**
- * Get the columns that should receive a unique identifier.
- *
- * @return array
- */
- public function uniqueIds()
- {
- return [$this->getKeyName()];
- }
-
- /**
- * Generate a new UUID for the model.
+ * Generate a new unique key for the model.
*
* @return string
*/
@@ -38,53 +19,13 @@ public function newUniqueId()
}
/**
- * Retrieve the model for a bound value.
+ * Determine if given key is valid.
*
- * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $query
* @param mixed $value
- * @param string|null $field
- * @return \Illuminate\Contracts\Database\Eloquent\Builder
- *
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- */
- public function resolveRouteBindingQuery($query, $value, $field = null)
- {
- if ($field && in_array($field, $this->uniqueIds()) && ! Str::isUuid($value)) {
- throw (new ModelNotFoundException)->setModel(get_class($this), $value);
- }
-
- if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! Str::isUuid($value)) {
- throw (new ModelNotFoundException)->setModel(get_class($this), $value);
- }
-
- return parent::resolveRouteBindingQuery($query, $value, $field);
- }
-
- /**
- * Get the auto-incrementing key type.
- *
- * @return string
- */
- public function getKeyType()
- {
- if (in_array($this->getKeyName(), $this->uniqueIds())) {
- return 'string';
- }
-
- return $this->keyType;
- }
-
- /**
- * Get the value indicating whether the IDs are incrementing.
- *
* @return bool
*/
- public function getIncrementing()
+ protected function isValidUniqueId($value): bool
{
- if (in_array($this->getKeyName(), $this->uniqueIds())) {
- return false;
- }
-
- return $this->incrementing;
+ return Str::isUuid($value);
}
}
diff --git a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php
index 232c824d1b1f..6e8b64e9b89c 100755
--- a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php
+++ b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php
@@ -22,6 +22,13 @@ class PostgresGrammar extends Grammar
'is distinct from', 'is not distinct from',
];
+ /**
+ * The Postgres grammar specific custom operators.
+ *
+ * @var array
+ */
+ protected static $customOperators = [];
+
/**
* The grammar specific bitwise operators.
*
@@ -31,6 +38,13 @@ class PostgresGrammar extends Grammar
'~', '&', '|', '#', '<<', '>>', '<<=', '>>=',
];
+ /**
+ * Indicates if the cascade option should be used when truncating.
+ *
+ * @var bool
+ */
+ protected static $cascadeTruncate = true;
+
/**
* Compile a basic where clause.
*
@@ -646,7 +660,7 @@ protected function compileDeleteWithJoinsOrLimit(Builder $query)
*/
public function compileTruncate(Builder $query)
{
- return ['truncate '.$this->wrapTable($query->from).' restart identity cascade' => []];
+ return ['truncate '.$this->wrapTable($query->from).' restart identity'.(static::$cascadeTruncate ? ' cascade' : '') => []];
}
/**
@@ -772,4 +786,38 @@ public function substituteBindingsIntoRawSql($sql, $bindings)
return $query;
}
+
+ /**
+ * Get the Postgres grammar specific operators.
+ *
+ * @return array
+ */
+ public function getOperators()
+ {
+ return array_values(array_unique(array_merge(parent::getOperators(), static::$customOperators)));
+ }
+
+ /**
+ * Set any Postgres grammar specific custom operators.
+ *
+ * @param array $operators
+ * @return void
+ */
+ public static function customOperators(array $operators)
+ {
+ static::$customOperators = array_values(
+ array_merge(static::$customOperators, array_filter(array_filter($operators, 'is_string')))
+ );
+ }
+
+ /**
+ * Enable or disable the "cascade" option when compiling the truncate statement.
+ *
+ * @param bool $value
+ * @return void
+ */
+ public static function cascadeOnTrucate(bool $value = true)
+ {
+ static::$cascadeTruncate = $value;
+ }
}
diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php
index 28dc472cf252..0d34270ceffe 100755
--- a/src/Illuminate/Database/Schema/Blueprint.php
+++ b/src/Illuminate/Database/Schema/Blueprint.php
@@ -1456,12 +1456,14 @@ public function computed($column, $expression)
* Create a new vector column on the table.
*
* @param string $column
- * @param int $dimensions
+ * @param int|null $dimensions
* @return \Illuminate\Database\Schema\ColumnDefinition
*/
- public function vector($column, $dimensions)
+ public function vector($column, $dimensions = null)
{
- return $this->addColumn('vector', $column, compact('dimensions'));
+ $options = $dimensions ? compact('dimensions') : [];
+
+ return $this->addColumn('vector', $column, $options);
}
/**
diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php
index 667a06ebe9f4..0bd6a4b22af0 100755
--- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php
+++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php
@@ -1128,7 +1128,9 @@ protected function typeComputed(Fluent $column)
*/
protected function typeVector(Fluent $column)
{
- return "vector($column->dimensions)";
+ return isset($column->dimensions) && $column->dimensions !== ''
+ ? "vector({$column->dimensions})"
+ : 'vector';
}
/**
diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php
index 85b57df62f50..6ec9221ff3d8 100755
--- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php
+++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php
@@ -1078,7 +1078,9 @@ protected function typeGeography(Fluent $column)
*/
protected function typeVector(Fluent $column)
{
- return "vector($column->dimensions)";
+ return isset($column->dimensions) && $column->dimensions !== ''
+ ? "vector({$column->dimensions})"
+ : 'vector';
}
/**
diff --git a/src/Illuminate/Events/QueuedClosure.php b/src/Illuminate/Events/QueuedClosure.php
index 31a462ace41f..8ec63a84711b 100644
--- a/src/Illuminate/Events/QueuedClosure.php
+++ b/src/Illuminate/Events/QueuedClosure.php
@@ -5,6 +5,8 @@
use Closure;
use Laravel\SerializableClosure\SerializableClosure;
+use function Illuminate\Support\enum_value;
+
class QueuedClosure
{
/**
@@ -69,12 +71,12 @@ public function onConnection($connection)
/**
* Set the desired queue for the job.
*
- * @param string|null $queue
+ * @param \BackedEnum|string|null $queue
* @return $this
*/
public function onQueue($queue)
{
- $this->queue = $queue;
+ $this->queue = enum_value($queue);
return $this;
}
diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php
index b8cd8d7c33e7..1a6ecc195856 100755
--- a/src/Illuminate/Foundation/Application.php
+++ b/src/Illuminate/Foundation/Application.php
@@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig
*
* @var string
*/
- const VERSION = '11.29.0';
+ const VERSION = '11.30.0';
/**
* The base path for the Laravel installation.
@@ -1465,6 +1465,17 @@ public function setDeferredServices(array $services)
$this->deferredServices = $services;
}
+ /**
+ * Determine if the given service is a deferred service.
+ *
+ * @param string $service
+ * @return bool
+ */
+ public function isDeferredService($service)
+ {
+ return isset($this->deferredServices[$service]);
+ }
+
/**
* Add an array of services to the application's deferred services.
*
@@ -1477,14 +1488,16 @@ public function addDeferredServices(array $services)
}
/**
- * Determine if the given service is a deferred service.
+ * Remove an array of services from the application's deferred services.
*
- * @param string $service
- * @return bool
+ * @param array $services
+ * @return void
*/
- public function isDeferredService($service)
+ public function removeDeferredServices(array $services)
{
- return isset($this->deferredServices[$service]);
+ foreach ($services as $service) {
+ unset($this->deferredServices[$service]);
+ }
}
/**
diff --git a/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php b/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php
index 19dc16416746..adabdcec0576 100644
--- a/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php
+++ b/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php
@@ -5,6 +5,8 @@
use Illuminate\Contracts\Auth\Access\Gate;
use Illuminate\Support\Str;
+use function Illuminate\Support\enum_value;
+
trait AuthorizesRequests
{
/**
@@ -49,6 +51,8 @@ public function authorizeForUser($user, $ability, $arguments = [])
*/
protected function parseAbilityAndArguments($ability, $arguments)
{
+ $ability = enum_value($ability);
+
if (is_string($ability) && ! str_contains($ability, '\\')) {
return [$ability, $arguments];
}
diff --git a/src/Illuminate/Foundation/Bus/PendingChain.php b/src/Illuminate/Foundation/Bus/PendingChain.php
index 2fb14990c56a..3e6f8729ee2f 100644
--- a/src/Illuminate/Foundation/Bus/PendingChain.php
+++ b/src/Illuminate/Foundation/Bus/PendingChain.php
@@ -8,6 +8,8 @@
use Illuminate\Support\Traits\Conditionable;
use Laravel\SerializableClosure\SerializableClosure;
+use function Illuminate\Support\enum_value;
+
class PendingChain
{
use Conditionable;
@@ -83,12 +85,12 @@ public function onConnection($connection)
/**
* Set the desired queue for the job.
*
- * @param string|null $queue
+ * @param \BackedEnum|string|null $queue
* @return $this
*/
public function onQueue($queue)
{
- $this->queue = $queue;
+ $this->queue = enum_value($queue);
return $this;
}
diff --git a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php
index 89ec3b9cc50f..dde1af231d43 100644
--- a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php
+++ b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php
@@ -269,6 +269,18 @@ public function withMiddleware(?callable $callback = null)
if ($priorities = $middleware->getMiddlewarePriority()) {
$kernel->setMiddlewarePriority($priorities);
}
+
+ if ($priorityAppends = $middleware->getMiddlewarePriorityAppends()) {
+ foreach ($priorityAppends as $middleware => $after) {
+ $kernel->addToMiddlewarePriorityAfter($after, $middleware);
+ }
+ }
+
+ if ($priorityPrepends = $middleware->getMiddlewarePriorityPrepends()) {
+ foreach ($priorityPrepends as $middleware => $before) {
+ $kernel->addToMiddlewarePriorityBefore($before, $middleware);
+ }
+ }
});
return $this;
diff --git a/src/Illuminate/Foundation/Configuration/Middleware.php b/src/Illuminate/Foundation/Configuration/Middleware.php
index 52b83acf518b..52cf908d61d9 100644
--- a/src/Illuminate/Foundation/Configuration/Middleware.php
+++ b/src/Illuminate/Foundation/Configuration/Middleware.php
@@ -145,6 +145,20 @@ class Middleware
*/
protected $priority = [];
+ /**
+ * The middleware to prepend to the middleware priority definition.
+ *
+ * @var array
+ */
+ protected $prependPriority = [];
+
+ /**
+ * The middleware to append to the middleware priority definition.
+ *
+ * @var array
+ */
+ protected $appendPriority = [];
+
/**
* Prepend middleware to the application's global middleware stack.
*
@@ -400,6 +414,34 @@ public function priority(array $priority)
return $this;
}
+ /**
+ * Prepend middleware to the priority middleware.
+ *
+ * @param array|string $before
+ * @param string $prepend
+ * @return $this
+ */
+ public function prependToPriorityList($before, $prepend)
+ {
+ $this->prependPriority[$prepend] = $before;
+
+ return $this;
+ }
+
+ /**
+ * Append middleware to the priority middleware.
+ *
+ * @param array|string $after
+ * @param string $append
+ * @return $this
+ */
+ public function appendToPriorityList($after, $append)
+ {
+ $this->appendPriority[$append] = $after;
+
+ return $this;
+ }
+
/**
* Get the global middleware.
*
@@ -766,4 +808,24 @@ public function getMiddlewarePriority()
{
return $this->priority;
}
+
+ /**
+ * Get the middleware to prepend to the middleware priority definition.
+ *
+ * @return array
+ */
+ public function getMiddlewarePriorityPrepends()
+ {
+ return $this->prependPriority;
+ }
+
+ /**
+ * Get the middleware to append to the middleware priority definition.
+ *
+ * @return array
+ */
+ public function getMiddlewarePriorityAppends()
+ {
+ return $this->appendPriority;
+ }
}
diff --git a/src/Illuminate/Foundation/Console/MailMakeCommand.php b/src/Illuminate/Foundation/Console/MailMakeCommand.php
index 2ec59a15d5a1..d6bf70f799ff 100644
--- a/src/Illuminate/Foundation/Console/MailMakeCommand.php
+++ b/src/Illuminate/Foundation/Console/MailMakeCommand.php
@@ -70,6 +70,10 @@ protected function writeMarkdownTemplate()
str_replace('.', '/', $this->getView()).'.blade.php'
);
+ if ($this->files->exists($path)) {
+ return $this->components->error(sprintf('%s [%s] already exists.', 'Markdown view', $path));
+ }
+
$this->files->ensureDirectoryExists(dirname($path));
$this->files->put($path, file_get_contents(__DIR__.'/stubs/markdown.stub'));
@@ -88,6 +92,10 @@ protected function writeView()
str_replace('.', '/', $this->getView()).'.blade.php'
);
+ if ($this->files->exists($path)) {
+ return $this->components->error(sprintf('%s [%s] already exists.', 'View', $path));
+ }
+
$this->files->ensureDirectoryExists(dirname($path));
$stub = str_replace(
diff --git a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php
index be497edaf88e..c137442a9b12 100644
--- a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php
+++ b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php
@@ -239,6 +239,8 @@ protected function registerExceptionTracking()
*/
protected function registerExceptionRenderer()
{
+ $this->loadViewsFrom(__DIR__.'/../Exceptions/views', 'laravel-exceptions');
+
if (! $this->app->hasDebugModeEnabled()) {
return;
}
diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithContainer.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithContainer.php
index b4aad547cc38..24afbda1ba81 100644
--- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithContainer.php
+++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithContainer.php
@@ -5,6 +5,7 @@
use Closure;
use Illuminate\Foundation\Mix;
use Illuminate\Foundation\Vite;
+use Illuminate\Support\Defer\DeferredCallbackCollection;
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\HtmlString;
use Mockery;
@@ -25,6 +26,13 @@ trait InteractsWithContainer
*/
protected $originalMix;
+ /**
+ * The original deferred callbacks collection.
+ *
+ * @var \Illuminate\Support\Defer\DeferredCallbackCollection|null
+ */
+ protected $originalDeferredCallbacksCollection;
+
/**
* Register an instance of an object in the container.
*
@@ -234,4 +242,38 @@ protected function withMix()
return $this;
}
+
+ /**
+ * Execute deferred functions immediately.
+ *
+ * @return $this
+ */
+ protected function withoutDefer()
+ {
+ if ($this->originalDeferredCallbacksCollection == null) {
+ $this->originalDeferredCallbacksCollection = $this->app->make(DeferredCallbackCollection::class);
+ }
+
+ $this->swap(DeferredCallbackCollection::class, new class extends DeferredCallbackCollection
+ {
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ $value();
+ }
+ });
+ }
+
+ /**
+ * Restore deferred functions.
+ *
+ * @return $this
+ */
+ protected function withDefer()
+ {
+ if ($this->originalDeferredCallbacksCollection) {
+ $this->app->instance(DeferredCallbackCollection::class, $this->originalDeferredCallbacksCollection);
+ }
+
+ return $this;
+ }
}
diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php
index 0b47714d1414..c4744b170d07 100644
--- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php
+++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php
@@ -231,7 +231,7 @@ protected function isSoftDeletableModel($model)
* Cast a JSON string to a database compatible type.
*
* @param array|object|string $value
- * @param string|null $collection
+ * @param string|null $connection
* @return \Illuminate\Contracts\Database\Query\Expression
*/
public function castAsJson($value, $connection = null)
diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php
index 3d210cd7888d..ed058498d86f 100644
--- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php
+++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php
@@ -21,6 +21,7 @@
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Http\Middleware\TrustHosts;
use Illuminate\Http\Middleware\TrustProxies;
+use Illuminate\Queue\Console\WorkCommand;
use Illuminate\Queue\Queue;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Facade;
@@ -178,6 +179,7 @@ protected function tearDownTheTestEnvironment(): void
TrustProxies::flushState();
TrustHosts::flushState();
ValidateCsrfToken::flushState();
+ WorkCommand::flushState();
if ($this->callbackException) {
throw $this->callbackException;
diff --git a/src/Illuminate/Http/Middleware/ValidatePostSize.php b/src/Illuminate/Http/Middleware/ValidatePostSize.php
index bfa620a85f46..8b3519c92842 100644
--- a/src/Illuminate/Http/Middleware/ValidatePostSize.php
+++ b/src/Illuminate/Http/Middleware/ValidatePostSize.php
@@ -21,7 +21,7 @@ public function handle($request, Closure $next)
$max = $this->getPostMaxSize();
if ($max > 0 && $request->server('CONTENT_LENGTH') > $max) {
- throw new PostTooLargeException;
+ throw new PostTooLargeException('The POST data is too large.');
}
return $next($request);
diff --git a/src/Illuminate/Mail/Mailer.php b/src/Illuminate/Mail/Mailer.php
index 93179f701591..bd6484f3ea67 100755
--- a/src/Illuminate/Mail/Mailer.php
+++ b/src/Illuminate/Mail/Mailer.php
@@ -465,7 +465,7 @@ protected function setGlobalToAndRemoveCcAndBcc($message)
* Queue a new mail message for sending.
*
* @param \Illuminate\Contracts\Mail\Mailable|string|array $view
- * @param string|null $queue
+ * @param \BackedEnum|string|null $queue
* @return mixed
*
* @throws \InvalidArgumentException
@@ -486,7 +486,7 @@ public function queue($view, $queue = null)
/**
* Queue a new mail message for sending on the given queue.
*
- * @param string $queue
+ * @param \BackedEnum|string|null $queue
* @param \Illuminate\Contracts\Mail\Mailable $view
* @return mixed
*/
diff --git a/src/Illuminate/Queue/Console/WorkCommand.php b/src/Illuminate/Queue/Console/WorkCommand.php
index e36e352904b9..4e3b94202f94 100644
--- a/src/Illuminate/Queue/Console/WorkCommand.php
+++ b/src/Illuminate/Queue/Console/WorkCommand.php
@@ -76,6 +76,13 @@ class WorkCommand extends Command
*/
protected $latestStartedAt;
+ /**
+ * Indicates if the worker's event listeners have been registered.
+ *
+ * @var bool
+ */
+ private static $hasRegisteredListeners = false;
+
/**
* Create a new queue work command.
*
@@ -172,6 +179,10 @@ protected function gatherWorkerOptions()
*/
protected function listenForEvents()
{
+ if (static::$hasRegisteredListeners) {
+ return;
+ }
+
$this->laravel['events']->listen(JobProcessing::class, function ($event) {
$this->writeOutput($event->job, 'starting');
});
@@ -189,6 +200,8 @@ protected function listenForEvents()
$this->logFailedJob($event);
});
+
+ static::$hasRegisteredListeners = true;
}
/**
@@ -360,4 +373,14 @@ protected function outputUsingJson()
return $this->option('json');
}
+
+ /**
+ * Reset static variables.
+ *
+ * @return void
+ */
+ public static function flushState()
+ {
+ static::$hasRegisteredListeners = false;
+ }
}
diff --git a/src/Illuminate/Routing/ResolvesRouteDependencies.php b/src/Illuminate/Routing/ResolvesRouteDependencies.php
index 9bf2f2523691..135a94c03894 100644
--- a/src/Illuminate/Routing/ResolvesRouteDependencies.php
+++ b/src/Illuminate/Routing/ResolvesRouteDependencies.php
@@ -96,6 +96,9 @@ protected function transformDependency(ReflectionParameter $parameter, $paramete
return $this->container->make($className);
}
+ // If the parameter has a type-hinted class, we will check to see if it is already in
+ // the list of parameters. If it is we will just skip it as it is probably a model
+ // binding and we do not want to mess with those; otherwise, we resolve it here.
if ($className && (! $this->alreadyInParameters($className, $parameters))) {
$isEnum = (new ReflectionClass($className))->isEnum();
diff --git a/src/Illuminate/Support/Facades/App.php b/src/Illuminate/Support/Facades/App.php
index 66f7f8aff318..2c01b3ad1d65 100755
--- a/src/Illuminate/Support/Facades/App.php
+++ b/src/Illuminate/Support/Facades/App.php
@@ -83,8 +83,9 @@
* @method static bool providerIsLoaded(string $provider)
* @method static array getDeferredServices()
* @method static void setDeferredServices(array $services)
- * @method static void addDeferredServices(array $services)
* @method static bool isDeferredService(string $service)
+ * @method static void addDeferredServices(array $services)
+ * @method static void removeDeferredServices(array $services)
* @method static void provideFacades(string $namespace)
* @method static string getLocale()
* @method static string currentLocale()
diff --git a/src/Illuminate/Support/Facades/Blade.php b/src/Illuminate/Support/Facades/Blade.php
index 3d32cf8621c7..01dc7ae76723 100755
--- a/src/Illuminate/Support/Facades/Blade.php
+++ b/src/Illuminate/Support/Facades/Blade.php
@@ -26,7 +26,8 @@
* @method static void aliasComponent(string $path, string|null $alias = null)
* @method static void include(string $path, string|null $alias = null)
* @method static void aliasInclude(string $path, string|null $alias = null)
- * @method static void directive(string $name, callable $handler)
+ * @method static void bindDirective(string $name, callable $handler)
+ * @method static void directive(string $name, callable $handler, bool $bind = false)
* @method static array getCustomDirectives()
* @method static \Illuminate\View\Compilers\BladeCompiler prepareStringsForCompilationUsing(callable $callback)
* @method static void precompiler(callable $precompiler)
diff --git a/src/Illuminate/Support/Facades/DB.php b/src/Illuminate/Support/Facades/DB.php
index e4967fab47fc..80e14dcf9a5d 100644
--- a/src/Illuminate/Support/Facades/DB.php
+++ b/src/Illuminate/Support/Facades/DB.php
@@ -105,7 +105,7 @@
* @method static string getServerVersion()
* @method static void resolverFor(string $driver, \Closure $callback)
* @method static \Closure|null getResolver(string $driver)
- * @method static mixed transaction(\Closure $callback, int $attempts = 1)
+ * @method static void transaction(\Closure $callback, int $attempts = 1)
* @method static void beginTransaction()
* @method static void commit()
* @method static void rollBack(int|null $toLevel = null)
diff --git a/src/Illuminate/Support/Facades/Mail.php b/src/Illuminate/Support/Facades/Mail.php
index 8987aa04cc3a..7223a76e87cc 100755
--- a/src/Illuminate/Support/Facades/Mail.php
+++ b/src/Illuminate/Support/Facades/Mail.php
@@ -28,8 +28,8 @@
* @method static string render(string|array $view, array $data = [])
* @method static \Illuminate\Mail\SentMessage|null send(\Illuminate\Contracts\Mail\Mailable|string|array $view, array $data = [], \Closure|string|null $callback = null)
* @method static \Illuminate\Mail\SentMessage|null sendNow(\Illuminate\Contracts\Mail\Mailable|string|array $mailable, array $data = [], \Closure|string|null $callback = null)
- * @method static mixed queue(\Illuminate\Contracts\Mail\Mailable|string|array $view, string|null $queue = null)
- * @method static mixed onQueue(string $queue, \Illuminate\Contracts\Mail\Mailable $view)
+ * @method static mixed queue(\Illuminate\Contracts\Mail\Mailable|string|array $view, \BackedEnum|string|null $queue = null)
+ * @method static mixed onQueue(\BackedEnum|string|null $queue, \Illuminate\Contracts\Mail\Mailable $view)
* @method static mixed queueOn(string $queue, \Illuminate\Contracts\Mail\Mailable $view)
* @method static mixed later(\DateTimeInterface|\DateInterval|int $delay, \Illuminate\Contracts\Mail\Mailable $view, string|null $queue = null)
* @method static mixed laterOn(string $queue, \DateTimeInterface|\DateInterval|int $delay, \Illuminate\Contracts\Mail\Mailable $view)
diff --git a/src/Illuminate/Testing/Constraints/HasInDatabase.php b/src/Illuminate/Testing/Constraints/HasInDatabase.php
index f17ce8c51afe..f6cd338e9587 100644
--- a/src/Illuminate/Testing/Constraints/HasInDatabase.php
+++ b/src/Illuminate/Testing/Constraints/HasInDatabase.php
@@ -51,7 +51,9 @@ public function __construct(Connection $database, array $data)
*/
public function matches($table): bool
{
- return $this->database->table($table)->where($this->data)->count() > 0;
+ return $this->database->table($table)
+ ->where($this->data)
+ ->exists();
}
/**
diff --git a/src/Illuminate/Testing/Constraints/NotSoftDeletedInDatabase.php b/src/Illuminate/Testing/Constraints/NotSoftDeletedInDatabase.php
index ff8195829f9f..87cef8b6d02d 100644
--- a/src/Illuminate/Testing/Constraints/NotSoftDeletedInDatabase.php
+++ b/src/Illuminate/Testing/Constraints/NotSoftDeletedInDatabase.php
@@ -61,7 +61,7 @@ public function matches($table): bool
return $this->database->table($table)
->where($this->data)
->whereNull($this->deletedAtColumn)
- ->count() > 0;
+ ->exists();
}
/**
diff --git a/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php b/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php
index baaeee27a181..0d14f83b6c67 100644
--- a/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php
+++ b/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php
@@ -63,7 +63,7 @@ public function matches($table): bool
return $this->database->table($table)
->where($this->data)
->whereNotNull($this->deletedAtColumn)
- ->count() > 0;
+ ->exists();
}
/**
diff --git a/src/Illuminate/Translation/Translator.php b/src/Illuminate/Translation/Translator.php
index 5f9b1e299896..aff3291e3267 100755
--- a/src/Illuminate/Translation/Translator.php
+++ b/src/Illuminate/Translation/Translator.php
@@ -118,7 +118,7 @@ public function has($key, $locale = null, $fallback = true)
$locale = $locale ?: $this->locale;
// We should temporarily disable the handling of missing translation keys
- // while perfroming the existence check. After the check, we will turn
+ // while performing the existence check. After the check, we will turn
// the missing translation keys handling back to its original value.
$handleMissingTranslationKeys = $this->handleMissingTranslationKeys;
@@ -200,7 +200,7 @@ public function get($key, array $replace = [], $locale = null, $fallback = true)
public function choice($key, $number, array $replace = [], $locale = null)
{
$line = $this->get(
- $key, $replace, $locale = $this->localeForChoice($key, $locale)
+ $key, [], $locale = $this->localeForChoice($key, $locale)
);
// If the given "number" is actually an array or countable we will simply count the
diff --git a/src/Illuminate/View/Compilers/BladeCompiler.php b/src/Illuminate/View/Compilers/BladeCompiler.php
index 7911462ebf32..396c21a45099 100644
--- a/src/Illuminate/View/Compilers/BladeCompiler.php
+++ b/src/Illuminate/View/Compilers/BladeCompiler.php
@@ -934,22 +934,37 @@ public function aliasInclude($path, $alias = null)
});
}
+ /**
+ * Register a handler for custom directives, binding the handler to the compiler.
+ *
+ * @param string $name
+ * @param callable $handler
+ * @return void
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function bindDirective($name, callable $handler)
+ {
+ $this->directive($name, $handler, bind: true);
+ }
+
/**
* Register a handler for custom directives.
*
* @param string $name
* @param callable $handler
+ * @param bool $bind
* @return void
*
* @throws \InvalidArgumentException
*/
- public function directive($name, callable $handler)
+ public function directive($name, callable $handler, bool $bind = false)
{
if (! preg_match('/^\w+(?:::\w+)?$/x', $name)) {
throw new InvalidArgumentException("The directive name [{$name}] is not valid. Directive names must only contain alphanumeric characters and underscores.");
}
- $this->customDirectives[$name] = $handler;
+ $this->customDirectives[$name] = $bind ? $handler->bindTo($this, BladeCompiler::class) : $handler;
}
/**
diff --git a/tests/Database/DatabasePostgresQueryGrammarTest.php b/tests/Database/DatabasePostgresQueryGrammarTest.php
index 6b2e2826ac0d..2fc526946ef5 100755
--- a/tests/Database/DatabasePostgresQueryGrammarTest.php
+++ b/tests/Database/DatabasePostgresQueryGrammarTest.php
@@ -3,6 +3,7 @@
namespace Illuminate\Tests\Database;
use Illuminate\Database\Connection;
+use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Grammars\PostgresGrammar;
use Mockery as m;
use PHPUnit\Framework\TestCase;
@@ -28,4 +29,46 @@ public function testToRawSql()
$this->assertSame('select * from "users" where \'{}\' ? \'Hello\\\'\\\'World?\' AND "email" = \'foo\'', $query);
}
+
+ public function testCustomOperators()
+ {
+ PostgresGrammar::customOperators(['@@@', '@>', '']);
+ PostgresGrammar::customOperators(['@@>', 1]);
+
+ $connection = m::mock(Connection::class);
+ $grammar = new PostgresGrammar;
+ $grammar->setConnection($connection);
+
+ $operators = $grammar->getOperators();
+
+ $this->assertIsList($operators);
+ $this->assertContains('@@@', $operators);
+ $this->assertContains('@@>', $operators);
+ $this->assertNotContains('', $operators);
+ $this->assertNotContains(1, $operators);
+ $this->assertSame(array_unique($operators), $operators);
+ }
+
+ public function testCompileTruncate()
+ {
+ $postgres = new PostgresGrammar;
+ $builder = m::mock(Builder::class);
+ $builder->from = 'users';
+
+ $this->assertEquals([
+ 'truncate "users" restart identity cascade' => [],
+ ], $postgres->compileTruncate($builder));
+
+ PostgresGrammar::cascadeOnTrucate(false);
+
+ $this->assertEquals([
+ 'truncate "users" restart identity' => [],
+ ], $postgres->compileTruncate($builder));
+
+ PostgresGrammar::cascadeOnTrucate();
+
+ $this->assertEquals([
+ 'truncate "users" restart identity cascade' => [],
+ ], $postgres->compileTruncate($builder));
+ }
}
diff --git a/tests/Foundation/FoundationAuthorizesRequestsTraitTest.php b/tests/Foundation/FoundationAuthorizesRequestsTraitTest.php
index 1005103aaf66..a628f00169ed 100644
--- a/tests/Foundation/FoundationAuthorizesRequestsTraitTest.php
+++ b/tests/Foundation/FoundationAuthorizesRequestsTraitTest.php
@@ -35,6 +35,24 @@ public function testBasicGateCheck()
$this->assertTrue($_SERVER['_test.authorizes.trait']);
}
+ public function testAcceptsBackedEnumAsAbility()
+ {
+ unset($_SERVER['_test.authorizes.trait.enum']);
+
+ $gate = $this->getBasicGate();
+
+ $gate->define('baz', function () {
+ $_SERVER['_test.authorizes.trait.enum'] = true;
+
+ return true;
+ });
+
+ $response = (new FoundationTestAuthorizeTraitClass)->authorize(TestAbility::BAZ);
+
+ $this->assertInstanceOf(Response::class, $response);
+ $this->assertTrue($_SERVER['_test.authorizes.trait.enum']);
+ }
+
public function testExceptionIsThrownIfGateCheckFails()
{
$this->expectException(AuthorizationException::class);
@@ -163,3 +181,8 @@ public function store($object)
$this->authorize($object);
}
}
+
+enum TestAbility: string
+{
+ case BAZ = 'baz';
+}
diff --git a/tests/Foundation/FoundationInteractsWithDatabaseTest.php b/tests/Foundation/FoundationInteractsWithDatabaseTest.php
index 898334c7ab56..24b02bc180b1 100644
--- a/tests/Foundation/FoundationInteractsWithDatabaseTest.php
+++ b/tests/Foundation/FoundationInteractsWithDatabaseTest.php
@@ -8,6 +8,7 @@
use Illuminate\Database\Query\Builder;
use Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase;
use Illuminate\Foundation\Testing\TestCase as TestingTestCase;
+use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Mockery as m;
use Orchestra\Testbench\Concerns\CreatesApplication;
@@ -39,14 +40,14 @@ protected function tearDown(): void
public function testSeeInDatabaseFindsResults()
{
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertDatabaseHas($this->table, $this->data);
}
public function testAssertDatabaseHasSupportsModelClass()
{
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertDatabaseHas(ProductStub::class, $this->data);
}
@@ -60,7 +61,7 @@ public function testAssertDatabaseHasConstrainsToModel()
...$this->data,
];
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertDatabaseHas(new ProductStub(['id' => 1]), $data);
}
@@ -70,7 +71,7 @@ public function testSeeInDatabaseDoesNotFindResults()
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The table is empty.');
- $builder = $this->mockCountBuilder(0);
+ $builder = $this->mockCountBuilder(false);
$builder->shouldReceive('get')->andReturn(collect());
@@ -83,7 +84,7 @@ public function testSeeInDatabaseFindsNotMatchingResults()
$this->expectExceptionMessage('Found similar results: '.json_encode([['title' => 'Forge']], JSON_PRETTY_PRINT));
- $builder = $this->mockCountBuilder(0);
+ $builder = $this->mockCountBuilder(false);
$builder->shouldReceive('take')->andReturnSelf();
$builder->shouldReceive('get')->andReturn(collect([['title' => 'Forge']]));
@@ -97,8 +98,7 @@ public function testSeeInDatabaseFindsManyNotMatchingResults()
$this->expectExceptionMessage('Found similar results: '.json_encode(['data', 'data', 'data'], JSON_PRETTY_PRINT).' and 2 others.');
- $builder = $this->mockCountBuilder(0);
- $builder->shouldReceive('count')->andReturn(0, 5);
+ $builder = $this->mockCountBuilder(false, countResult: [5, 5]);
$builder->shouldReceive('take')->andReturnSelf();
$builder->shouldReceive('get')->andReturn(
@@ -110,14 +110,14 @@ public function testSeeInDatabaseFindsManyNotMatchingResults()
public function testDontSeeInDatabaseDoesNotFindResults()
{
- $this->mockCountBuilder(0);
+ $this->mockCountBuilder(false);
$this->assertDatabaseMissing($this->table, $this->data);
}
public function testAssertDatabaseMissingSupportsModelClass()
{
- $this->mockCountBuilder(0);
+ $this->mockCountBuilder(false);
$this->assertDatabaseMissing(ProductStub::class, $this->data);
}
@@ -131,7 +131,7 @@ public function testAssertDatabaseMissingConstrainsToModel()
...$this->data,
];
- $this->mockCountBuilder(0);
+ $this->mockCountBuilder(false);
$this->assertDatabaseMissing(new ProductStub(['id' => 1]), $data);
}
@@ -140,7 +140,7 @@ public function testDontSeeInDatabaseFindsResults()
{
$this->expectException(ExpectationFailedException::class);
- $builder = $this->mockCountBuilder(1);
+ $builder = $this->mockCountBuilder(true);
$builder->shouldReceive('take')->andReturnSelf();
$builder->shouldReceive('get')->andReturn(collect([$this->data]));
@@ -150,14 +150,14 @@ public function testDontSeeInDatabaseFindsResults()
public function testAssertTableEntriesCount()
{
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertDatabaseCount($this->table, 1);
}
public function testAssertDatabaseCountSupportModels()
{
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertDatabaseCount(ProductStub::class, 1);
$this->assertDatabaseCount(new ProductStub, 1);
@@ -165,7 +165,7 @@ public function testAssertDatabaseCountSupportModels()
public function testAssertDatabaseEmpty()
{
- $this->mockCountBuilder(0);
+ $this->mockCountBuilder(false);
$this->assertDatabaseEmpty(ProductStub::class);
$this->assertDatabaseEmpty(new ProductStub);
@@ -175,14 +175,14 @@ public function testAssertTableEntriesCountWrong()
{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('Failed asserting that table [products] matches expected entries count of 3. Entries found: 1.');
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertDatabaseCount($this->table, 3);
}
public function testAssertDatabaseMissingPassesWhenDoesNotFindResults()
{
- $this->mockCountBuilder(0);
+ $this->mockCountBuilder(false);
$this->assertDatabaseMissing($this->table, $this->data);
}
@@ -191,7 +191,7 @@ public function testAssertDatabaseMissingFailsWhenFindsResults()
{
$this->expectException(ExpectationFailedException::class);
- $builder = $this->mockCountBuilder(1);
+ $builder = $this->mockCountBuilder(true);
$builder->shouldReceive('get')->andReturn(collect([$this->data]));
@@ -202,7 +202,7 @@ public function testAssertModelMissingPassesWhenDoesNotFindModelResults()
{
$this->data = ['id' => 1];
- $builder = $this->mockCountBuilder(0);
+ $builder = $this->mockCountBuilder(false);
$builder->shouldReceive('get')->andReturn(collect());
@@ -211,14 +211,14 @@ public function testAssertModelMissingPassesWhenDoesNotFindModelResults()
public function testAssertSoftDeletedInDatabaseFindsResults()
{
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertSoftDeleted($this->table, $this->data);
}
public function testAssertSoftDeletedSupportModelStrings()
{
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertSoftDeleted(ProductStub::class, $this->data);
}
@@ -228,7 +228,7 @@ public function testAssertSoftDeletedInDatabaseDoesNotFindResults()
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The table is empty.');
- $builder = $this->mockCountBuilder(0);
+ $builder = $this->mockCountBuilder(false);
$builder->shouldReceive('get')->andReturn(collect());
@@ -242,7 +242,7 @@ public function testAssertSoftDeletedInDatabaseDoesNotFindModelResults()
$this->data = ['id' => 1];
- $builder = $this->mockCountBuilder(0);
+ $builder = $this->mockCountBuilder(false);
$builder->shouldReceive('get')->andReturn(collect());
@@ -257,7 +257,7 @@ public function testAssertSoftDeletedInDatabaseDoesNotFindModelWithCustomColumnR
$model = new CustomProductStub(['id' => 1, 'name' => 'Laravel']);
$this->data = ['id' => 1, 'name' => 'Tailwind'];
- $builder = $this->mockCountBuilder(0, 'trashed_at');
+ $builder = $this->mockCountBuilder(false, 'trashed_at');
$builder->shouldReceive('get')->andReturn(collect());
@@ -272,7 +272,7 @@ public function testAssertSoftDeletedInDatabaseDoesNotFindModePassedViaFcnWithCu
$model = new CustomProductStub(['id' => 1, 'name' => 'Laravel']);
$this->data = ['id' => 1];
- $builder = $this->mockCountBuilder(0, 'trashed_at');
+ $builder = $this->mockCountBuilder(false, 'trashed_at');
$builder->shouldReceive('get')->andReturn(collect());
@@ -281,14 +281,14 @@ public function testAssertSoftDeletedInDatabaseDoesNotFindModePassedViaFcnWithCu
public function testAssertNotSoftDeletedInDatabaseFindsResults()
{
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertNotSoftDeleted($this->table, $this->data);
}
public function testAssertNotSoftDeletedSupportModelStrings()
{
- $this->mockCountBuilder(1);
+ $this->mockCountBuilder(true);
$this->assertNotSoftDeleted(ProductStub::class, $this->data);
}
@@ -298,7 +298,7 @@ public function testAssertNotSoftDeletedOnlyFindsMatchingModels()
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('Failed asserting that any existing row');
- $builder = $this->mockCountBuilder(0);
+ $builder = $this->mockCountBuilder(false);
$builder->shouldReceive('get')->andReturn(collect(), collect(1));
@@ -310,7 +310,7 @@ public function testAssertNotSoftDeletedInDatabaseDoesNotFindResults()
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('The table is empty.');
- $builder = $this->mockCountBuilder(0);
+ $builder = $this->mockCountBuilder(false);
$builder->shouldReceive('get')->andReturn(collect());
@@ -324,7 +324,7 @@ public function testAssertNotSoftDeletedInDatabaseDoesNotFindModelResults()
$this->data = ['id' => 1];
- $builder = $this->mockCountBuilder(0);
+ $builder = $this->mockCountBuilder(false);
$builder->shouldReceive('get')->andReturn(collect());
@@ -339,7 +339,7 @@ public function testAssertNotSoftDeletedInDatabaseDoesNotFindModelWithCustomColu
$model = new CustomProductStub(['id' => 1, 'name' => 'Laravel']);
$this->data = ['id' => 1, 'name' => 'Tailwind'];
- $builder = $this->mockCountBuilder(0, 'trashed_at');
+ $builder = $this->mockCountBuilder(false, 'trashed_at');
$builder->shouldReceive('get')->andReturn(collect());
@@ -354,7 +354,7 @@ public function testAssertNotSoftDeletedInDatabaseDoesNotFindModelPassedViaFcnWi
$model = new CustomProductStub(['id' => 1, 'name' => 'Laravel']);
$this->data = ['id' => 1];
- $builder = $this->mockCountBuilder(0, 'trashed_at');
+ $builder = $this->mockCountBuilder(false, 'trashed_at');
$builder->shouldReceive('get')->andReturn(collect());
@@ -365,7 +365,7 @@ public function testAssertExistsPassesWhenFindsResults()
{
$this->data = ['id' => 1];
- $builder = $this->mockCountBuilder(1);
+ $builder = $this->mockCountBuilder(true);
$builder->shouldReceive('get')->andReturn(collect($this->data));
@@ -482,10 +482,13 @@ public function testExpectsDatabaseQueryCount()
$case->tearDown();
}
- protected function mockCountBuilder($countResult, $deletedAtColumn = 'deleted_at')
+ protected function mockCountBuilder($existsResult, $deletedAtColumn = 'deleted_at', $countResult = null)
{
$builder = m::mock(Builder::class);
+ $countResult = Arr::wrap($countResult);
+ $countResult = ! empty($countResult) ? $countResult : [$existsResult ? 1 : 0];
+
$key = array_key_first($this->data);
$value = $this->data[$key];
@@ -501,7 +504,9 @@ protected function mockCountBuilder($countResult, $deletedAtColumn = 'deleted_at
$builder->shouldReceive('whereNull')->with($deletedAtColumn)->andReturnSelf();
- $builder->shouldReceive('count')->andReturn($countResult)->byDefault();
+ $builder->shouldReceive('exists')->andReturn($existsResult)->byDefault();
+
+ $builder->shouldReceive('count')->andReturn(...$countResult)->byDefault();
$this->connection->shouldReceive('table')
->with($this->table)
diff --git a/tests/Foundation/Testing/Concerns/InteractsWithContainerTest.php b/tests/Foundation/Testing/Concerns/InteractsWithContainerTest.php
index 7b1c279c8659..ed7ad1bc84be 100644
--- a/tests/Foundation/Testing/Concerns/InteractsWithContainerTest.php
+++ b/tests/Foundation/Testing/Concerns/InteractsWithContainerTest.php
@@ -4,6 +4,7 @@
use Illuminate\Foundation\Mix;
use Illuminate\Foundation\Vite;
+use Illuminate\Support\Defer\DeferredCallbackCollection;
use Orchestra\Testbench\TestCase;
use stdClass;
@@ -73,6 +74,26 @@ public function testWithMixRestoresOriginalHandlerAndReturnsInstance()
$this->assertSame($this, $instance);
}
+ public function testWithoutDefer()
+ {
+ $called = [];
+ defer(function () use (&$called) {
+ $called[] = 1;
+ });
+ $this->assertSame([], $called);
+
+ $this->withoutDefer();
+ defer(function () use (&$called) {
+ $called[] = 2;
+ });
+ $this->assertSame([2], $called);
+
+ $this->withDefer();
+ $this->assertSame([2], $called);
+ $this->app[DeferredCallbackCollection::class]->invoke();
+ $this->assertSame([2, 1], $called);
+ }
+
public function testForgetMock()
{
$this->mock(InstanceStub::class)
diff --git a/tests/Integration/Concurrency/ConcurrencyTest.php b/tests/Integration/Concurrency/ConcurrencyTest.php
index 76012712c375..c62295255013 100644
--- a/tests/Integration/Concurrency/ConcurrencyTest.php
+++ b/tests/Integration/Concurrency/ConcurrencyTest.php
@@ -1,6 +1,6 @@
app['files']
+ ->put(
+ $this->app->basePath($existingMarkdownPath),
+ '