diff --git a/src/broadcasting/src/BroadcastManager.php b/src/broadcasting/src/BroadcastManager.php index 7f51ca4b1b..805c72e25a 100644 --- a/src/broadcasting/src/BroadcastManager.php +++ b/src/broadcasting/src/BroadcastManager.php @@ -300,9 +300,10 @@ protected function get(string $name): Broadcaster } /** - * Resolve the given broadcaster with Pool Proxy if need. + * Resolve the given broadcaster with a pool proxy if needed. * * @throws InvalidArgumentException + * @throws RuntimeException */ protected function resolve(string $name): Broadcaster { diff --git a/src/console/src/Concerns/ConfiguresPrompts.php b/src/console/src/Concerns/ConfiguresPrompts.php index d17bb80875..6790f1d544 100644 --- a/src/console/src/Concerns/ConfiguresPrompts.php +++ b/src/console/src/Concerns/ConfiguresPrompts.php @@ -179,6 +179,8 @@ function () use ($prompt): mixed { * @param bool|string $required * @param null|(Closure(PResult): mixed) $validate * @return PResult + * + * @throws PromptValidationException */ protected function promptUntilValid($prompt, $required, $validate) { diff --git a/src/console/src/Scheduling/ManagesFrequencies.php b/src/console/src/Scheduling/ManagesFrequencies.php index ac1df43e14..db2e9535a7 100644 --- a/src/console/src/Scheduling/ManagesFrequencies.php +++ b/src/console/src/Scheduling/ManagesFrequencies.php @@ -124,6 +124,8 @@ public function everyThirtySeconds(): static * Schedule the event to run multiple times per minute. * * @param int<1, 59> $seconds + * + * @throws InvalidArgumentException */ protected function repeatEvery(int $seconds): static { diff --git a/src/console/src/Scheduling/Schedule.php b/src/console/src/Scheduling/Schedule.php index 6259ab79ee..358dce0821 100644 --- a/src/console/src/Scheduling/Schedule.php +++ b/src/console/src/Scheduling/Schedule.php @@ -493,6 +493,8 @@ public static function flushState(): void /** * Dynamically handle calls into the schedule instance. + * + * @throws BadMethodCallException */ public function __call(string $method, array $parameters): mixed { diff --git a/src/console/src/View/Components/Task.php b/src/console/src/View/Components/Task.php index 76cdcb76d6..2561e73a8c 100644 --- a/src/console/src/View/Components/Task.php +++ b/src/console/src/View/Components/Task.php @@ -19,6 +19,8 @@ class Task extends Component * Render the component using the given arguments. * * @param null|(callable(): mixed) $task + * + * @throws Throwable */ public function render(string $description, ?callable $task = null, int $verbosity = OutputInterface::VERBOSITY_NORMAL): void { diff --git a/src/contracts/src/Routing/Registrar.php b/src/contracts/src/Routing/Registrar.php index c6b4a41933..5fd9250043 100644 --- a/src/contracts/src/Routing/Registrar.php +++ b/src/contracts/src/Routing/Registrar.php @@ -40,6 +40,11 @@ public function patch(string $uri, array|string|callable $action): Route; */ public function options(string $uri, array|string|callable $action): Route; + /** + * Register a new QUERY route with the router. + */ + public function query(string $uri, array|string|callable $action): Route; + /** * Register a new route with the given verbs. */ diff --git a/src/database/src/Concerns/BuildsQueries.php b/src/database/src/Concerns/BuildsQueries.php index 82440fa43b..0abb2de37b 100644 --- a/src/database/src/Concerns/BuildsQueries.php +++ b/src/database/src/Concerns/BuildsQueries.php @@ -308,6 +308,9 @@ public function lazyByIdDesc(int $chunkSize = 1000, ?string $column = null, ?str * Query lazily, by chunking the results of a query by comparing IDs in a given order. * * @return LazyCollection + * + * @throws InvalidArgumentException + * @throws RuntimeException if the ID column is missing while iterating the results */ protected function orderedLazyById(int $chunkSize = 1000, ?string $column = null, ?string $alias = null, SortDirection|bool $descending = false): LazyCollection { diff --git a/src/database/src/Concerns/CompilesJsonPaths.php b/src/database/src/Concerns/CompilesJsonPaths.php index 57b39d6af6..a594f15920 100644 --- a/src/database/src/Concerns/CompilesJsonPaths.php +++ b/src/database/src/Concerns/CompilesJsonPaths.php @@ -30,6 +30,7 @@ protected function wrapJsonPath(string $value, string $delimiter = '->'): string { $value = preg_replace("/([\\\\]+)?\\'/", "'", $value); + // Keep this explode() to avoid an extra Stringable allocation per call. $jsonPath = (new Collection(explode($delimiter, $value))) ->map(fn ($segment) => $this->wrapJsonPathSegment($segment)) ->join('.'); diff --git a/src/database/src/Console/MonitorCommand.php b/src/database/src/Console/MonitorCommand.php index 5d01159912..3d1ee36dcf 100644 --- a/src/database/src/Console/MonitorCommand.php +++ b/src/database/src/Console/MonitorCommand.php @@ -8,6 +8,7 @@ use Hypervel\Database\ConnectionResolverInterface; use Hypervel\Database\Events\DatabaseBusy; use Hypervel\Support\Collection; +use Hypervel\Support\Stringable; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'db:monitor')] @@ -65,7 +66,7 @@ public function handle(): void */ protected function parseDatabases(?string $databases): Collection { - return (new Collection(explode(',', $databases ?? '')))->map(function ($database) { + return (new Stringable($databases))->explode(',')->map(function ($database) { if ($database === '') { $database = $this->hypervel->make('config')->string('database.default'); } diff --git a/src/database/src/Eloquent/Casts/AsCollection.php b/src/database/src/Eloquent/Casts/AsCollection.php index b6c33f9b47..7f0d63635f 100644 --- a/src/database/src/Eloquent/Casts/AsCollection.php +++ b/src/database/src/Eloquent/Casts/AsCollection.php @@ -22,11 +22,19 @@ class AsCollection implements Castable public static function castUsing(array $arguments): CastsAttributes { return new class($arguments) implements CastsAttributes { + /** + * Create a new collection cast instance. + */ public function __construct(protected array $arguments) { $this->arguments = array_pad(array_values($this->arguments), 2, ''); } + /** + * Transform the attribute from the underlying model values. + * + * @throws InvalidArgumentException + */ public function get(Model $model, string $key, mixed $value, array $attributes): ?Collection { if (! isset($attributes[$key])) { @@ -60,6 +68,9 @@ public function get(Model $model, string $key, mixed $value, array $attributes): : $instance->mapInto($this->arguments[1][0]); } + /** + * Transform the attribute to its underlying model values. + */ public function set(Model $model, string $key, mixed $value, array $attributes): array { $encoded = Json::encode($value); diff --git a/src/database/src/Eloquent/Casts/AsEncryptedCollection.php b/src/database/src/Eloquent/Casts/AsEncryptedCollection.php index ed0150518d..d377c28ed6 100644 --- a/src/database/src/Eloquent/Casts/AsEncryptedCollection.php +++ b/src/database/src/Eloquent/Casts/AsEncryptedCollection.php @@ -23,11 +23,19 @@ class AsEncryptedCollection implements Castable public static function castUsing(array $arguments): CastsAttributes { return new class($arguments) implements CastsAttributes { + /** + * Create a new encrypted collection cast instance. + */ public function __construct(protected array $arguments) { $this->arguments = array_pad(array_values($this->arguments), 2, ''); } + /** + * Transform the attribute from the underlying model values. + * + * @throws InvalidArgumentException + */ public function get(Model $model, string $key, mixed $value, array $attributes): ?Collection { $collectionClass = empty($this->arguments[0]) ? Collection::class : $this->arguments[0]; @@ -61,6 +69,9 @@ public function get(Model $model, string $key, mixed $value, array $attributes): : $instance->mapInto($this->arguments[1][0]); } + /** + * Transform the attribute to its underlying model values. + */ public function set(Model $model, string $key, mixed $value, array $attributes): ?array { if (! is_null($value)) { diff --git a/src/database/src/Eloquent/Concerns/HasAttributes.php b/src/database/src/Eloquent/Concerns/HasAttributes.php index 7e4a746ad5..aa849bf819 100644 --- a/src/database/src/Eloquent/Concerns/HasAttributes.php +++ b/src/database/src/Eloquent/Concerns/HasAttributes.php @@ -595,6 +595,8 @@ public function isRelation(string $key): bool /** * Handle a lazy loading violation. + * + * @throws LazyLoadingViolationException */ protected function handleLazyLoadingViolation(string $key): mixed { @@ -773,6 +775,8 @@ protected function flushCastCaches(): void /** * Ensure that the given casts are strings. + * + * @throws InvalidArgumentException */ protected function ensureCastsAreStringValues(array $casts): array { @@ -1265,6 +1269,8 @@ protected function getEnumCaseFromValue(string $enumClass, string|int $value): m * Get the storable value from the given enum. * * @param UnitEnum $value + * + * @throws ValueError */ protected function getStorableEnumValue(string $expectedEnum, mixed $value): string|int { @@ -1303,6 +1309,8 @@ protected function getArrayAttributeByKey(string $key): array /** * Cast the given attribute to JSON. + * + * @throws JsonEncodingException */ protected function castAttributeAsJson(string $key, mixed $value): string { @@ -1390,6 +1398,8 @@ public static function currentEncrypter(): EncrypterContract /** * Cast the given attribute to a hashed string. + * + * @throws RuntimeException */ protected function castAttributeAsHashedString(string $key, #[SensitiveParameter] mixed $value): ?string { @@ -1429,6 +1439,8 @@ public function fromFloat(mixed $value): float /** * Return a decimal as string. + * + * @throws MathException */ protected function asDecimal(float|string $value, int $decimals): string { diff --git a/src/database/src/Eloquent/Concerns/QueriesRelationships.php b/src/database/src/Eloquent/Concerns/QueriesRelationships.php index 6a4efb08a1..8987056050 100644 --- a/src/database/src/Eloquent/Concerns/QueriesRelationships.php +++ b/src/database/src/Eloquent/Concerns/QueriesRelationships.php @@ -692,6 +692,7 @@ protected function getMorphTypeForQuery(string $model): string * * @param EloquentCollection|Model $related * + * @throws InvalidArgumentException * @throws RelationNotFoundException */ public function whereBelongsTo(mixed $related, ?string $relationshipName = null, string $boolean = 'and'): static @@ -733,8 +734,6 @@ public function whereBelongsTo(mixed $related, ?string $relationshipName = null, /** * Add a "BelongsTo" relationship with an "or where" clause to the query. - * - * @throws RuntimeException */ public function orWhereBelongsTo(mixed $related, ?string $relationshipName = null): static { @@ -746,6 +745,7 @@ public function orWhereBelongsTo(mixed $related, ?string $relationshipName = nul * * @param EloquentCollection|Model $related * + * @throws InvalidArgumentException * @throws RelationNotFoundException */ public function whereAttachedTo(mixed $related, ?string $relationshipName = null, string $boolean = 'and'): static diff --git a/src/database/src/Eloquent/Concerns/TransformsToResource.php b/src/database/src/Eloquent/Concerns/TransformsToResource.php index cdb885285a..b05037f1b6 100644 --- a/src/database/src/Eloquent/Concerns/TransformsToResource.php +++ b/src/database/src/Eloquent/Concerns/TransformsToResource.php @@ -31,6 +31,8 @@ public function toResource(?string $resourceClass = null): JsonResource /** * Guess the resource class for the model. + * + * @throws LogicException */ protected function guessResource(): JsonResource { diff --git a/src/database/src/Eloquent/MassPrunable.php b/src/database/src/Eloquent/MassPrunable.php index 06abe1ddb4..54de357531 100644 --- a/src/database/src/Eloquent/MassPrunable.php +++ b/src/database/src/Eloquent/MassPrunable.php @@ -47,6 +47,8 @@ public function pruneAll(int $chunkSize = 1000): int * Get the prunable model query. * * @return Builder + * + * @throws LogicException */ public function prunable(): Builder { diff --git a/src/database/src/Eloquent/Model.php b/src/database/src/Eloquent/Model.php index d566333c26..a5b7283918 100644 --- a/src/database/src/Eloquent/Model.php +++ b/src/database/src/Eloquent/Model.php @@ -367,6 +367,9 @@ public function __construct(array $attributes = []) /** * Check if the model needs to be booted and if so, do it. + * + * @throws LogicException + * @throws RuntimeException */ protected function bootIfNotBooted(): void { diff --git a/src/database/src/Eloquent/PendingHasThroughRelationship.php b/src/database/src/Eloquent/PendingHasThroughRelationship.php index 3068fe09c2..4bc946fb18 100644 --- a/src/database/src/Eloquent/PendingHasThroughRelationship.php +++ b/src/database/src/Eloquent/PendingHasThroughRelationship.php @@ -104,6 +104,8 @@ public function has(callable|string $callback): mixed /** * Handle dynamic method calls into the model. + * + * @throws BadMethodCallException */ public function __call(string $method, array $parameters): mixed { diff --git a/src/database/src/Eloquent/Prunable.php b/src/database/src/Eloquent/Prunable.php index 0069897899..b9d9faefa3 100644 --- a/src/database/src/Eloquent/Prunable.php +++ b/src/database/src/Eloquent/Prunable.php @@ -8,12 +8,15 @@ use Hypervel\Contracts\Events\Dispatcher; use Hypervel\Database\Events\ModelsPruned; use LogicException; +use Swoole\Coroutine\CanceledException; use Throwable; trait Prunable { /** * Prune all prunable models in the database. + * + * @throws Throwable */ public function pruneAll(int $chunkSize = 1000): int { @@ -29,14 +32,10 @@ public function pruneAll(int $chunkSize = 1000): int $model->prune(); ++$total; + } catch (CanceledException $exception) { + throw $exception; } catch (Throwable $e) { - $handler = app(ExceptionHandler::class); - - if ($handler) { - $handler->report($e); - } else { - throw $e; - } + app(ExceptionHandler::class)->report($e); } }); @@ -54,6 +53,8 @@ public function pruneAll(int $chunkSize = 1000): int * Get the prunable model query. * * @return Builder + * + * @throws LogicException */ public function prunable(): Builder { diff --git a/src/database/src/Grammar.php b/src/database/src/Grammar.php index 77cc86445b..96ce294be6 100755 --- a/src/database/src/Grammar.php +++ b/src/database/src/Grammar.php @@ -61,6 +61,7 @@ public function wrapTable(Expression|string $table, ?string $prefix = null): str if (str_contains($table, '.')) { $table = substr_replace($table, '.' . $prefix, strrpos($table, '.'), 1); + // Keep this explode() to avoid an extra Stringable allocation per call. return (new Collection(explode('.', $table))) ->map($this->wrapValue(...)) ->implode('.'); diff --git a/src/database/src/Query/Builder.php b/src/database/src/Query/Builder.php index f1828352a4..8d683ca5e8 100644 --- a/src/database/src/Query/Builder.php +++ b/src/database/src/Query/Builder.php @@ -1262,6 +1262,8 @@ public function orWhereNullSafeEquals(ExpressionContract|string $column, mixed $ /** * Add a "where in" clause to the query. + * + * @throws InvalidArgumentException */ public function whereIn(ExpressionContract|string $column, mixed $values, string $boolean = 'and', bool $not = false): static { @@ -3877,6 +3879,8 @@ public function update(array $values): int /** * Update records in a PostgreSQL database using the update from syntax. + * + * @throws LogicException */ public function updateFrom(array $values): int { @@ -4292,6 +4296,8 @@ public function getConnection(): ConnectionInterface /** * Ensure the database connection supports vector queries. + * + * @throws RuntimeException */ protected function ensureConnectionSupportsVectors(): void { diff --git a/src/database/src/Query/Grammars/Grammar.php b/src/database/src/Query/Grammars/Grammar.php index 074fa9652f..2e7f0002d5 100755 --- a/src/database/src/Query/Grammars/Grammar.php +++ b/src/database/src/Query/Grammars/Grammar.php @@ -314,6 +314,8 @@ protected function whereBitwise(Builder $query, array $where): string /** * Compile a "where like" clause. + * + * @throws RuntimeException */ protected function whereLike(Builder $query, array $where): string { @@ -695,6 +697,8 @@ public function compileJsonValueCast(string $value): string /** * Compile a "where fulltext" clause. + * + * @throws RuntimeException */ public function whereFullText(Builder $query, array $where): string { diff --git a/src/database/src/Schema/Builder.php b/src/database/src/Schema/Builder.php index 52286ce4f6..66a102f12f 100755 --- a/src/database/src/Schema/Builder.php +++ b/src/database/src/Schema/Builder.php @@ -325,6 +325,8 @@ public function whenTableDoesntHaveIndex(string $table, array|string $index, Clo /** * Get the data type for the given column name. + * + * @throws InvalidArgumentException */ public function getColumnType(string $table, string $column, bool $fullDefinition = false): string { @@ -691,6 +693,8 @@ public function ensureVectorExtensionExists(?string $schema = null): void /** * Create a new extension on the schema if it does not exist. + * + * @throws RuntimeException */ public function ensureExtensionExists(string $name, ?string $schema = null): void { diff --git a/src/database/src/Schema/Grammars/Grammar.php b/src/database/src/Schema/Grammars/Grammar.php index 4c866692e2..a65f4caf9d 100755 --- a/src/database/src/Schema/Grammars/Grammar.php +++ b/src/database/src/Schema/Grammars/Grammar.php @@ -62,6 +62,8 @@ public function compileDropDatabaseIfExists(string $name): string /** * Compile the query to determine the schemas. + * + * @throws RuntimeException */ public function compileSchemas(): string { @@ -245,6 +247,8 @@ public function compileForeign(Blueprint $blueprint, Fluent $command): ?string /** * Compile a drop foreign key command. + * + * @throws RuntimeException */ public function compileDropForeign(Blueprint $blueprint, Fluent $command): array|string|null { diff --git a/src/database/src/Schema/Grammars/MySqlGrammar.php b/src/database/src/Schema/Grammars/MySqlGrammar.php index d1f27862f1..d0a1df761e 100755 --- a/src/database/src/Schema/Grammars/MySqlGrammar.php +++ b/src/database/src/Schema/Grammars/MySqlGrammar.php @@ -10,6 +10,7 @@ use Hypervel\Database\Schema\ColumnDefinition; use Hypervel\Support\Collection; use Hypervel\Support\Fluent; +use Hypervel\Support\Stringable; use Override; /** @@ -612,7 +613,7 @@ public function compileTableComment(Blueprint $blueprint, Fluent $command): stri public function escapeNames(array $names): array { return array_map( - fn ($name) => (new Collection(explode('.', $name)))->map($this->wrapValue(...))->implode('.'), + fn ($name) => (new Stringable($name))->explode('.')->map($this->wrapValue(...))->implode('.'), $names ); } diff --git a/src/database/src/Schema/Grammars/PostgresGrammar.php b/src/database/src/Schema/Grammars/PostgresGrammar.php index db2676b47c..bcab3bb32e 100755 --- a/src/database/src/Schema/Grammars/PostgresGrammar.php +++ b/src/database/src/Schema/Grammars/PostgresGrammar.php @@ -6,8 +6,8 @@ use Hypervel\Database\Query\Expression; use Hypervel\Database\Schema\Blueprint; -use Hypervel\Support\Collection; use Hypervel\Support\Fluent; +use Hypervel\Support\Stringable; use Override; class PostgresGrammar extends Grammar @@ -639,7 +639,7 @@ public function compileTableComment(Blueprint $blueprint, Fluent $command): stri public function escapeNames(array $names): array { return array_map( - fn ($name) => (new Collection(explode('.', $name)))->map($this->wrapValue(...))->implode('.'), + fn ($name) => (new Stringable($name))->explode('.')->map($this->wrapValue(...))->implode('.'), $names ); } diff --git a/src/database/src/Schema/Grammars/SQLiteGrammar.php b/src/database/src/Schema/Grammars/SQLiteGrammar.php index 4e1ee9cd1b..8117d14921 100644 --- a/src/database/src/Schema/Grammars/SQLiteGrammar.php +++ b/src/database/src/Schema/Grammars/SQLiteGrammar.php @@ -713,6 +713,8 @@ public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command): /** * Compile a drop foreign key command. + * + * @throws RuntimeException */ public function compileDropForeign(Blueprint $blueprint, Fluent $command): ?array { diff --git a/src/database/src/Schema/MySqlSchemaState.php b/src/database/src/Schema/MySqlSchemaState.php index c00a02fba6..79a8e9b97c 100644 --- a/src/database/src/Schema/MySqlSchemaState.php +++ b/src/database/src/Schema/MySqlSchemaState.php @@ -12,6 +12,7 @@ use Pdo\Mysql; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; +use Throwable; /** * @property MySqlConnection $connection @@ -157,6 +158,8 @@ protected function baseVariables(array $config): array * Execute the given dump process. * * @param array $variables + * + * @throws Throwable */ protected function executeDumpProcess(Process $process, ?callable $output, array $variables, int $depth = 0): Process { diff --git a/src/docs/http-tests.md b/src/docs/http-tests.md index 3c8dd22d73..0209ab21a9 100644 --- a/src/docs/http-tests.md +++ b/src/docs/http-tests.md @@ -70,6 +70,8 @@ $response = $this->query('/search', ['filter' => 'active']); $response = $this->queryJson('/search', ['filter' => 'active']); ``` +Because these requests are simulated, they aren't affected by the [HTTP/1.1 limitation on `QUERY` routes](/docs/{{version}}/routing#available-router-methods) that applies in production. + Instead of returning an `Hypervel\Http\Response` instance, test request methods return an instance of `Hypervel\Testing\TestResponse`, which provides a [variety of helpful assertions](#available-assertions) that allow you to inspect your application's responses: ```php tab=Pest diff --git a/src/docs/routing.md b/src/docs/routing.md index 50c6e2f98b..86130c8b5a 100644 --- a/src/docs/routing.md +++ b/src/docs/routing.md @@ -108,8 +108,14 @@ Route::put($uri, $callback); Route::patch($uri, $callback); Route::delete($uri, $callback); Route::options($uri, $callback); +Route::query($uri, $callback); ``` +The `query` method registers a route for HTTP `QUERY` requests. Like `GET`, a `QUERY` request only reads data, but it sends its query in the request body, which suits searches that are too large or complex to fit in a URL. + +> [!WARNING] +> Swoole 6.2.2 rejects HTTP `QUERY` requests sent over HTTP/1.1 with a `400` response before they reach your application. Sending `QUERY` requests requires HTTP/2 on the connection to Hypervel, including the connection from any reverse proxy. + Sometimes you may need to register a route that responds to multiple HTTP verbs. You may do so using the `match` method. Or, you may even register a route that responds to all HTTP verbs using the `any` method: ```php @@ -123,7 +129,7 @@ Route::any('/', function () { ``` > [!NOTE] -> When defining multiple routes that share the same URI, routes using the `get`, `post`, `put`, `patch`, `delete`, and `options` methods should be defined before routes using the `any`, `match`, and `redirect` methods. This ensures the incoming request is matched with the correct route. +> When defining multiple routes that share the same URI, routes using the `get`, `post`, `put`, `patch`, `delete`, `options`, and `query` methods should be defined before routes using the `any`, `match`, and `redirect` methods. This ensures the incoming request is matched with the correct route. #### Dependency Injection diff --git a/src/filesystem/src/FileResponseBuilder.php b/src/filesystem/src/FileResponseBuilder.php index 41c439e35a..b16672aa3e 100644 --- a/src/filesystem/src/FileResponseBuilder.php +++ b/src/filesystem/src/FileResponseBuilder.php @@ -49,9 +49,7 @@ public function build( ); } - $headers['Accept-Ranges'] = in_array($request->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) - ? 'bytes' - : 'none'; + $headers['Accept-Ranges'] = $request->isMethodSafe() ? 'bytes' : 'none'; $response = new IterableStreamedResponse([]); diff --git a/src/foundation/src/Console/AboutCommand.php b/src/foundation/src/Console/AboutCommand.php index a728c548f8..29448d207f 100644 --- a/src/foundation/src/Console/AboutCommand.php +++ b/src/foundation/src/Console/AboutCommand.php @@ -292,7 +292,7 @@ protected static function addToSection(string $section, callable|string|array $d */ protected function sections(): array { - return (new Collection(explode(',', $this->option('only') ?? ''))) + return (new Stringable($this->option('only') ?? ''))->explode(',') ->filter() ->map(fn ($only) => $this->toSearchKeyword($only)) ->all(); diff --git a/src/foundation/src/Console/MailMakeCommand.php b/src/foundation/src/Console/MailMakeCommand.php index b72e08210d..76ffa1b378 100644 --- a/src/foundation/src/Console/MailMakeCommand.php +++ b/src/foundation/src/Console/MailMakeCommand.php @@ -7,8 +7,8 @@ use Hypervel\Console\Concerns\CreatesMatchingTest; use Hypervel\Console\GeneratorCommand; use Hypervel\Foundation\Inspiring; -use Hypervel\Support\Collection; use Hypervel\Support\Str; +use Hypervel\Support\Stringable; use Override; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputInterface; @@ -163,7 +163,7 @@ protected function getView(): string if (! $view) { $name = str_replace('\\', '/', $this->argument('name')); - $view = 'mail.' . (new Collection(explode('/', $name))) + $view = 'mail.' . (new Stringable($name))->explode('/') ->map(fn ($part) => Str::kebab($part)) ->implode('.'); } diff --git a/src/foundation/src/Console/NotificationMakeCommand.php b/src/foundation/src/Console/NotificationMakeCommand.php index 9c2e573c57..c1ff5fea40 100644 --- a/src/foundation/src/Console/NotificationMakeCommand.php +++ b/src/foundation/src/Console/NotificationMakeCommand.php @@ -6,8 +6,8 @@ use Hypervel\Console\Concerns\CreatesMatchingTest; use Hypervel\Console\GeneratorCommand; -use Hypervel\Support\Collection; use Hypervel\Support\Str; +use Hypervel\Support\Stringable; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -130,7 +130,7 @@ protected function afterPromptingForMissingArguments(InputInterface $input, Outp $wantsMarkdownView = confirm('Would you like to create a markdown view?'); if ($wantsMarkdownView) { - $defaultMarkdownView = (new Collection(explode('/', str_replace('\\', '/', $this->argument('name'))))) + $defaultMarkdownView = (new Stringable($this->argument('name')))->replace('\\', '/')->explode('/') ->map(fn ($path) => Str::kebab($path)) ->prepend('mail') ->implode('.'); diff --git a/src/foundation/src/Console/OptimizeClearCommand.php b/src/foundation/src/Console/OptimizeClearCommand.php index bf40e1d230..5c8949f0b4 100644 --- a/src/foundation/src/Console/OptimizeClearCommand.php +++ b/src/foundation/src/Console/OptimizeClearCommand.php @@ -7,6 +7,7 @@ use Hypervel\Console\Command; use Hypervel\Support\Collection; use Hypervel\Support\ServiceProvider; +use Hypervel\Support\Stringable; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'optimize:clear')] @@ -29,7 +30,7 @@ public function handle(): void { $this->components->info('Clearing cached bootstrap files.'); - $exceptions = Collection::wrap(explode(',', $this->option('except') ?? '')) + $exceptions = (new Stringable($this->option('except') ?? ''))->explode(',') ->map(fn ($except) => trim($except)) ->filter() ->unique() diff --git a/src/foundation/src/Console/OptimizeCommand.php b/src/foundation/src/Console/OptimizeCommand.php index 06e22e4d74..cca0c6ec25 100644 --- a/src/foundation/src/Console/OptimizeCommand.php +++ b/src/foundation/src/Console/OptimizeCommand.php @@ -7,6 +7,7 @@ use Hypervel\Console\Command; use Hypervel\Support\Collection; use Hypervel\Support\ServiceProvider; +use Hypervel\Support\Stringable; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'optimize')] @@ -29,7 +30,7 @@ public function handle(): void { $this->components->info('Caching framework bootstrap, configuration, and metadata.'); - $exceptions = Collection::wrap(explode(',', $this->option('except') ?? '')) + $exceptions = (new Stringable($this->option('except') ?? ''))->explode(',') ->map(fn ($except) => trim($except)) ->filter() ->unique() diff --git a/src/foundation/src/Console/ReloadCommand.php b/src/foundation/src/Console/ReloadCommand.php index a0f28859b3..c2b5a31247 100644 --- a/src/foundation/src/Console/ReloadCommand.php +++ b/src/foundation/src/Console/ReloadCommand.php @@ -7,6 +7,7 @@ use Hypervel\Console\Command; use Hypervel\Support\Collection; use Hypervel\Support\ServiceProvider; +use Hypervel\Support\Stringable; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'reload')] @@ -29,7 +30,7 @@ public function handle(): void { $this->components->info('Reloading services.'); - $exceptions = Collection::wrap(explode(',', $this->option('except') ?? '')) + $exceptions = (new Stringable($this->option('except') ?? ''))->explode(',') ->map(fn ($except) => trim($except)) ->filter() ->unique() diff --git a/src/foundation/src/Console/RouteListCommand.php b/src/foundation/src/Console/RouteListCommand.php index bf41c72d5e..a79d22a2c3 100644 --- a/src/foundation/src/Console/RouteListCommand.php +++ b/src/foundation/src/Console/RouteListCommand.php @@ -65,6 +65,7 @@ class RouteListCommand extends Command 'GET' => 'blue', 'HEAD' => '#6C7280', 'OPTIONS' => '#6C7280', + 'QUERY' => '#6C7280', 'POST' => 'yellow', 'PUT' => 'yellow', 'PATCH' => 'yellow', @@ -357,7 +358,7 @@ protected function forCli(Collection $routes): array $routes = $routes->map( fn ($route) => array_merge($route, [ 'action' => $this->formatActionForCli($route), - 'method' => $route['method'] === 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS' ? 'ANY' : $route['method'], + 'method' => $route['method'] === implode('|', Router::$verbs) ? 'ANY' : $route['method'], 'uri' => $route['domain'] ? ($route['domain'] . '/' . ltrim($route['uri'], '/')) : $route['uri'], ]), ); @@ -449,7 +450,7 @@ protected function formatActionForCli(array $route): ?string : false; if (is_string($path) && str_starts_with($path, base_path('vendor'))) { - $actionCollection = new Collection(explode('\\', $action)); + $actionCollection = (new Stringable($action))->explode('\\'); return $name . $actionCollection->take(2)->implode('\\') . ' ' . $actionCollection->last(); } diff --git a/src/foundation/src/Http/HealthCheckController.php b/src/foundation/src/Http/HealthCheckController.php index 8f35a4820a..c1d1e4f966 100644 --- a/src/foundation/src/Http/HealthCheckController.php +++ b/src/foundation/src/Http/HealthCheckController.php @@ -34,6 +34,8 @@ public function __construct( /** * Run the application health check. + * + * @throws Throwable */ public function __invoke(Request $request): Response { diff --git a/src/foundation/src/Http/Middleware/PreventRequestForgery.php b/src/foundation/src/Http/Middleware/PreventRequestForgery.php index 0d7094da67..8172f438f6 100644 --- a/src/foundation/src/Http/Middleware/PreventRequestForgery.php +++ b/src/foundation/src/Http/Middleware/PreventRequestForgery.php @@ -94,7 +94,7 @@ public function handle(Request $request, Closure $next): Response */ protected function isReading(Request $request): bool { - return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS'], true); + return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS', 'QUERY'], true); } /** diff --git a/src/foundation/src/Testing/Concerns/InteractsWithDatabase.php b/src/foundation/src/Testing/Concerns/InteractsWithDatabase.php index 825ecc6e21..1b35aac1fa 100644 --- a/src/foundation/src/Testing/Concerns/InteractsWithDatabase.php +++ b/src/foundation/src/Testing/Concerns/InteractsWithDatabase.php @@ -26,10 +26,8 @@ trait InteractsWithDatabase * * @param class-string|iterable|Model|string $table * @param array $data - * @param null|string $connection - * @return $this */ - protected function assertDatabaseHas($table, array $data = [], $connection = null) + protected function assertDatabaseHas(iterable|Model|string $table, array $data = [], UnitEnum|string|null $connection = null): static { if (is_iterable($table)) { foreach ($table as $item) { @@ -67,10 +65,8 @@ protected function assertDatabaseHas($table, array $data = [], $connection = nul * * @param class-string|iterable|Model|string $table * @param array $data - * @param null|string $connection - * @return $this */ - protected function assertDatabaseMissing($table, array $data = [], $connection = null) + protected function assertDatabaseMissing(iterable|Model|string $table, array $data = [], UnitEnum|string|null $connection = null): static { if (is_iterable($table)) { foreach ($table as $item) { @@ -107,11 +103,9 @@ protected function assertDatabaseMissing($table, array $data = [], $connection = /** * Assert the count of table entries. * - * @param Model|string $table - * @param null|string $connection - * @return $this + * @param class-string|Model|string $table */ - protected function assertDatabaseCount($table, int $count, $connection = null) + protected function assertDatabaseCount(Model|string $table, int $count, UnitEnum|string|null $connection = null): static { $this->assertThat( $this->getTable($table), @@ -125,10 +119,8 @@ protected function assertDatabaseCount($table, int $count, $connection = null) * Assert that the given table or tables has no entries. * * @param class-string|iterable|Model|string>|Model|string $table - * @param null|string $connection - * @return $this */ - protected function assertDatabaseEmpty($table, $connection = null) + protected function assertDatabaseEmpty(iterable|Model|string $table, UnitEnum|string|null $connection = null): static { if (is_iterable($table)) { foreach ($table as $item) { @@ -151,11 +143,8 @@ protected function assertDatabaseEmpty($table, $connection = null) * * @param class-string|iterable|Model|string>|Model|string $table * @param array $data - * @param null|string $connection - * @param null|string $deletedAtColumn - * @return $this */ - protected function assertSoftDeleted($table, array $data = [], $connection = null, $deletedAtColumn = 'deleted_at') + protected function assertSoftDeleted(iterable|Model|string $table, array $data = [], UnitEnum|string|null $connection = null, ?string $deletedAtColumn = 'deleted_at'): static { if (is_iterable($table)) { foreach ($table as $item) { @@ -199,11 +188,8 @@ protected function assertSoftDeleted($table, array $data = [], $connection = nul * * @param class-string|iterable|Model|string>|Model|string $table * @param array $data - * @param null|string $connection - * @param null|string $deletedAtColumn - * @return $this */ - protected function assertNotSoftDeleted($table, array $data = [], $connection = null, $deletedAtColumn = 'deleted_at') + protected function assertNotSoftDeleted(iterable|Model|string $table, array $data = [], UnitEnum|string|null $connection = null, ?string $deletedAtColumn = 'deleted_at'): static { if (is_iterable($table)) { foreach ($table as $item) { @@ -246,9 +232,8 @@ protected function assertNotSoftDeleted($table, array $data = [], $connection = * Assert the given model exists in the database. * * @param class-string|iterable|Model|string $model - * @return $this */ - protected function assertModelExists($model) + protected function assertModelExists(iterable|Model|string $model): static { return $this->assertDatabaseHas($model); } @@ -257,21 +242,16 @@ protected function assertModelExists($model) * Assert the given model does not exist in the database. * * @param class-string|iterable|Model|string $model - * @return $this */ - protected function assertModelMissing($model) + protected function assertModelMissing(iterable|Model|string $model): static { return $this->assertDatabaseMissing($model); } /** * Specify the number of database queries that should occur throughout the test. - * - * @param int $expected - * @param null|string $connection - * @return $this */ - public function expectsDatabaseQueryCount($expected, $connection = null) + public function expectsDatabaseQueryCount(int $expected, UnitEnum|string|null $connection = null): static { with($this->getConnection($connection), function ($connectionInstance) use ($expected, $connection) { $actual = 0; @@ -297,12 +277,9 @@ public function expectsDatabaseQueryCount($expected, $connection = null) /** * Determine if the argument is a soft deletable model. * - * @param mixed $model - * @return bool - * * @phpstan-assert-if-true Model $model */ - protected function isSoftDeletableModel($model) + protected function isSoftDeletableModel(mixed $model): bool { return $model instanceof Model && $model::isSoftDeletable(); } @@ -330,11 +307,9 @@ public function castAsJson(array|object|string $value, UnitEnum|string|null $con /** * Get the database connection. * - * @param null|string $connection * @param null|class-string|Model|string $table - * @return Connection */ - protected function getConnection($connection = null, $table = null) + protected function getConnection(UnitEnum|string|null $connection = null, Model|string|null $table = null): Connection { $database = $this->app->make('db'); @@ -353,9 +328,8 @@ protected function getConnection($connection = null, $table = null) * Get the table name from the given model or string. * * @param class-string|Model|string $table - * @return string */ - protected function getTable($table) + protected function getTable(Model|string $table): string { if ($table instanceof Model) { return $table->getTable(); @@ -367,10 +341,9 @@ protected function getTable($table) /** * Get the table connection specified in the given model. * - * @param class-string|Model|string $table - * @return null|string + * @param null|class-string|Model|string $table */ - protected function getTableConnection($table) + protected function getTableConnection(Model|string|null $table): ?string { if ($table instanceof Model) { return $table->getConnectionName(); @@ -382,11 +355,9 @@ protected function getTableConnection($table) /** * Get the table column name used for soft deletes. * - * @param string $table - * @param string $defaultColumnName - * @return string + * @param class-string|Model|string $table */ - protected function getDeletedAtColumn($table, $defaultColumnName = 'deleted_at') + protected function getDeletedAtColumn(Model|string $table, ?string $defaultColumnName = 'deleted_at'): ?string { return $this->newModelFor($table)?->getDeletedAtColumn() ?: $defaultColumnName; } @@ -394,10 +365,9 @@ protected function getDeletedAtColumn($table, $defaultColumnName = 'deleted_at') /** * Get the model entity from the given model or string. * - * @param Model|string $table - * @return null|Model + * @param null|class-string|Model|string $table */ - protected function newModelFor($table) + protected function newModelFor(Model|string|null $table): ?Model { return is_subclass_of($table, Model::class) ? (new $table) : null; } diff --git a/src/http/src/Middleware/SetCacheHeaders.php b/src/http/src/Middleware/SetCacheHeaders.php index f0e5c5f876..30c549c357 100644 --- a/src/http/src/Middleware/SetCacheHeaders.php +++ b/src/http/src/Middleware/SetCacheHeaders.php @@ -83,6 +83,7 @@ public function handle(Request $request, Closure $next, array|string $options = */ protected function parseOptions(string $options): array { + // Keep this explode() to avoid an extra Stringable allocation per call. return (new Collection(explode(';', rtrim($options, ';'))))->mapWithKeys(function ($option) { $data = explode('=', $option, 2); diff --git a/src/http/src/Middleware/ValidatePathEncoding.php b/src/http/src/Middleware/ValidatePathEncoding.php index 367644291c..6fb2690ef9 100644 --- a/src/http/src/Middleware/ValidatePathEncoding.php +++ b/src/http/src/Middleware/ValidatePathEncoding.php @@ -13,6 +13,8 @@ class ValidatePathEncoding { /** * Validate that the incoming request has a valid UTF-8 encoded path. + * + * @throws MalformedUrlException */ public function handle(Request $request, Closure $next): Response { diff --git a/src/mail/resources/views/html/button.blade.php b/src/mail/resources/views/html/button.blade.php index 4a9bf7d004..050e969d21 100644 --- a/src/mail/resources/views/html/button.blade.php +++ b/src/mail/resources/views/html/button.blade.php @@ -12,7 +12,7 @@
-{{ $slot }} +{!! $slot !!}
diff --git a/src/mail/resources/views/html/header.blade.php b/src/mail/resources/views/html/header.blade.php index 459960b702..2178c39731 100644 --- a/src/mail/resources/views/html/header.blade.php +++ b/src/mail/resources/views/html/header.blade.php @@ -5,7 +5,7 @@ @if (trim($slot) === 'Hypervel') @else -{{ $slot }} +{!! $slot !!} @endif diff --git a/src/mail/resources/views/html/layout.blade.php b/src/mail/resources/views/html/layout.blade.php index dde46c6fc1..04b4825839 100644 --- a/src/mail/resources/views/html/layout.blade.php +++ b/src/mail/resources/views/html/layout.blade.php @@ -31,7 +31,7 @@ -{{ $header ?? '' }} +{!! $header ?? '' !!} @@ -40,16 +40,16 @@ -{{ $footer ?? '' }} +{!! $footer ?? '' !!} diff --git a/src/mail/resources/views/html/message.blade.php b/src/mail/resources/views/html/message.blade.php index d2dc7bebf4..42f06910f8 100644 --- a/src/mail/resources/views/html/message.blade.php +++ b/src/mail/resources/views/html/message.blade.php @@ -7,13 +7,13 @@ {{-- Body --}} -{{ $slot }} +{!! $slot !!} {{-- Subcopy --}} @isset($subcopy) -{{ $subcopy }} +{!! $subcopy !!} @endisset diff --git a/src/process/src/PendingProcess.php b/src/process/src/PendingProcess.php index ee07447868..57bafb65bf 100644 --- a/src/process/src/PendingProcess.php +++ b/src/process/src/PendingProcess.php @@ -326,6 +326,7 @@ protected function fakeFor(string $command): ?callable * Resolve the given fake handler for a synchronous process. * * @throws LogicException + * @throws Throwable */ protected function resolveSynchronousFake(string $command, Closure $fake): ProcessResultContract { diff --git a/src/queue/src/Console/MonitorCommand.php b/src/queue/src/Console/MonitorCommand.php index baef10b4c5..02acea51f9 100644 --- a/src/queue/src/Console/MonitorCommand.php +++ b/src/queue/src/Console/MonitorCommand.php @@ -11,6 +11,7 @@ use Hypervel\Queue\Events\QueueBusy; use Hypervel\Support\CarbonImmutable; use Hypervel\Support\Collection; +use Hypervel\Support\Stringable; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'queue:monitor')] @@ -74,7 +75,7 @@ public function handle(): void */ protected function parseQueues(string $queues): Collection { - return (new Collection(explode(',', $queues)))->map(function (string $queue): array { + return (new Stringable($queues))->explode(',')->map(function (string $queue): array { [$connection, $queue] = array_pad(explode(':', $queue, 2), 2, null); if (! isset($queue)) { diff --git a/src/queue/src/InteractsWithQueue.php b/src/queue/src/InteractsWithQueue.php index f6f6063451..61ad79334a 100644 --- a/src/queue/src/InteractsWithQueue.php +++ b/src/queue/src/InteractsWithQueue.php @@ -222,6 +222,8 @@ public function assertNotReleased(): static /** * Ensure that queue interactions have been faked. + * + * @throws RuntimeException */ private function ensureQueueInteractionsHaveBeenFaked(): void { diff --git a/src/queue/src/Middleware/ThrottlesExceptions.php b/src/queue/src/Middleware/ThrottlesExceptions.php index 580a4ece82..20ec40086b 100644 --- a/src/queue/src/Middleware/ThrottlesExceptions.php +++ b/src/queue/src/Middleware/ThrottlesExceptions.php @@ -83,6 +83,8 @@ public function __construct( /** * Process the job. + * + * @throws Throwable */ public function handle(mixed $job, callable $next): mixed { diff --git a/src/queue/src/Queue.php b/src/queue/src/Queue.php index 87fd51e6de..8455291ca8 100644 --- a/src/queue/src/Queue.php +++ b/src/queue/src/Queue.php @@ -178,6 +178,8 @@ protected function createPayloadArray(array|object|string $job, ?string $queue, /** * Create a payload for an object-based queue handler. + * + * @throws RuntimeException */ protected function createObjectPayload(object $job, ?string $queue): array { diff --git a/src/queue/src/QueueManager.php b/src/queue/src/QueueManager.php index a492215f03..0638a7e85c 100644 --- a/src/queue/src/QueueManager.php +++ b/src/queue/src/QueueManager.php @@ -12,7 +12,7 @@ use Hypervel\Contracts\ObjectPool\Factory as PoolFactory; use Hypervel\Contracts\Queue\Factory as FactoryContract; use Hypervel\Contracts\Queue\Monitor as MonitorContract; -use Hypervel\Contracts\Queue\Queue; +use Hypervel\Contracts\Queue\Queue as QueueContract; use Hypervel\ObjectPool\Concerns\HasPoolProxy; use Hypervel\Queue\Connectors\ConnectorInterface; use Hypervel\Queue\Events\JobExceptionOccurred; @@ -34,7 +34,7 @@ use function Hypervel\Support\enum_value; /** - * @mixin Queue + * @mixin QueueContract */ class QueueManager implements FactoryContract, MonitorContract { @@ -357,7 +357,7 @@ public function connected(UnitEnum|string|null $name = null): bool /** * Resolve a queue connection instance. */ - public function connection(UnitEnum|string|null $name = null): Queue + public function connection(UnitEnum|string|null $name = null): QueueContract { if ($name instanceof UnitEnum) { $name = (string) enum_value($name); @@ -382,7 +382,7 @@ public function connection(UnitEnum|string|null $name = null): Queue * * @throws InvalidArgumentException */ - protected function resolve(string $name): Queue + protected function resolve(string $name): QueueContract { $config = $this->getConfig($name); @@ -570,6 +570,18 @@ public function setApplication(Container $app): static return $this; } + /** + * Register a callback to be executed when creating job payloads. + * + * Boot-only. The callback persists in a static property for the worker + * lifetime and runs on every subsequent payload creation across all + * coroutines. Passing null clears the registry. + */ + public function createPayloadUsing(?callable $callback): void + { + Queue::createPayloadUsing($callback); + } + /** * Dynamically pass calls to the default connection. */ diff --git a/src/redis/src/RedisProxy.php b/src/redis/src/RedisProxy.php index ea14eb0cfa..555c53c751 100644 --- a/src/redis/src/RedisProxy.php +++ b/src/redis/src/RedisProxy.php @@ -802,6 +802,8 @@ public function psubscribe(array|string $channels, Closure $callback): void /** * Run a command against the Redis database. + * + * @throws Throwable */ public function command(string $method, array $parameters = []): mixed { diff --git a/src/routing/src/Middleware/SubstituteBindings.php b/src/routing/src/Middleware/SubstituteBindings.php index 7c813d3f62..cee08ee1ad 100644 --- a/src/routing/src/Middleware/SubstituteBindings.php +++ b/src/routing/src/Middleware/SubstituteBindings.php @@ -27,6 +27,8 @@ public function __construct(Registrar $router) /** * Handle an incoming request. + * + * @throws ModelNotFoundException */ public function handle(Request $request, Closure $next): Response { diff --git a/src/routing/src/RouteRegistrar.php b/src/routing/src/RouteRegistrar.php index 09f002d4c9..82db600b6a 100644 --- a/src/routing/src/RouteRegistrar.php +++ b/src/routing/src/RouteRegistrar.php @@ -21,6 +21,7 @@ * @method Route patch(string $uri, callable|array|string|null $action = null) * @method Route post(string $uri, callable|array|string|null $action = null) * @method Route put(string $uri, callable|array|string|null $action = null) + * @method Route query(string $uri, callable|array|string|null $action = null) * @method $this as(string $value) * @method $this can(UnitEnum|string $ability, array|string $models = []) * @method $this controller(string $controller) @@ -60,7 +61,7 @@ class RouteRegistrar * @var string[] */ protected array $passthru = [ - 'get', 'post', 'put', 'patch', 'delete', 'options', 'any', + 'get', 'post', 'put', 'patch', 'delete', 'options', 'query', 'any', ]; /** diff --git a/src/routing/src/Router.php b/src/routing/src/Router.php index 99522031c7..48e0362529 100644 --- a/src/routing/src/Router.php +++ b/src/routing/src/Router.php @@ -120,7 +120,7 @@ class Router implements BindingRegistrar, RegistrarContract * * @var array */ - public static array $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; + public static array $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'QUERY']; /** * Create a new Router instance. @@ -180,6 +180,14 @@ public function options(string $uri, array|string|callable|null $action = null): return $this->addRoute('OPTIONS', $uri, $action); } + /** + * Register a new QUERY route with the router. + */ + public function query(string $uri, array|string|callable|null $action = null): Route + { + return $this->addRoute('QUERY', $uri, $action); + } + /** * Register a new route responding to all verbs. */ diff --git a/src/routing/src/RoutingServiceProvider.php b/src/routing/src/RoutingServiceProvider.php index a3a0a074b3..5b98b8562d 100644 --- a/src/routing/src/RoutingServiceProvider.php +++ b/src/routing/src/RoutingServiceProvider.php @@ -122,6 +122,8 @@ protected function registerRedirector(): void /** * Register a binding for the PSR-7 request implementation. + * + * @throws BindingResolutionException */ protected function registerPsrRequest(): void { @@ -145,6 +147,8 @@ protected function registerPsrRequest(): void /** * Register a binding for the PSR-7 response implementation. + * + * @throws BindingResolutionException */ protected function registerPsrResponse(): void { diff --git a/src/routing/src/UrlGenerator.php b/src/routing/src/UrlGenerator.php index 220cf814cc..6b3555919e 100755 --- a/src/routing/src/UrlGenerator.php +++ b/src/routing/src/UrlGenerator.php @@ -407,6 +407,7 @@ public function hasCorrectSignature(Request $request, bool $absolute = true, Clo $url = $absolute ? $request->url() : '/' . $request->path(); // REMOVED: Vapor's VAPOR_RAW_QUERY_STRING override; Swoole supplies the raw QUERY_STRING. + // Keep this explode() to avoid an extra Stringable allocation per call. $queryString = (new Collection(explode('&', (string) $request->server->get('QUERY_STRING')))) ->reject(function ($parameter) use ($ignoreQuery) { $parameter = Str::before($parameter, '='); diff --git a/src/socialite/src/Socialite.php b/src/socialite/src/Socialite.php index 03763c9fd4..98e52c0fac 100644 --- a/src/socialite/src/Socialite.php +++ b/src/socialite/src/Socialite.php @@ -17,7 +17,7 @@ * @method static \Hypervel\Socialite\SocialiteManager forgetDrivers() * @method static \Hypervel\Contracts\Container\Container getContainer() * @method static string getDefaultDriver() - * @method static array getDrivers() + * @method static array getDrivers() * @method static \Hypervel\Socialite\SocialiteManager setContainer(\Hypervel\Contracts\Container\Container $container) * @method static \Hypervel\Socialite\Contracts\Provider with(string $driver) * diff --git a/src/support/src/Benchmark.php b/src/support/src/Benchmark.php index 05a10a16fa..65f1741451 100644 --- a/src/support/src/Benchmark.php +++ b/src/support/src/Benchmark.php @@ -13,6 +13,9 @@ class Benchmark /** * Measure a callable or array of callables over the given number of iterations. + * + * @param array|Closure $benchmarkables + * @return array|float */ public static function measure(Closure|array $benchmarkables, int $iterations = 1): array|float { @@ -39,7 +42,7 @@ public static function measure(Closure|array $benchmarkables, int $iterations = * @template TReturn of mixed * * @param (callable(): TReturn) $callback - * @return array{0: TReturn, 1: float} + * @return array{0: TReturn, 1: float|int} */ public static function value(callable $callback): array { @@ -54,6 +57,8 @@ public static function value(callable $callback): array /** * Measure a callable or array of callables over the given number of iterations, then dump and die. + * + * @param array|Closure $benchmarkables */ public static function dd(Closure|array $benchmarkables, int $iterations = 1): never { diff --git a/src/support/src/Composer.php b/src/support/src/Composer.php index 152d3d1a48..a4c3bf16ef 100644 --- a/src/support/src/Composer.php +++ b/src/support/src/Composer.php @@ -9,6 +9,7 @@ use Hypervel\Filesystem\Filesystem; use JsonException; use RuntimeException; +use Stringable; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; @@ -135,6 +136,8 @@ public function modify(callable $callback): void /** * Regenerate the Composer autoloader files. + * + * @param array|string $extra */ public function dumpAutoloads(string|array $extra = '', ?string $composerBinary = null): int { @@ -155,6 +158,8 @@ public function dumpOptimized(?string $composerBinary = null): int /** * Get the Composer binary / command for the environment. + * + * @return array */ public function findComposer(?string $composerBinary = null): array { @@ -210,6 +215,9 @@ protected function phpBinary(): string /** * Get a new Symfony process instance. + * + * @param array $command + * @param array $env */ protected function getProcess(array $command, array $env = []): Process { diff --git a/src/support/src/ConfigurationUrlParser.php b/src/support/src/ConfigurationUrlParser.php index b02cee62ac..5ec5cd35a7 100644 --- a/src/support/src/ConfigurationUrlParser.php +++ b/src/support/src/ConfigurationUrlParser.php @@ -23,11 +23,16 @@ class ConfigurationUrlParser /** * The drivers aliases map. + * + * @var array */ protected static array $driverAliases = self::DEFAULT_DRIVER_ALIASES; /** * Parse the database configuration, hydrating options using a database configuration URL if possible. + * + * @param array|string $config + * @return array */ public function parseConfiguration(array|string $config): array { @@ -62,6 +67,9 @@ public function parseConfiguration(array|string $config): array /** * Get the primary database connection options. + * + * @param array $url + * @return array */ protected function getPrimaryOptions(array $url): array { @@ -77,6 +85,8 @@ protected function getPrimaryOptions(array $url): array /** * Get the database driver from the URL. + * + * @param array $url */ protected function getDriver(array $url): ?string { @@ -91,6 +101,8 @@ protected function getDriver(array $url): ?string /** * Get the database name from the URL. + * + * @param array $url */ protected function getDatabase(array $url): ?string { @@ -101,6 +113,9 @@ protected function getDatabase(array $url): ?string /** * Get all of the additional database options from the query string. + * + * @param array $url + * @return array */ protected function getQueryOptions(array $url): array { @@ -120,6 +135,8 @@ protected function getQueryOptions(array $url): array /** * Parse the string URL to an array of components. * + * @return array + * * @throws InvalidArgumentException */ protected function parseUrl(string $url): array @@ -159,6 +176,8 @@ protected function parseStringsToNativeTypes(mixed $value): mixed /** * Get all of the current drivers' aliases. + * + * @return array */ public static function getDriverAliases(): array { diff --git a/src/support/src/Facades/Hash.php b/src/support/src/Facades/Hash.php index dc30ce1132..4cdde9a51e 100644 --- a/src/support/src/Facades/Hash.php +++ b/src/support/src/Facades/Hash.php @@ -17,7 +17,7 @@ * @method static \Hypervel\Hashing\HashManager forgetDrivers() * @method static \Hypervel\Contracts\Container\Container getContainer() * @method static string getDefaultDriver() - * @method static array getDrivers() + * @method static array getDrivers() * @method static array info(string $hashedValue) * @method static bool isHashed(string $value) * @method static string make(string $value, array $options = []) diff --git a/src/support/src/Facades/Image.php b/src/support/src/Facades/Image.php index 48a7053f0f..506851600e 100644 --- a/src/support/src/Facades/Image.php +++ b/src/support/src/Facades/Image.php @@ -17,7 +17,7 @@ * @method static \Hypervel\Image\Image fromUrl(string $url) * @method static \Hypervel\Contracts\Container\Container getContainer() * @method static string getDefaultDriver() - * @method static array getDrivers() + * @method static array getDrivers() * @method static \Hypervel\Image\ImageManager setContainer(\Hypervel\Contracts\Container\Container $container) * @method static \Hypervel\Image\ImageManager transformUsing(string $driver, string $transformation, callable $callback) * diff --git a/src/support/src/Facades/Jwt.php b/src/support/src/Facades/Jwt.php index 21bc6d82a7..32c662990f 100644 --- a/src/support/src/Facades/Jwt.php +++ b/src/support/src/Facades/Jwt.php @@ -13,7 +13,7 @@ * @method static \Hypervel\Jwt\JwtManager forgetDrivers() * @method static \Hypervel\Contracts\Container\Container getContainer() * @method static string getDefaultDriver() - * @method static array getDrivers() + * @method static array getDrivers() * @method static bool hasBlacklistEnabled() * @method static bool invalidate(string $token, bool $forceForever = false) * @method static string refresh(string $token, bool $forceForever = false, bool $resetClaims = false, array $customClaims = [], int|false|null $ttl = false) diff --git a/src/support/src/Facades/MaintenanceMode.php b/src/support/src/Facades/MaintenanceMode.php index 0aaea879d6..b488c18ec8 100644 --- a/src/support/src/Facades/MaintenanceMode.php +++ b/src/support/src/Facades/MaintenanceMode.php @@ -12,7 +12,7 @@ * @method static \Hypervel\Foundation\MaintenanceModeManager forgetDrivers() * @method static \Hypervel\Contracts\Container\Container getContainer() * @method static string getDefaultDriver() - * @method static array getDrivers() + * @method static array getDrivers() * @method static \Hypervel\Foundation\MaintenanceModeManager setContainer(\Hypervel\Contracts\Container\Container $container) * * @see \Hypervel\Foundation\MaintenanceModeManager diff --git a/src/support/src/Facades/Notification.php b/src/support/src/Facades/Notification.php index d1b071a330..284ae5b2e4 100644 --- a/src/support/src/Facades/Notification.php +++ b/src/support/src/Facades/Notification.php @@ -19,7 +19,7 @@ * @method static \Hypervel\Notifications\ChannelManager forgetDrivers() * @method static \Hypervel\Contracts\Container\Container getContainer() * @method static string getDefaultDriver() - * @method static array getDrivers() + * @method static array getDrivers() * @method static string|null getLocale() * @method static bool hasMacro(string $name) * @method static \Hypervel\Notifications\ChannelManager locale(string $locale) diff --git a/src/support/src/Facades/Queue.php b/src/support/src/Facades/Queue.php index e25067fc02..8b1fa5c035 100644 --- a/src/support/src/Facades/Queue.php +++ b/src/support/src/Facades/Queue.php @@ -15,6 +15,7 @@ * @method static void before(mixed $callback) * @method static bool connected(\UnitEnum|string|null $name = null) * @method static \Hypervel\Contracts\Queue\Queue connection(\UnitEnum|string|null $name = null) + * @method static void createPayloadUsing(callable|null $callback) * @method static void exceptionOccurred(mixed $callback) * @method static void extend(string $driver, \Closure $resolver) * @method static void failing(mixed $callback) @@ -58,7 +59,6 @@ * @method static int reservedSize(\UnitEnum|string|null $queue = null) * @method static \Hypervel\Contracts\Queue\Queue setConnectionName(string $name) * @method static int size(\UnitEnum|string|null $queue = null) - * @method static void createPayloadUsing(callable|null $callback) * @method static void flushState() * @method static array getConfig() * @method static \Hypervel\Contracts\Container\Container getContainer() diff --git a/src/support/src/Facades/Route.php b/src/support/src/Facades/Route.php index f7d3571a3e..8a2bc2609f 100644 --- a/src/support/src/Facades/Route.php +++ b/src/support/src/Facades/Route.php @@ -64,6 +64,7 @@ * @method static \Hypervel\Routing\Router prependMiddlewareToGroup(string $group, string $middleware) * @method static \Hypervel\Routing\Router pushMiddlewareToGroup(string $group, string $middleware) * @method static \Hypervel\Routing\Route put(string $uri, callable|array|string|null $action = null) + * @method static \Hypervel\Routing\Route query(string $uri, callable|array|string|null $action = null) * @method static \Hypervel\Routing\Route redirect(string $uri, string $destination, int $status = 302) * @method static \Hypervel\Routing\Router removeMiddlewareFromGroup(string $group, array|string $middleware) * @method static array resolveMiddleware(array $middleware, array $excluded = []) diff --git a/src/support/src/Facades/Session.php b/src/support/src/Facades/Session.php index 3362d3d345..70f5ba80d5 100644 --- a/src/support/src/Facades/Session.php +++ b/src/support/src/Facades/Session.php @@ -14,7 +14,7 @@ * @method static \Hypervel\Session\UserSessions forUser(\Hypervel\Contracts\Auth\Authenticatable|string|int $user, \UnitEnum|string|null $guard = null) * @method static \Hypervel\Contracts\Container\Container getContainer() * @method static string getDefaultDriver() - * @method static array getDrivers() + * @method static array getDrivers() * @method static array getSessionConfig() * @method static \Hypervel\Session\SessionManager setContainer(\Hypervel\Contracts\Container\Container $container) * @method static void setDefaultDriver(\UnitEnum|string $name) diff --git a/src/support/src/Fluent.php b/src/support/src/Fluent.php index 16e1c94678..f0dc2e33d3 100644 --- a/src/support/src/Fluent.php +++ b/src/support/src/Fluent.php @@ -119,6 +119,8 @@ public function scope(string $key, mixed $default = null): static /** * Get all of the attributes from the fluent instance. + * + * @return ($keys is null ? array : array) */ public function all(mixed $keys = null): array { diff --git a/src/support/src/Manager.php b/src/support/src/Manager.php index c373a006b5..db3a5cc777 100644 --- a/src/support/src/Manager.php +++ b/src/support/src/Manager.php @@ -23,11 +23,15 @@ abstract class Manager /** * The registered custom driver creators. + * + * @var array */ protected array $customCreators = []; /** * The array of created "drivers". + * + * @var array */ protected array $drivers = []; @@ -135,6 +139,8 @@ public function extend(string $driver, Closure $callback): static /** * Get all of the created "drivers". + * + * @return array */ public function getDrivers(): array { diff --git a/src/support/src/MessageBag.php b/src/support/src/MessageBag.php index dffbbfab27..6949083dac 100755 --- a/src/support/src/MessageBag.php +++ b/src/support/src/MessageBag.php @@ -103,6 +103,8 @@ public function merge(MessageProvider|array $messages): static /** * Determine if messages exist for all of the given keys. + * + * @param null|array|string $key */ public function has(array|string|null $key = null): bool { @@ -128,6 +130,8 @@ public function has(array|string|null $key = null): bool /** * Determine if messages exist for any of the given keys. + * + * @param null|array|string $keys */ public function hasAny(array|string|null $keys = []): bool { @@ -149,6 +153,8 @@ public function hasAny(array|string|null $keys = []): bool /** * Determine if messages don't exist for all of the given keys. + * + * @param null|array|string $key */ public function missing(array|string|null $key = null): bool { diff --git a/src/support/src/Testing/Fakes/BusFake.php b/src/support/src/Testing/Fakes/BusFake.php index 4092ddc8a8..cadcd75bcb 100644 --- a/src/support/src/Testing/Fakes/BusFake.php +++ b/src/support/src/Testing/Fakes/BusFake.php @@ -369,6 +369,8 @@ public function assertDispatchedWithoutChain(Closure|string $command, ?callable /** * Assert if a job was dispatched with chained jobs based on a truth-test callback. + * + * @throws RuntimeException */ protected function assertDispatchedWithChainOfObjects(string $command, array $expectedChain, ?callable $callback): void { diff --git a/src/support/src/Testing/Fakes/ExceptionHandlerFake.php b/src/support/src/Testing/Fakes/ExceptionHandlerFake.php index 54e8065690..c5598f0dc7 100644 --- a/src/support/src/Testing/Fakes/ExceptionHandlerFake.php +++ b/src/support/src/Testing/Fakes/ExceptionHandlerFake.php @@ -105,6 +105,8 @@ public function assertReportedCount(int $count): void * Assert if an exception of the given type has not been reported. * * @param class-string|(Closure(Throwable): bool) $exception + * + * @throws ExpectationFailedException */ public function assertNotReported(Closure|string $exception): void { @@ -136,6 +138,8 @@ public function assertNothingReported(): void /** * Report or log an exception. + * + * @throws Throwable */ public function report(Throwable $e): void { @@ -250,8 +254,6 @@ public function setHandler(ExceptionHandler $handler): static /** * Handle dynamic method calls to the handler. - * - * @param array $parameters */ public function __call(string $method, array $parameters): mixed { diff --git a/src/validation/src/Concerns/ValidatesAttributes.php b/src/validation/src/Concerns/ValidatesAttributes.php index c30d6e8fa4..38ce9ce5e4 100644 --- a/src/validation/src/Concerns/ValidatesAttributes.php +++ b/src/validation/src/Concerns/ValidatesAttributes.php @@ -1857,6 +1857,8 @@ public function validateMissingWithAll(string $attribute, mixed $value, mixed $p * Validate the value of an attribute is a multiple of a given value. * * @param array $parameters + * + * @throws MathException */ public function validateMultipleOf(string $attribute, mixed $value, mixed $parameters): bool { @@ -1868,11 +1870,7 @@ public function validateMultipleOf(string $attribute, mixed $value, mixed $param /** * Determine if a value is an exact multiple of a divisor. * - * Uses BigDecimal remainder for arbitrary-precision comparison. Both - * value and divisor must be numeric; the exception translation wrapping - * stays in the caller (validateMultipleOf) for non-numeric cases, but - * the BigDecimal exception is thrown from here since that's where the - * math operation lives. + * @throws MathException */ protected function isMultipleOf(mixed $value, mixed $divisor): bool { diff --git a/tests/Filesystem/FileResponseBuilderTest.php b/tests/Filesystem/FileResponseBuilderTest.php index b337ac14cd..a723d8cb90 100644 --- a/tests/Filesystem/FileResponseBuilderTest.php +++ b/tests/Filesystem/FileResponseBuilderTest.php @@ -87,6 +87,29 @@ function (?int $start, ?int $end) use (&$resolverCalls): mixed { $this->assertSame('bytes', $response->headers->get('Accept-Ranges')); } + #[DataProvider('acceptRangesProvider')] + public function testAcceptRangesFollowsWhetherTheRequestMethodIsSafe(string $method, string $acceptRanges): void + { + $response = $this->build( + Request::create('/file.txt', $method), + fn (?int $start, ?int $end): mixed => $this->stream('body'), + 4, + ); + + $this->assertSame($acceptRanges, $response->headers->get('Accept-Ranges')); + } + + /** + * Get request methods with their expected Accept-Ranges header. + */ + public static function acceptRangesProvider(): array + { + return [ + 'safe query' => ['QUERY', 'bytes'], + 'unsafe post' => ['POST', 'none'], + ]; + } + public function testBodyContainingOnlyZeroIsWritten(): void { $response = $this->build( diff --git a/tests/Foundation/FoundationInteractsWithDatabaseTest.php b/tests/Foundation/FoundationInteractsWithDatabaseTest.php index aff36ddc81..9e0709cb5b 100644 --- a/tests/Foundation/FoundationInteractsWithDatabaseTest.php +++ b/tests/Foundation/FoundationInteractsWithDatabaseTest.php @@ -335,11 +335,12 @@ public function testAssertSoftDeletedInDatabaseFindsResults() $this->assertSoftDeleted($this->table, $this->data); } - public function testAssertSoftDeletedSupportModelStrings() + public function testAssertSoftDeletedSupportModelStrings(): void { $this->mockCountBuilder(true); $this->assertSoftDeleted(ProductStub::class, $this->data); + $this->assertSoftDeleted(ProductStub::class, $this->data, deletedAtColumn: null); } public function testAssertSoftDeletedInDatabaseDoesNotFindResults(): void @@ -401,11 +402,12 @@ public function testAssertNotSoftDeletedInDatabaseFindsResults() $this->assertNotSoftDeleted($this->table, $this->data); } - public function testAssertNotSoftDeletedSupportModelStrings() + public function testAssertNotSoftDeletedSupportModelStrings(): void { $this->mockCountBuilder(true); $this->assertNotSoftDeleted(ProductStub::class, $this->data); + $this->assertNotSoftDeleted(ProductStub::class, $this->data, deletedAtColumn: null); } public function testAssertNotSoftDeletedOnlyFindsMatchingModels(): void diff --git a/tests/Http/Middleware/PreventRequestForgeryTest.php b/tests/Http/Middleware/PreventRequestForgeryTest.php index 2fc1238b54..90f8ce0cdc 100644 --- a/tests/Http/Middleware/PreventRequestForgeryTest.php +++ b/tests/Http/Middleware/PreventRequestForgeryTest.php @@ -27,6 +27,16 @@ public function testSameOriginHeaderPasses(): void $this->assertSame('OK', $response->getContent()); } + public function testQueryRequestsPassWithoutToken(): void + { + $middleware = $this->createMiddleware(); + $request = $this->createRequest(method: 'QUERY'); + + $response = $middleware->handle($request, fn (): Response => new Response('OK')); + + $this->assertSame('OK', $response->getContent()); + } + public function testSameSiteHeaderRejectedByDefault(): void { $middleware = $this->createMiddleware(); @@ -131,11 +141,11 @@ public function testOriginOnlyModePassesSameOrigin(): void /** * Create a request with the given headers and token. */ - protected function createRequest(array $server = [], ?string $token = null): Request + protected function createRequest(array $server = [], ?string $token = null, string $method = 'POST'): Request { $request = Request::create( 'http://example.com/test', - 'POST', + $method, $token ? ['_token' => $token] : [], [], [], diff --git a/tests/Integration/Database/EloquentPrunableTest.php b/tests/Integration/Database/EloquentPrunableTest.php index 5b02a604f5..1b5d4045f1 100644 --- a/tests/Integration/Database/EloquentPrunableTest.php +++ b/tests/Integration/Database/EloquentPrunableTest.php @@ -6,6 +6,7 @@ use Exception; use Hypervel\Contracts\Events\Dispatcher; +use Hypervel\Database\Eloquent\Builder; use Hypervel\Database\Eloquent\Model; use Hypervel\Database\Eloquent\Prunable; use Hypervel\Database\Eloquent\SoftDeletes; @@ -15,6 +16,7 @@ use Hypervel\Support\Facades\Exceptions; use Hypervel\Support\Facades\Schema; use LogicException; +use Swoole\Coroutine\CanceledException; /** * The fixtures are intentionally smaller than Laravel's because this suite @@ -31,6 +33,7 @@ protected function afterRefreshingDatabase(): void 'prunable_test_model_missing_prunable_methods', 'prunable_with_custom_prune_method_test_models', 'prunable_with_exceptions', + 'prunable_with_cancellations', ])->each(function ($table) { Schema::create($table, function (Blueprint $table) { $table->increments('id'); @@ -49,7 +52,7 @@ public function testPrunableMethodMustBeImplemented(): void PrunableTestModelMissingPrunableMethod::create()->pruneAll(); } - public function testPrunesRecords() + public function testPrunesRecords(): void { Event::fake(); @@ -84,7 +87,7 @@ static function (ModelsPruned $event) use (&$observedEvents): void { $this->assertSame([], $observedEvents); } - public function testPrunesSoftDeletedRecords() + public function testPrunesSoftDeletedRecords(): void { Event::fake(); @@ -103,7 +106,7 @@ public function testPrunesSoftDeletedRecords() Event::assertDispatched(ModelsPruned::class, 3); } - public function testPruneWithCustomPruneMethod() + public function testPruneWithCustomPruneMethod(): void { Event::fake(); @@ -124,7 +127,7 @@ public function testPruneWithCustomPruneMethod() Event::assertDispatched(ModelsPruned::class, 1); } - public function testPruneWithExceptionAtOneOfModels() + public function testPruneWithExceptionAtOneOfModels(): void { Event::fake(); Exceptions::fake(); @@ -144,6 +147,23 @@ public function testPruneWithExceptionAtOneOfModels() Exceptions::assertReportedCount(1); Exceptions::assertReported(fn (Exception $exception) => $exception->getMessage() === 'foo bar'); } + + public function testPruneRethrowsCancellationWithoutReportingIt(): void + { + Exceptions::fake(); + + PrunableWithCancellation::insert(array_fill(0, 10, ['name' => 'foo'])); + + try { + (new PrunableWithCancellation)->pruneAll(); + + $this->fail('The cancellation was not rethrown.'); + } catch (CanceledException $exception) { + $this->assertSame('canceled', $exception->getMessage()); + } + + Exceptions::assertNothingReported(); + } } class PrunableTestModel extends Model @@ -201,6 +221,31 @@ public function prune() } } +class PrunableWithCancellation extends Model +{ + use Prunable; + + /** + * Get the prunable model query. + */ + public function prunable(): Builder + { + return $this->where('id', '<=', 10); + } + + /** + * Prune the model in the database. + */ + public function prune(): int|bool|null + { + if ($this->id === 5) { + throw new CanceledException('canceled'); + } + + return true; + } +} + class PrunableTestModelMissingPrunableMethod extends Model { use Prunable; diff --git a/tests/Integration/Mail/Fixtures/table-with-template.blade.php b/tests/Integration/Mail/Fixtures/table-with-template.blade.php index ecdac4dc37..e70665704c 100644 --- a/tests/Integration/Mail/Fixtures/table-with-template.blade.php +++ b/tests/Integration/Mail/Fixtures/table-with-template.blade.php @@ -1,4 +1,4 @@ - + *Hi* {{ $user->name }} diff --git a/tests/Integration/Mail/MailableWithSecuredEncodingTest.php b/tests/Integration/Mail/MailableWithSecuredEncodingTest.php index 3fb163c1d0..32fb6facfe 100644 --- a/tests/Integration/Mail/MailableWithSecuredEncodingTest.php +++ b/tests/Integration/Mail/MailableWithSecuredEncodingTest.php @@ -70,7 +70,7 @@ public function build(): static }; $mailable->assertSeeInHtml($expected, false); - $mailable->assertSeeInHtml('

This is a subcopy

', false); + $mailable->assertSeeInHtml('

This is a subcopy

', false); $mailable->assertSeeInHtml(<<<'TABLE' diff --git a/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php b/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php index 87332e0d50..22e90bbe9b 100644 --- a/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php +++ b/tests/Integration/Mail/MailableWithoutSecuredEncodingTest.php @@ -70,7 +70,7 @@ public function build(): static }; $mailable->assertSeeInHtml($expected, false); - $mailable->assertSeeInHtml('

This is a subcopy

', false); + $mailable->assertSeeInHtml('

This is a subcopy

', false); $mailable->assertSeeInHtml(<<<'TABLE'
diff --git a/tests/Integration/Routing/Fixtures/query_routes.php b/tests/Integration/Routing/Fixtures/query_routes.php new file mode 100644 index 0000000000..0402820b83 --- /dev/null +++ b/tests/Integration/Routing/Fixtures/query_routes.php @@ -0,0 +1,14 @@ +json([ + 'method' => request()->method(), + 'term' => request()->query('term'), + 'filter' => request()->input('filter'), + ]); +}); diff --git a/tests/Integration/Routing/RouteCachingTest.php b/tests/Integration/Routing/RouteCachingTest.php index 03f650b5ba..d6a21df19f 100644 --- a/tests/Integration/Routing/RouteCachingTest.php +++ b/tests/Integration/Routing/RouteCachingTest.php @@ -32,6 +32,17 @@ public function testRedirectRoutes() $this->get('/foo/1')->assertRedirect('/foo/1/bar'); } + public function testQueryRoutes(): void + { + $this->defineCacheRoutes(file_get_contents(__DIR__ . '/Fixtures/query_routes.php')); + + $this->call('QUERY', '/search?term=hypervel', ['filter' => 'framework'])->assertExactJson([ + 'method' => 'QUERY', + 'term' => 'hypervel', + 'filter' => 'framework', + ]); + } + public function testSetContainerInvalidatesControllerDispatcherCache() { $container1 = new Container; diff --git a/tests/Queue/QueueManagerTest.php b/tests/Queue/QueueManagerTest.php index ac9be0067a..13ef340b9f 100644 --- a/tests/Queue/QueueManagerTest.php +++ b/tests/Queue/QueueManagerTest.php @@ -429,6 +429,18 @@ public function testSetApplicationUpdatesCachedDirectQueueInPlace(): void $this->assertSame($queue, $manager->connection('sync')); } + public function testPayloadCallbacksCanBeRegisteredWithoutResolvingTheDefaultConnection(): void + { + $container = $this->getContainer(); + $container->make('config')->set('queue.default', 'missing'); + + $manager = new QueueManager($container); + + $manager->createPayloadUsing(static fn (string $connection, ?string $queue, array $payload): array => []); + + $this->assertFalse($manager->connected('missing')); + } + protected function getContainer(): Container { $container = new Container; diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index 81c4e0f81b..8fc8ef9e38 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -223,6 +223,17 @@ public function testCanRegisterPostRouteWithClosureAction() $this->seeMiddleware('post-middleware'); } + public function testCanRegisterQueryRouteWithClosureAction(): void + { + $this->router->middleware('query-middleware')->query('users', function (): string { + return 'found'; + }); + + $this->assertTrue($this->getRoute()->matches(Request::create('users', 'QUERY'))); + $this->assertSame(['QUERY'], $this->getRoute()->methods()); + $this->seeMiddleware('query-middleware'); + } + public function testCanRegisterAnyRouteWithClosureAction() { $this->router->middleware('test-middleware')->any('users', function () { diff --git a/tests/Routing/RoutingRouteTest.php b/tests/Routing/RoutingRouteTest.php index d4a169c172..377ab7cdd8 100644 --- a/tests/Routing/RoutingRouteTest.php +++ b/tests/Routing/RoutingRouteTest.php @@ -99,8 +99,12 @@ public function testBasicDispatchingOfRoutes() $router->post('foo/bar', function () { return 'post hello'; }); + $router->query('foo/bar', function (): string { + return 'query hello'; + }); $this->assertSame('hello', $router->dispatch(Request::create('foo/bar', 'GET'))->getContent()); $this->assertSame('post hello', $router->dispatch(Request::create('foo/bar', 'POST'))->getContent()); + $this->assertSame('query hello', $router->dispatch(Request::create('foo/bar', 'QUERY'))->getContent()); $router = $this->getRouter(); $router->get('foo/{bar}', function ($name) { @@ -170,6 +174,7 @@ public function testBasicDispatchingOfRoutes() return 'hello'; }); $this->assertEmpty($router->dispatch(Request::create('foo/bar', 'HEAD'))->getContent()); + $this->assertSame('hello', $router->dispatch(Request::create('foo/bar', 'QUERY'))->getContent()); $router = $this->getRouter(); $router->get('foo/bar', function () { diff --git a/types/Support/Fluent.php b/types/Support/Fluent.php new file mode 100644 index 0000000000..e7ecfb3be6 --- /dev/null +++ b/types/Support/Fluent.php @@ -0,0 +1,14 @@ + $fluent */ +$fluent = new Fluent(['count' => 1]); + +assertType('array', $fluent->all()); +assertType('array', $fluent->all('count')); +assertType('array', $fluent->all(['count', 'total']));