diff --git a/composer.json b/composer.json index 8b4bd32294e8..7950a36bcef5 100644 --- a/composer.json +++ b/composer.json @@ -120,12 +120,15 @@ "psr/simple-cache-implementation": "1.0|2.0|3.0" }, "conflict": { + "carbonphp/carbon-doctrine-types": "<3.0.0|>=4.0", + "doctrine/dbal": "<4.0.0|>=5.0", "tightenco/collect": "<5.5.33" }, "autoload": { "files": [ "src/Illuminate/Collections/helpers.php", "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", "src/Illuminate/Support/helpers.php" ], diff --git a/src/Illuminate/Bus/DynamoBatchRepository.php b/src/Illuminate/Bus/DynamoBatchRepository.php index 25a4d4a90ebc..7753fa21297c 100644 --- a/src/Illuminate/Bus/DynamoBatchRepository.php +++ b/src/Illuminate/Bus/DynamoBatchRepository.php @@ -102,6 +102,7 @@ public function get($limit = 50, $before = null) ':id' => array_filter(['S' => $before]), ]), 'Limit' => $limit, + 'ScanIndexForward' => false, ]); return array_map( diff --git a/src/Illuminate/Cache/ArrayLock.php b/src/Illuminate/Cache/ArrayLock.php index 4c20783b2362..8e1ebe203eea 100644 --- a/src/Illuminate/Cache/ArrayLock.php +++ b/src/Illuminate/Cache/ArrayLock.php @@ -87,6 +87,10 @@ public function release() */ protected function getCurrentOwner() { + if (! $this->exists()) { + return null; + } + return $this->store->locks[$this->name]['owner']; } diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index c14465c6b3fa..e857e1ae18bd 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -109,17 +109,17 @@ public static function divide($array) */ public static function dot($array, $prepend = '') { - $results = []; + $results = [[]]; foreach ($array as $key => $value) { if (is_array($value) && ! empty($value)) { - $results = array_merge($results, static::dot($value, $prepend.$key.'.')); + $results[] = static::dot($value, $prepend.$key.'.'); } else { - $results[$prepend.$key] = $value; + $results[] = [$prepend.$key => $value]; } } - return $results; + return array_merge(...$results); } /** diff --git a/src/Illuminate/Console/MigrationGeneratorCommand.php b/src/Illuminate/Console/MigrationGeneratorCommand.php index fe1d98d81aa8..c741c03358fe 100644 --- a/src/Illuminate/Console/MigrationGeneratorCommand.php +++ b/src/Illuminate/Console/MigrationGeneratorCommand.php @@ -4,6 +4,8 @@ use Illuminate\Filesystem\Filesystem; +use function Illuminate\Filesystem\join_paths; + abstract class MigrationGeneratorCommand extends Command { /** @@ -102,7 +104,7 @@ protected function replaceMigrationPlaceholders($path, $table) protected function migrationExists($table) { return count($this->files->glob( - $this->laravel->joinPaths($this->laravel->databasePath('migrations'), '*_*_*_*_create_'.$table.'_table.php') + join_paths($this->laravel->databasePath('migrations'), '*_*_*_*_create_'.$table.'_table.php') )) !== 0; } } diff --git a/src/Illuminate/Console/Scheduling/ManagesFrequencies.php b/src/Illuminate/Console/Scheduling/ManagesFrequencies.php index c9016bdda227..cc797b69e521 100644 --- a/src/Illuminate/Console/Scheduling/ManagesFrequencies.php +++ b/src/Illuminate/Console/Scheduling/ManagesFrequencies.php @@ -583,7 +583,7 @@ public function quarterly() * Schedule the event to run quarterly on a given day and time. * * @param int $dayOfQuarter - * @param int $time + * @param string $time * @return $this */ public function quarterlyOn($dayOfQuarter = 1, $time = '0:0') diff --git a/src/Illuminate/Database/Console/TableCommand.php b/src/Illuminate/Database/Console/TableCommand.php index 1a7407d5791b..499e1708f6a6 100644 --- a/src/Illuminate/Database/Console/TableCommand.php +++ b/src/Illuminate/Database/Console/TableCommand.php @@ -212,7 +212,7 @@ protected function displayForCli(array $data) $columns->each(function ($column) { $this->components->twoColumnDetail( $column['column'].' '.$column['attributes']->implode(', ').'', - ($column['default'] ? ''.$column['default'].' ' : '').''.$column['type'].'' + (! is_null($column['default']) ? ''.$column['default'].' ' : '').''.$column['type'].'' ); }); diff --git a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php index 8bf7d39f6935..b628d70d2b02 100755 --- a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -146,6 +146,31 @@ protected function compileJsonLength($column, $operator, $value) return 'json_array_length('.$field.$path.') '.$operator.' '.$value; } + /** + * Compile a "JSON contains" statement into SQL. + * + * @param string $column + * @param mixed $value + * @return string + */ + protected function compileJsonContains($column, $value) + { + [$field, $path] = $this->wrapJsonFieldAndPath($column); + + return 'exists (select 1 from json_each('.$field.$path.') where '.$this->wrap('json_each.value').' is '.$value.')'; + } + + /** + * Prepare the binding for a "JSON contains" statement. + * + * @param mixed $binding + * @return mixed + */ + public function prepareBindingForJsonContains($binding) + { + return $binding; + } + /** * Compile a "JSON contains key" statement into SQL. * diff --git a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index d35ee2dc2c6c..e7b062785eb0 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -107,7 +107,7 @@ public function processColumns($results) $autoincrement = $result->default !== null && str_starts_with($result->default, 'nextval('); return [ - 'name' => str_starts_with($result->name, '"') ? str_replace('"', '', $result->name) : $result->name, + 'name' => $result->name, 'type_name' => $result->type_name, 'type' => $result->type, 'collation' => $result->collation, diff --git a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php index 8f5fb98206e0..adcb16a2038a 100644 --- a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php @@ -36,7 +36,7 @@ public function processColumns($results) return [ 'name' => $result->name, - 'type_name' => strtok($type, '('), + 'type_name' => strtok($type, '(') ?: '', 'type' => $type, 'collation' => null, 'nullable' => (bool) $result->nullable, diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index 640cf0177c37..c8a534cafec9 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -130,6 +130,10 @@ public function toSql(Connection $connection, Grammar $grammar) $this->ensureCommandsAreValid($connection); foreach ($this->commands as $command) { + if ($command->shouldBeSkipped) { + continue; + } + $method = 'compile'.ucfirst($command->name); if (method_exists($grammar, $method) || $grammar::hasMacro($method)) { @@ -309,6 +313,28 @@ public function innoDb() $this->engine('InnoDB'); } + /** + * Specify the character set that should be used for the table. + * + * @param string $charset + * @return void + */ + public function charset($charset) + { + $this->charset = $charset; + } + + /** + * Specify the collation that should be used for the table. + * + * @param string $collation + * @return void + */ + public function collation($collation) + { + $this->collation = $collation; + } + /** * Indicate that the table needs to be temporary. * diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 07847fa23f8e..6544c1d2b588 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -158,7 +158,7 @@ public function compileColumns($database, $table) return sprintf( 'select column_name as `name`, data_type as `type_name`, column_type as `type`, ' .'collation_name as `collation`, is_nullable as `nullable`, ' - .'column_default as `default`, column_comment AS `comment`, extra as `extra` ' + .'column_default as `default`, column_comment as `comment`, extra as `extra` ' .'from information_schema.columns where table_schema = %s and table_name = %s ' .'order by ordinal_position asc', $this->quoteString($database), @@ -248,10 +248,22 @@ public function compileCreate(Blueprint $blueprint, Fluent $command, Connection */ protected function compileCreateTable($blueprint, $command, $connection) { + $tableStructure = $this->getColumns($blueprint); + + if ($primaryKey = $this->getCommandByName($blueprint, 'primary')) { + $tableStructure[] = sprintf( + 'primary key %s(%s)', + $primaryKey->algorithm ? 'using '.$primaryKey->algorithm : '', + $this->columnize($primaryKey->columns) + ); + + $primaryKey->shouldBeSkipped = true; + } + return sprintf('%s table %s (%s)', $blueprint->temporary ? 'create temporary' : 'create', $this->wrapTable($blueprint), - implode(', ', $this->getColumns($blueprint)) + implode(', ', $tableStructure) ); } diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index a0b67f01ca60..56a0bc510f88 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -169,11 +169,11 @@ public function compileColumnListing() public function compileColumns($schema, $table) { return sprintf( - 'select quote_ident(a.attname) as name, t.typname as type_name, format_type(a.atttypid, a.atttypmod) as type, ' + 'select a.attname as name, t.typname as type_name, format_type(a.atttypid, a.atttypmod) as type, ' .'(select tc.collcollate from pg_catalog.pg_collation tc where tc.oid = a.attcollation) as collation, ' .'not a.attnotnull as nullable, ' .'(select pg_get_expr(adbin, adrelid) from pg_attrdef where c.oid = pg_attrdef.adrelid and pg_attrdef.adnum = a.attnum) as default, ' - .'(select pg_description.description from pg_description where pg_description.objoid = c.oid and a.attnum = pg_description.objsubid) as comment ' + .'col_description(c.oid, a.attnum) as comment ' .'from pg_attribute a, pg_class c, pg_type t, pg_namespace n ' .'where c.relname = %s and n.nspname = %s and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid and n.oid = c.relnamespace ' .'order by a.attnum', diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 744a9d691534..02766df8e84f 100755 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -122,7 +122,7 @@ public function compileColumnListing($table) public function compileColumns($table) { return sprintf( - "select name, type, not 'notnull' as 'nullable', dflt_value as 'default', pk as 'primary' " + 'select name, type, not "notnull" as "nullable", dflt_value as "default", pk as "primary" ' .'from pragma_table_info(%s) order by cid asc', $this->wrap(str_replace('.', '__', $table)) ); diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index dd56619c74d9..d97ff4ce7847 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -162,7 +162,8 @@ public function compileColumns($table) .'join sys.schemas as scm on obj.schema_id = scm.schema_id ' .'left join sys.default_constraints def on col.default_object_id = def.object_id and col.object_id = def.parent_object_id ' ."left join sys.extended_properties as prop on obj.object_id = prop.major_id and col.column_id = prop.minor_id and prop.name = 'MS_Description' " - ."where obj.type = 'U' and obj.name = %s and scm.name = SCHEMA_NAME()", + ."where obj.type in ('U', 'V') and obj.name = %s and scm.name = SCHEMA_NAME() " + .'order by col.column_id', $this->quoteString($table), ); } diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index c840493bd621..a4fbaf38280e 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -34,6 +34,10 @@ "dev-master": "11.x-dev" } }, + "conflict": { + "carbonphp/carbon-doctrine-types": "<3.0.0|>=4.0", + "doctrine/dbal": "<4.0.0|>=5.0" + }, "suggest": { "ext-filter": "Required to use the Postgres database driver.", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^4.0).", diff --git a/src/Illuminate/Filesystem/composer.json b/src/Illuminate/Filesystem/composer.json index b46be7d136d2..95ee6851a485 100644 --- a/src/Illuminate/Filesystem/composer.json +++ b/src/Illuminate/Filesystem/composer.json @@ -24,7 +24,10 @@ "autoload": { "psr-4": { "Illuminate\\Filesystem\\": "" - } + }, + "files": [ + "functions.php" + ] }, "extra": { "branch-alias": { diff --git a/src/Illuminate/Filesystem/functions.php b/src/Illuminate/Filesystem/functions.php new file mode 100644 index 000000000000..47a26fd5eb38 --- /dev/null +++ b/src/Illuminate/Filesystem/functions.php @@ -0,0 +1,25 @@ + $path) { + if (empty($path)) { + unset($paths[$index]); + } else { + $paths[$index] = DIRECTORY_SEPARATOR.ltrim($path, DIRECTORY_SEPARATOR); + } + } + + return $basePath.implode('', $paths); + } +} diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 7679ed757c94..21456adc1e49 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -32,6 +32,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; +use function Illuminate\Filesystem\join_paths; + class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface { use Macroable; @@ -624,7 +626,7 @@ public function viewPath($path = '') */ public function joinPaths($basePath, $path = '') { - return $basePath.($path != '' ? DIRECTORY_SEPARATOR.ltrim($path, DIRECTORY_SEPARATOR) : ''); + return join_paths($basePath, $path); } /** diff --git a/src/Illuminate/Foundation/Console/AboutCommand.php b/src/Illuminate/Foundation/Console/AboutCommand.php index 23d2b23b631a..b393c60f1d6a 100644 --- a/src/Illuminate/Foundation/Console/AboutCommand.php +++ b/src/Illuminate/Foundation/Console/AboutCommand.php @@ -159,6 +159,8 @@ protected function displayJson($data) */ protected function gatherApplicationInformation() { + self::$data = []; + $formatEnabledStatus = fn ($value) => $value ? 'ENABLED' : 'OFF'; $formatCachedStatus = fn ($value) => $value ? 'CACHED' : 'NOT CACHED'; diff --git a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php index 2134171e0ee7..7a9bb7c91a66 100644 --- a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php @@ -98,11 +98,11 @@ public function registerConsoleSchedule() */ public function registerDumper() { - AbstractCloner::$defaultCasters[ConnectionInterface::class] = [StubCaster::class, 'cutInternals']; - AbstractCloner::$defaultCasters[Container::class] = [StubCaster::class, 'cutInternals']; - AbstractCloner::$defaultCasters[Dispatcher::class] = [StubCaster::class, 'cutInternals']; - AbstractCloner::$defaultCasters[Factory::class] = [StubCaster::class, 'cutInternals']; - AbstractCloner::$defaultCasters[Grammar::class] = [StubCaster::class, 'cutInternals']; + AbstractCloner::$defaultCasters[ConnectionInterface::class] ??= [StubCaster::class, 'cutInternals']; + AbstractCloner::$defaultCasters[Container::class] ??= [StubCaster::class, 'cutInternals']; + AbstractCloner::$defaultCasters[Dispatcher::class] ??= [StubCaster::class, 'cutInternals']; + AbstractCloner::$defaultCasters[Factory::class] ??= [StubCaster::class, 'cutInternals']; + AbstractCloner::$defaultCasters[Grammar::class] ??= [StubCaster::class, 'cutInternals']; $basePath = $this->app->basePath(); diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 8aad234668c9..a5468e378830 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -583,7 +583,7 @@ public function call($method, $uri, $parameters = [], $cookies = [], $files = [] ); $response = $kernel->handle( - $request = Request::createFromBase($symfonyRequest) + $request = $this->createTestRequest($symfonyRequest) ); $kernel->terminate($request, $response); @@ -710,6 +710,17 @@ protected function followRedirects($response) return $response; } + /** + * Create the request instance used for testing from the given Symfony request. + * + * @param \Symfony\Component\HttpFoundation\Request $symfonyRequest + * @return \Illuminate\Http\Request + */ + protected function createTestRequest($symfonyRequest) + { + return Request::createFromBase($symfonyRequest); + } + /** * Create the test response instance from the given response. * diff --git a/src/Illuminate/Notifications/Messages/MailMessage.php b/src/Illuminate/Notifications/Messages/MailMessage.php index d847072c8f59..280f2e1f2e2e 100644 --- a/src/Illuminate/Notifications/Messages/MailMessage.php +++ b/src/Illuminate/Notifications/Messages/MailMessage.php @@ -129,6 +129,21 @@ public function view($view, array $data = []) return $this; } + /** + * Set the plain text view for the mail message. + * + * @param string $textView + * @param array $data + * @return $this + */ + public function text($textView, array $data = []) + { + return $this->view([ + 'html' => is_array($this->view) ? ($this->view['html'] ?? null) : $this->view, + 'text' => $textView, + ], $data); + } + /** * Set the Markdown template for the notification. * diff --git a/src/Illuminate/Pipeline/Pipeline.php b/src/Illuminate/Pipeline/Pipeline.php index 53b9fef182e3..0f32b6121125 100644 --- a/src/Illuminate/Pipeline/Pipeline.php +++ b/src/Illuminate/Pipeline/Pipeline.php @@ -5,11 +5,14 @@ use Closure; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract; +use Illuminate\Support\Traits\Conditionable; use RuntimeException; use Throwable; class Pipeline implements PipelineContract { + use Conditionable; + /** * The container implementation. * diff --git a/src/Illuminate/Support/Facades/Pipeline.php b/src/Illuminate/Support/Facades/Pipeline.php index 1570c7d28104..0874905199ba 100644 --- a/src/Illuminate/Support/Facades/Pipeline.php +++ b/src/Illuminate/Support/Facades/Pipeline.php @@ -10,6 +10,8 @@ * @method static mixed then(\Closure $destination) * @method static mixed thenReturn() * @method static \Illuminate\Pipeline\Pipeline setContainer(\Illuminate\Contracts\Container\Container $container) + * @method static \Illuminate\Pipeline\Pipeline|mixed when(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) + * @method static \Illuminate\Pipeline\Pipeline|mixed unless(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) * * @see \Illuminate\Pipeline\Pipeline */ diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 5291583ca5fc..f28ec0bbdd68 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -821,6 +821,17 @@ public static function padRight($value, $length, $pad = ' ') */ public static function parseCallback($callback, $default = null) { + if (static::contains($callback, "@anonymous\0")) { + if (static::substrCount($callback, '@') > 1) { + return [ + static::beforeLast($callback, '@'), + static::afterLast($callback, '@'), + ]; + } + + return [$callback, $default]; + } + return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default]; } diff --git a/src/Illuminate/Support/Testing/Fakes/BusFake.php b/src/Illuminate/Support/Testing/Fakes/BusFake.php index 38a3f2c15708..e62fac4b08a8 100644 --- a/src/Illuminate/Support/Testing/Fakes/BusFake.php +++ b/src/Illuminate/Support/Testing/Fakes/BusFake.php @@ -11,6 +11,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Traits\ReflectsClosures; use PHPUnit\Framework\Assert as PHPUnit; +use RuntimeException; class BusFake implements Fake, QueueingDispatcher { diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 38e7466e0095..a51e5d6afa65 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -155,7 +155,7 @@ public function assertStatus($status) { $message = $this->statusMessageWithDetails($status, $actual = $this->getStatusCode()); - PHPUnit::assertSame($actual, $status, $message); + PHPUnit::assertSame($status, $actual, $message); return $this; } diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index ce0ce658072c..312c0b71e34c 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -1438,6 +1438,10 @@ public function validateJson($attribute, $value) return false; } + if (function_exists('json_validate')) { + return json_validate($value); + } + json_decode($value); return json_last_error() === JSON_ERROR_NONE; diff --git a/src/Illuminate/View/Compilers/BladeCompiler.php b/src/Illuminate/View/Compilers/BladeCompiler.php index 18aae67e14ba..c006bbd0f5c2 100644 --- a/src/Illuminate/View/Compilers/BladeCompiler.php +++ b/src/Illuminate/View/Compilers/BladeCompiler.php @@ -30,6 +30,7 @@ class BladeCompiler extends Compiler implements CompilerInterface Concerns\CompilesLayouts, Concerns\CompilesLoops, Concerns\CompilesRawPhp, + Concerns\CompilesSessions, Concerns\CompilesStacks, Concerns\CompilesStyles, Concerns\CompilesTranslations, diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesSessions.php b/src/Illuminate/View/Compilers/Concerns/CompilesSessions.php new file mode 100644 index 000000000000..0c375b406542 --- /dev/null +++ b/src/Illuminate/View/Compilers/Concerns/CompilesSessions.php @@ -0,0 +1,37 @@ +stripParentheses($expression); + + return 'has($__sessionArgs[0])) : +if (isset($value)) { $__sessionPrevious[] = $value; } +$value = session()->get($__sessionArgs[0]); ?>'; + } + + /** + * Compile the endsession statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndsession($expression) + { + return ''; + } +} diff --git a/src/Illuminate/View/ComponentAttributeBag.php b/src/Illuminate/View/ComponentAttributeBag.php index 98025abcd0fd..f9969b9911b0 100644 --- a/src/Illuminate/View/ComponentAttributeBag.php +++ b/src/Illuminate/View/ComponentAttributeBag.php @@ -351,6 +351,26 @@ protected function resolveAppendableAttributeDefault($attributeDefaults, $key, $ return $value; } + /** + * Determine if the attribute bag is empty. + * + * @return bool + */ + public function isEmpty() + { + return trim((string) $this) === ''; + } + + /** + * Determine if the attribute bag is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return ! $this->isEmpty(); + } + /** * Get all of the raw attributes. * diff --git a/tests/Cache/CacheArrayStoreTest.php b/tests/Cache/CacheArrayStoreTest.php index 9538d41a2148..08326e4a4a8a 100755 --- a/tests/Cache/CacheArrayStoreTest.php +++ b/tests/Cache/CacheArrayStoreTest.php @@ -293,4 +293,36 @@ public function testReleasingLockAfterAlreadyForceReleasedByAnotherOwnerFails() $this->assertFalse($wannabeOwner->release()); } + + public function testOwnerStatusCanBeCheckedAfterRestoringLock() + { + $store = new ArrayStore; + $firstLock = $store->lock('foo', 10); + + $this->assertTrue($firstLock->get()); + $owner = $firstLock->owner(); + + $secondLock = $store->restoreLock('foo', $owner); + $this->assertTrue($secondLock->isOwnedByCurrentProcess()); + } + + public function testOtherOwnerDoesNotOwnLockAfterRestore() + { + $store = new ArrayStore; + $firstLock = $store->lock('foo', 10); + + $this->assertTrue($firstLock->get()); + + $secondLock = $store->restoreLock('foo', 'other_owner'); + + $this->assertFalse($secondLock->isOwnedByCurrentProcess()); + } + + public function testRestoringNonExistingLockDoesNotOwnAnything() + { + $store = new ArrayStore; + $firstLock = $store->restoreLock('foo', 'owner'); + + $this->assertFalse($firstLock->isOwnedByCurrentProcess()); + } } diff --git a/tests/Database/DatabaseMySqlSchemaGrammarTest.php b/tests/Database/DatabaseMySqlSchemaGrammarTest.php index 7ec3e87f9752..3c1cea72fd49 100755 --- a/tests/Database/DatabaseMySqlSchemaGrammarTest.php +++ b/tests/Database/DatabaseMySqlSchemaGrammarTest.php @@ -46,6 +46,18 @@ public function testBasicCreateTable() $this->assertCount(1, $statements); $this->assertSame('alter table `users` add `id` int unsigned not null auto_increment primary key, add `email` varchar(255) not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->uuid('id')->primary(); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('create table `users` (`id` char(36) not null, primary key (`id`))', $statements[0]); } public function testAutoIncrementStartingValue() @@ -119,8 +131,8 @@ public function testCharsetCollationCreateTable() $blueprint->create(); $blueprint->increments('id'); $blueprint->string('email'); - $blueprint->charset = 'utf8mb4'; - $blueprint->collation = 'utf8mb4_unicode_ci'; + $blueprint->charset('utf8mb4'); + $blueprint->collation('utf8mb4_unicode_ci'); $conn = $this->getConnection(); $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index b05798eca8a7..011e22c99afb 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -5192,10 +5192,15 @@ public function testWhereJsonContainsPostgres() public function testWhereJsonContainsSqlite() { - $this->expectException(RuntimeException::class); + $builder = $this->getSQLiteBuilder(); + $builder->select('*')->from('users')->whereJsonContains('options', 'en')->toSql(); + $this->assertSame('select * from "users" where exists (select 1 from json_each("options") where "json_each"."value" is ?)', $builder->toSql()); + $this->assertEquals(['en'], $builder->getBindings()); $builder = $this->getSQLiteBuilder(); - $builder->select('*')->from('users')->whereJsonContains('options->languages', ['en'])->toSql(); + $builder->select('*')->from('users')->whereJsonContains('users.options->language', 'en')->toSql(); + $this->assertSame('select * from "users" where exists (select 1 from json_each("users"."options", \'$."language"\') where "json_each"."value" is ?)', $builder->toSql()); + $this->assertEquals(['en'], $builder->getBindings()); } public function testWhereJsonContainsSqlServer() @@ -5244,10 +5249,15 @@ public function testWhereJsonDoesntContainPostgres() public function testWhereJsonDoesntContainSqlite() { - $this->expectException(RuntimeException::class); + $builder = $this->getSQLiteBuilder(); + $builder->select('*')->from('users')->whereJsonDoesntContain('options', 'en')->toSql(); + $this->assertSame('select * from "users" where not exists (select 1 from json_each("options") where "json_each"."value" is ?)', $builder->toSql()); + $this->assertEquals(['en'], $builder->getBindings()); $builder = $this->getSQLiteBuilder(); - $builder->select('*')->from('users')->whereJsonDoesntContain('options->languages', ['en'])->toSql(); + $builder->select('*')->from('users')->whereJsonDoesntContain('users.options->language', 'en')->toSql(); + $this->assertSame('select * from "users" where not exists (select 1 from json_each("users"."options", \'$."language"\') where "json_each"."value" is ?)', $builder->toSql()); + $this->assertEquals(['en'], $builder->getBindings()); } public function testWhereJsonDoesntContainSqlServer() diff --git a/tests/Filesystem/JoinPathsHelperTest.php b/tests/Filesystem/JoinPathsHelperTest.php new file mode 100644 index 000000000000..2a4d7cf2e089 --- /dev/null +++ b/tests/Filesystem/JoinPathsHelperTest.php @@ -0,0 +1,38 @@ +assertSame($expected, $given); + } + + public static function unixDataProvider() + { + yield ['app/Http/Kernel.php', join_paths('app', 'Http', 'Kernel.php')]; + yield ['app/Http/Kernel.php', join_paths('app', '', 'Http', 'Kernel.php')]; + } + + #[RequiresOperatingSystem('Windows')] + #[DataProvider('windowsDataProvider')] + public function testItCanMergePathsForWindows(string $expected, string $given) + { + $this->assertSame($expected, $given); + } + + public static function windowsDataProvider() + { + yield ['app\Http\Kernel.php', join_paths('app', 'Http', 'Kernel.php')]; + yield ['app\Http\Kernel.php', join_paths('app', '', 'Http', 'Kernel.php')]; + } +} diff --git a/tests/Integration/Database/Queue/QueueTransactionTest.php b/tests/Integration/Database/Queue/QueueTransactionTest.php new file mode 100644 index 000000000000..1586f2539d90 --- /dev/null +++ b/tests/Integration/Database/Queue/QueueTransactionTest.php @@ -0,0 +1,54 @@ +get('database.default') === 'testing') { + $this->markTestSkipped('Test does not support using :memory: database connection'); + } + + $config->set(['queue.default' => 'database']); + } + + public function testItCanHandleTimeoutJob() + { + dispatch(new Fixtures\TimeOutJobWithTransaction); + + $this->assertSame(1, DB::table('jobs')->count()); + $this->assertSame(0, DB::table('failed_jobs')->count()); + + try { + remote('queue:work --stop-when-empty', [ + 'DB_CONNECTION' => config('database.default'), + 'QUEUE_CONNECTION' => config('queue.default'), + ])->run(); + } catch (Throwable $e) { + $this->assertInstanceOf(ProcessSignaledException::class, $e); + $this->assertSame('The process has been signaled with signal "9".', $e->getMessage()); + } + + $this->assertSame(0, DB::table('jobs')->count()); + $this->assertSame(1, DB::table('failed_jobs')->count()); + } +} diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 18010c5a58bc..a290abf6336d 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -210,6 +210,38 @@ public function testGetAndDropTypes() $this->assertEmpty($types); } + public function testGetColumns() + { + Schema::create('foo', function (Blueprint $table) { + $table->id(); + $table->string('bar')->nullable(); + $table->string('baz')->default('test'); + }); + + $columns = Schema::getColumns('foo'); + + $this->assertCount(3, $columns); + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'id' && $column['auto_increment'] && ! $column['nullable'] + )); + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'bar' && $column['nullable'] + )); + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'baz' && ! $column['nullable'] && str_contains($column['default'], 'test') + )); + } + + public function testGetColumnsOnView() + { + DB::statement('create view foo (bar) as select 1'); + + $columns = Schema::getColumns('foo'); + + $this->assertCount(1, $columns); + $this->assertTrue($columns[0]['name'] === 'bar'); + } + public function testGetIndexes() { Schema::create('foo', function (Blueprint $table) { diff --git a/tests/Integration/Queue/UniqueJobTest.php b/tests/Integration/Queue/UniqueJobTest.php index 7904ab781f78..36eb9aeb5cb7 100644 --- a/tests/Integration/Queue/UniqueJobTest.php +++ b/tests/Integration/Queue/UniqueJobTest.php @@ -64,7 +64,7 @@ public function testLockIsReleasedForFailedJobs() $this->expectException(Exception::class); try { - dispatchSync($job = new UniqueTestFailJob); + dispatch_sync($job = new UniqueTestFailJob); } finally { $this->assertTrue($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); diff --git a/tests/Pipeline/PipelineTest.php b/tests/Pipeline/PipelineTest.php index d2233a271329..332af26b46f3 100644 --- a/tests/Pipeline/PipelineTest.php +++ b/tests/Pipeline/PipelineTest.php @@ -243,6 +243,36 @@ public function testPipelineThenReturnMethodRunsPipelineThenReturnsPassable() unset($_SERVER['__test.pipe.one']); } + + public function testPipelineConditionable() + { + $result = (new Pipeline(new Container)) + ->send('foo') + ->when(true, function (Pipeline $pipeline) { + $pipeline->pipe([PipelineTestPipeOne::class]); + }) + ->then(function ($piped) { + return $piped; + }); + + $this->assertSame('foo', $result); + $this->assertSame('foo', $_SERVER['__test.pipe.one']); + unset($_SERVER['__test.pipe.one']); + + $_SERVER['__test.pipe.one'] = null; + $result = (new Pipeline(new Container)) + ->send('foo') + ->when(false, function (Pipeline $pipeline) { + $pipeline->pipe([PipelineTestPipeOne::class]); + }) + ->then(function ($piped) { + return $piped; + }); + + $this->assertSame('foo', $result); + $this->assertNull($_SERVER['__test.pipe.one']); + unset($_SERVER['__test.pipe.one']); + } } class PipelineTestPipeOne diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index 2c5f6c224e67..a539eb4b2b02 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -100,19 +100,42 @@ public function testDivide() public function testDot() { $array = Arr::dot(['foo' => ['bar' => 'baz']]); - $this->assertEquals(['foo.bar' => 'baz'], $array); + $this->assertSame(['foo.bar' => 'baz'], $array); $array = Arr::dot([]); - $this->assertEquals([], $array); + $this->assertSame([], $array); $array = Arr::dot(['foo' => []]); - $this->assertEquals(['foo' => []], $array); + $this->assertSame(['foo' => []], $array); $array = Arr::dot(['foo' => ['bar' => []]]); - $this->assertEquals(['foo.bar' => []], $array); + $this->assertSame(['foo.bar' => []], $array); $array = Arr::dot(['name' => 'taylor', 'languages' => ['php' => true]]); - $this->assertEquals(['name' => 'taylor', 'languages.php' => true], $array); + $this->assertSame(['name' => 'taylor', 'languages.php' => true], $array); + + $array = Arr::dot(['user' => ['name' => 'Taylor', 'age' => 25, 'languages' => ['PHP', 'C#']]]); + $this->assertSame([ + 'user.name' => 'Taylor', + 'user.age' => 25, + 'user.languages.0' =>'PHP', + 'user.languages.1' => 'C#', + ], $array); + + $array = Arr::dot(['foo', 'foo' => ['bar' => 'baz', 'baz' => ['a' => 'b']]]); + $this->assertSame([ + 'foo', + 'foo.bar' => 'baz', + 'foo.baz.a' => 'b', + ], $array); + + $array = Arr::dot(['foo' => 'bar', 'empty_array' => [], 'user' => ['name' => 'Taylor'], 'key' => 'value']); + $this->assertSame([ + 'foo' => 'bar', + 'empty_array' => [], + 'user.name' => 'Taylor', + 'key' => 'value', + ], $array); } public function testUndot() diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index a2f6e47457de..86e48bd502c0 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -325,9 +325,15 @@ public function testConvertCase() public function testParseCallback() { + $this->assertEquals(['Class', 'method'], Str::parseCallback('Class@method')); $this->assertEquals(['Class', 'method'], Str::parseCallback('Class@method', 'foo')); $this->assertEquals(['Class', 'foo'], Str::parseCallback('Class', 'foo')); $this->assertEquals(['Class', null], Str::parseCallback('Class')); + + $this->assertEquals(["Class@anonymous\0/laravel/382.php:8$2ec", 'method'], Str::parseCallback("Class@anonymous\0/laravel/382.php:8$2ec@method")); + $this->assertEquals(["Class@anonymous\0/laravel/382.php:8$2ec", 'method'], Str::parseCallback("Class@anonymous\0/laravel/382.php:8$2ec@method", 'foo')); + $this->assertEquals(["Class@anonymous\0/laravel/382.php:8$2ec", 'foo'], Str::parseCallback("Class@anonymous\0/laravel/382.php:8$2ec", 'foo')); + $this->assertEquals(["Class@anonymous\0/laravel/382.php:8$2ec", null], Str::parseCallback("Class@anonymous\0/laravel/382.php:8$2ec")); } public function testSlug() diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index 6f565e981f8a..3934bbbebc13 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -596,7 +596,7 @@ public function testAssertMethodNotAllowed() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [405] but received 200.\nFailed asserting that 405 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [405] but received 200.\nFailed asserting that 200 is identical to 405."); $response->assertMethodNotAllowed(); } @@ -614,7 +614,7 @@ public function testAssertNotAcceptable() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [406] but received 200.\nFailed asserting that 406 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [406] but received 200.\nFailed asserting that 200 is identical to 406."); $response->assertNotAcceptable(); $this->fail(); @@ -665,7 +665,7 @@ public function testAssertBadRequest() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [400] but received 200.\nFailed asserting that 400 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [400] but received 200.\nFailed asserting that 200 is identical to 400."); $response->assertBadRequest(); $this->fail(); @@ -684,7 +684,7 @@ public function testAssertRequestTimeout() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [408] but received 200.\nFailed asserting that 408 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [408] but received 200.\nFailed asserting that 200 is identical to 408."); $response->assertRequestTimeout(); $this->fail(); @@ -703,7 +703,7 @@ public function testAssertPaymentRequired() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [402] but received 200.\nFailed asserting that 402 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [402] but received 200.\nFailed asserting that 200 is identical to 402."); $response->assertPaymentRequired(); $this->fail(); @@ -722,7 +722,7 @@ public function testAssertMovedPermanently() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [301] but received 200.\nFailed asserting that 301 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [301] but received 200.\nFailed asserting that 200 is identical to 301."); $response->assertMovedPermanently(); $this->fail(); @@ -741,7 +741,7 @@ public function testAssertFound() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [302] but received 200.\nFailed asserting that 302 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [302] but received 200.\nFailed asserting that 200 is identical to 302."); $response->assertFound(); $this->fail(); @@ -760,7 +760,7 @@ public function testAssertNotModified() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [304] but received 200.\nFailed asserting that 304 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [304] but received 200.\nFailed asserting that 200 is identical to 304."); $response->assertNotModified(); $this->fail(); @@ -779,7 +779,7 @@ public function testAssertTemporaryRedirect() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [307] but received 200.\nFailed asserting that 307 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [307] but received 200.\nFailed asserting that 200 is identical to 307."); $response->assertTemporaryRedirect(); $this->fail(); @@ -798,7 +798,7 @@ public function testAssertPermanentRedirect() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [308] but received 200.\nFailed asserting that 308 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [308] but received 200.\nFailed asserting that 200 is identical to 308."); $response->assertPermanentRedirect(); $this->fail(); @@ -817,7 +817,7 @@ public function testAssertConflict() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [409] but received 200.\nFailed asserting that 409 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [409] but received 200.\nFailed asserting that 200 is identical to 409."); $response->assertConflict(); $this->fail(); @@ -836,7 +836,7 @@ public function testAssertGone() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [410] but received 200.\nFailed asserting that 410 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [410] but received 200.\nFailed asserting that 200 is identical to 410."); $response->assertGone(); } @@ -854,7 +854,7 @@ public function testAssertTooManyRequests() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [429] but received 200.\nFailed asserting that 429 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [429] but received 200.\nFailed asserting that 200 is identical to 429."); $response->assertTooManyRequests(); $this->fail(); @@ -873,7 +873,7 @@ public function testAssertAccepted() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [202] but received 200.\nFailed asserting that 202 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [202] but received 200.\nFailed asserting that 200 is identical to 202."); $response->assertAccepted(); $this->fail(); @@ -920,7 +920,7 @@ public function testAssertInternalServerError() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [500] but received 200.\nFailed asserting that 500 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [500] but received 200.\nFailed asserting that 200 is identical to 500."); $response->assertInternalServerError(); } @@ -938,7 +938,7 @@ public function testAssertServiceUnavailable() ); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage("Expected response status code [503] but received 200.\nFailed asserting that 503 is identical to 200."); + $this->expectExceptionMessage("Expected response status code [503] but received 200.\nFailed asserting that 200 is identical to 503."); $response->assertServiceUnavailable(); } diff --git a/tests/View/Blade/BladeSessionTest.php b/tests/View/Blade/BladeSessionTest.php new file mode 100644 index 000000000000..d3d553e0a7e9 --- /dev/null +++ b/tests/View/Blade/BladeSessionTest.php @@ -0,0 +1,27 @@ +{{ $value }} +@endsession'; + $expected = ' +has($__sessionArgs[0])) : +if (isset($value)) { $__sessionPrevious[] = $value; } +$value = session()->get($__sessionArgs[0]); ?> + +'; + + $this->assertEquals($expected, $this->compiler->compileString($string)); + } +} diff --git a/tests/View/ViewComponentAttributeBagTest.php b/tests/View/ViewComponentAttributeBagTest.php index 2d15ee11fac1..1b64720f9b79 100644 --- a/tests/View/ViewComponentAttributeBagTest.php +++ b/tests/View/ViewComponentAttributeBagTest.php @@ -128,4 +128,18 @@ public function testAttributeExistence() $this->assertFalse((bool) $bag->has('name', 'class')); $this->assertTrue((bool) $bag->missing('class')); } + + public function testAttributeIsEmpty() + { + $bag = new ComponentAttributeBag([]); + + $this->assertTrue((bool) $bag->isEmpty()); + } + + public function testAttributeIsNotEmpty() + { + $bag = new ComponentAttributeBag(['name' => 'test']); + + $this->assertTrue((bool) $bag->isNotEmpty()); + } }