From 6fb2d08b6b9ffda85abec51f4773aee763a52a73 Mon Sep 17 00:00:00 2001 From: bert-w Date: Tue, 27 Feb 2024 22:59:11 +0100 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 39561b1dab03c45debdddadf71523570626aeaaa Merge: 819e6e0997 778683570e Author: Nuno Maduro Date: Mon Feb 26 10:36:42 2024 +0000 Merge branch '10.x' commit 778683570e1a9d0a8a0da86f43f710edbd928a51 Author: Amir Reza Mehrbakhsh Date: Mon Feb 26 09:10:21 2024 +0100 Update return type (#50252) commit 819e6e09974770cc476eeb3063d63b4b366bdd7c Author: Taylor Otwell Date: Sun Feb 25 14:03:19 2024 -0600 tweak controller middleware commit 5f8684b3dde621a3fd6b523e9f48f232de38611c Author: Magnus Hauge Bakke Date: Sun Feb 25 16:34:09 2024 +0100 [10.x] Add Lateral Join to Query Builder (#50050) * Add lateral join support to Query Builder * formatting --------- Co-authored-by: Taylor Otwell commit 00fb1f3460385f195f39afc420db34064c1aa2f6 Author: taylorotwell Date: Sun Feb 25 15:13:57 2024 +0000 Update facade docblocks commit 6802941843ffefec6054ed37450b2a53ade72f64 Author: Alexandre Gérault Date: Sun Feb 25 16:13:25 2024 +0100 [11.x] Add typed getters for configuration (#50140) * feat: add typed getters * refactor: use builtin type functions instead of webmozart assertions * style: fix for styleci * feat(config): add missing methods in contract * fix: remove contract from methods and add phpdoc * style: remove doubled empty lines * formatting * import class * add default values --------- Co-authored-by: Taylor Otwell commit 8648a4a8ff761b01d9a390b9ddb4cdd46201e86d Author: Taylor Otwell Date: Sun Feb 25 08:52:28 2024 -0600 use uuid by default on uuid commit ca2ce7c0924c325590b7506b3d830f5b845ab9a8 Author: taylorotwell Date: Sun Feb 25 14:45:37 2024 +0000 Update facade docblocks commit e45d4248840b5e43d20eec0c7db448e68f386130 Author: Sebastien Armand Date: Sun Feb 25 06:45:05 2024 -0800 [10.x] Custom RateLimiter increase (#50197) * Allow ratelimiter custom increments * add test * formatting * formatting * Revert formatting * Formatting * formatting --------- Co-authored-by: Tim MacDonald Co-authored-by: Taylor Otwell commit 7af199446137b8eaba53a3bea2edd028279c6176 Author: Guilhem-DELAITRE <89917125+Guilhem-DELAITRE@users.noreply.github.com> Date: Sun Feb 25 15:40:35 2024 +0100 [10.x] Fixes on nesting operations performed while applying scopes. (#50207) * Add tests to emphasize the issues with nesting due to scope (nesting "or" groups but not "or not" groups and doubling first where clause negation) * Fix issues : also nest on "or not" clause, and don't repeat first where clause negation when nesting commit 06a5717aafa12f7a41017e7ffb6961fc6cfb11e8 Author: Goi Garg Date: Sun Feb 25 20:01:43 2024 +0530 Update message.blade.php (#50224) Using recommended method defined in latest docs commit 8d47be393e43ffeacd49556471110454f868da5f Author: Anton5360 <72033639+Anton5360@users.noreply.github.com> Date: Sun Feb 25 07:30:59 2024 -0700 [10.x] Add only and except methods to Enum validation rule (#50226) * Implement only and except logic * Cover only and except logic with tests * Fix code styling * Fix code styling * Improve php doc * Fix code styling * Fix code styling * formatting * fix visibility * fix type hints * remove type hint * fix type hints --------- Co-authored-by: Taylor Otwell commit 30324cf06d1f34fe359c5e458bcbb8da5413db71 Author: Liam Duckett <116881406+liamduckett@users.noreply.github.com> Date: Sun Feb 25 14:18:43 2024 +0000 Make GuardsAttributes fillable property DocBlock more specific (#50229) commit 26dfe56c7fe1f75c979b8e1255aafdbc5d9380e2 Author: Nuno Maduro Date: Sun Feb 25 14:08:50 2024 +0000 [11.x] Publishes `broadcasting.php` configuration file on `install:broadcasting" (#50234) * Publishes configuration file on `install:broadcasting" * Update BroadcastingInstallCommand.php * Update BroadcastingInstallCommand.php --------- Co-authored-by: Taylor Otwell commit e38bcca6aa78a250cda24530fa09f6b8610dac18 Author: Taylor Otwell Date: Fri Feb 23 14:35:30 2024 -0600 slim configuration commit 56250738b43f0ff3e20a3596ac2caa0b589a1aac Author: Dries Vints Date: Fri Feb 23 17:33:02 2024 +0100 Fix release notes commit d309d71f0102719be7e7984e4462889d2ab24f48 Author: Dries Vints Date: Fri Feb 23 17:16:58 2024 +0100 Update CHANGELOG.md commit dda69ba7208a3093390594ba2f6095041d2b18c2 Author: driesvints Date: Fri Feb 23 16:14:41 2024 +0000 Update CHANGELOG commit c76102c97e8a6991b42d7deb7384023682fa3a6c Author: taylorotwell Date: Fri Feb 23 15:52:45 2024 +0000 Update facade docblocks commit f7f9b955a8678fe9154a14991361f16c5c46d917 Author: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Fri Feb 23 19:22:10 2024 +0330 [11.x] Add missing methods to `Filesystem` contract (#50213) * add `putFile` to Filesystem contract * add `putFileAs` to Filesystem contract commit 4ebce1e453d6f2c5c23905751f781ec2a6edf0b6 Author: S.a Mahmoudzadeh <36761585+saMahmoudzadeh@users.noreply.github.com> Date: Fri Feb 23 19:08:25 2024 +0330 [10.x] Fix Accepting nullable Parameters, updated doc block, and null pointer exception handling in batchable trait (#50209) * fix(Reachable Trait): fix parameters type and doc block in With withFakeBatch method * fix(Reachable Trait): fix null pointer exception * fix(Batchable Trait): fix parameters type and doc block in With withFakeBatch method * fix(Batchable Trait): fix null pointer exception * Update Batchable.php --------- Co-authored-by: Taylor Otwell commit 759b7cdb2d393505fb878b3a9465003e4390547c Author: Taylor Otwell Date: Thu Feb 22 20:48:16 2024 -0600 remove unnecessary values from config stubs commit 98e7f7cc93f6fc1c7aff0054c7f460a4fe2f140c Author: Taylor Otwell Date: Thu Feb 22 17:04:19 2024 -0600 working on config files commit fb0a8ba01b19730ef5db409249e4ba5137337d12 Author: Taylor Otwell Date: Thu Feb 22 15:17:27 2024 -0600 support customizing event discovery commit ff462c0e96fae9f3d559e7ede72e1f66e2ceb29a Author: Nuno Maduro Date: Thu Feb 22 20:48:48 2024 +0000 Adds event:generate command as does not make sense on l11 skeletons (#50204) commit e0be50a7b770c46b522377cb46318e06e312014e Author: taylorotwell Date: Thu Feb 22 14:52:40 2024 +0000 Update facade docblocks commit ae606ae6004c1ae7dca922fe37ab3f9e517c197f Author: S.a Mahmoudzadeh <36761585+saMahmoudzadeh@users.noreply.github.com> Date: Thu Feb 22 18:22:10 2024 +0330 [10.x] update doc block in PendingProcess.php (#50198) * refactor(PendingProcess): update doc block for start method * refactor(PendingProcess): update doc block for resolveAsynchronousFake method commit 980b1afc28cb3e9db4a80999fa446f6472b770d4 Author: Tomasz Kisiel <49162745+kishieel@users.noreply.github.com> Date: Wed Feb 21 21:11:13 2024 +0100 [11.x] Allows to exclude URIs in PreventRequestsDuringMaintenance middleware (#50127) * [11.x] Allows to exclude URIs in PreventRequestsDuringMaintenance middleware * Fix styles * Adds `flushState` * wording --------- Co-authored-by: Nuno Maduro commit 3ae58da8899d115266ff3ccab1a967a439320e20 Author: Graham Campbell Date: Wed Feb 21 20:09:52 2024 +0000 [11.x] Improve MySQL connect init time by setting all our variables in a single shot (#50044) * Improve MySQL connect init time by setting all our variables in a single * formatting --------- Co-authored-by: Taylor Otwell commit e550f2bf06e4d2f44bf241d2861eea059904976f Author: Graham Campbell Date: Wed Feb 21 19:58:47 2024 +0000 [10.x] Fix optional charset and collation when creating database (#50168) * Fix optional charset and collation when creating database * Update MySqlGrammar.php * Re-use the var i made for this commit a10f61663e4ac6c8bb64c73734531f00d8149fef Author: Nuno Maduro Date: Wed Feb 21 17:20:36 2024 +0000 [11.x] Allows to disable `vendor:publish` updating migrations date (#50162) * Allows to not update the migrations date on publish * fix * formatting * formatting * formatting --------- Co-authored-by: Taylor Otwell commit f7c57c47f677b3fcfa1c70c6c3c51d0f90866d31 Author: Dmytro Kulyk Date: Wed Feb 21 17:19:17 2024 +0200 Added passing loaded relationship to value callback (#50167) commit 597f77eab4943a2bb06408c0eda183954e589adb Author: Francisco Madeira Date: Wed Feb 21 14:18:52 2024 +0000 [11.x] The health route should be loaded even when there is a model binding at the root (#50165) * make sure the "health" route is loaded before the web routes. * style: fixes. commit 500fb9fffcaecd7d8972e5e1f2a6023da7f578ac Author: StyleCI Bot Date: Wed Feb 21 14:18:14 2024 +0000 Apply fixes from StyleCI commit e708043384ab9887cfcb9aa20453f54c0d888855 Author: Brenier Arnaud Date: Wed Feb 21 15:17:53 2024 +0100 [10.x] Arr::select not working when $keys is a string (#50169) * Arr::select not working when $keys is a string Adding a check about $keys type. If it is a string, transform it into an array. If not, it throws a foreach() error * add test --------- Co-authored-by: Taylor Otwell commit 58e42349ba99712f64ad84627588e841c4598981 Author: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Wed Feb 21 17:44:06 2024 +0330 [11.x] Add tests for `make:enum` command (#50184) * Create EnumMakeCommandTest.php * add testItCanGenerateEnumFile * add testItCanGenerateEnumFileWithString * add testItCanGenerateEnumFileWithInt commit abeec173e027cde01f9cd1ac5ca1c485cfbcf20a Author: Adrian Nürnberger Date: Wed Feb 21 15:12:51 2024 +0100 add regression test (#50176) commit 608a6b253e913f36078e8990856e6023b211a1a4 Author: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Wed Feb 21 17:37:58 2024 +0330 remove getDefaultNamespace function in EnumMakeCommand (#50185) commit dcf5d1d722b84ad38a5e053289130b6962f830bd Author: taylorotwell Date: Wed Feb 21 14:07:36 2024 +0000 Update version to v10.45.1 commit 6b98a716f9f66cca9ed43a3edb8fc3fd18997ec3 Author: Taylor Otwell Date: Wed Feb 21 08:07:01 2024 -0600 wip commit 08bf276de0efc9af130e10cb93ede827715543c3 Author: Sjors Ottjes Date: Tue Feb 20 21:17:36 2024 +0100 fix Process::fake() not matching multi line commands (#50164) commit 51134d6802b6193ec4b0a24c45a03b2bae730327 Author: Kuba Szymanowski Date: Tue Feb 20 18:03:56 2024 +0100 Fix typehint for ResetPassword::toMailUsing() (#50163) commit 465668622caef586f047e86247984deedb893314 Author: driesvints Date: Tue Feb 20 16:10:57 2024 +0000 Update CHANGELOG commit 8b08d8cd79f8093eb51a8c59e21647bedfbf05f2 Author: taylorotwell Date: Tue Feb 20 15:32:48 2024 +0000 Update version to v10.45.0 commit 4e19d4e8faf501cedef0bc83afbea1b207e5e375 Author: Dmytro Kulyk Date: Tue Feb 20 17:20:15 2024 +0200 [10.x] Added getQualifiedMorphTypeName to MorphToMany (#50153) It is in addition to qualified names for MorphToMany relations. commit 56bdf4626bc4a3fb6702a9e631e5c1c5b55a4e73 Author: Jonas Staudenmeir Date: Tue Feb 20 16:14:37 2024 +0100 Remove workaround for MariaDB integration tests (#50152) commit 745a5d637b54a8e799275c8112da0be588aadd27 Merge: 738879d58d 0804e5f3d1 Author: Nuno Maduro Date: Tue Feb 20 10:27:44 2024 +0000 Merge branch '10.x' commit 738879d58d03cf14b5ba8957c56bb1cbfd65cdbc Author: Nuno Maduro Date: Mon Feb 19 22:33:06 2024 +0000 Flushes the state of `ValidateCsrfToken` middleware (#50150) commit 7a32ff07805317b889cae8d1dc718e65816ec0e9 Author: Dmytro Morozov Date: Mon Feb 19 17:21:18 2024 +0200 [11.x] Fix conditionally loaded attributes (#50065) * Fix confitionally loaded attributes * Add test commit 343cbd60f1ae05838b29220f4884dcda6a00a52a Author: Sven Luijten <11269635+svenluijten@users.noreply.github.com> Date: Mon Feb 19 16:00:39 2024 +0100 [11.x] Fix undefined key "mysql" in DbCommand (#50147) * Fix undefined key "mysql" in DbCommand * Remove whitespace at end of line commit 0804e5f3d1d806f41c0c39d211482f0586cfacf7 Author: StyleCI Bot Date: Mon Feb 19 14:42:06 2024 +0000 Apply fixes from StyleCI commit 025b24cbc9f487ee88ca43f009123ef0c63b6b94 Author: Italo Date: Mon Feb 19 11:41:45 2024 -0300 [10.x] Adds Tappable and Conditionable to Relation class (#50124) * [10.x] Fixes proxying conditionable and tappable to relation * [10.x] Fixes proxying conditionable and tappable to relation * Update Relation.php --------- Co-authored-by: Taylor Otwell commit b694a01887fc451cb5fd57232b4429dc31dbc2d9 Author: Nuno Maduro Date: Mon Feb 19 14:32:22 2024 +0000 Adds `stopIgnoring` to configuration (#50145) commit 61db49798c7e4597f041600339df5ca575bfc52b Author: Jonas Staudenmeir Date: Mon Feb 19 15:30:51 2024 +0100 Add MariaDB driver (#50146) commit 85b28780401dfd37ca1230a30d39494ce642a0ea Author: Dmytro Kulyk Date: Mon Feb 19 04:05:20 2024 +0200 [10.x] Mark model instanse as not exists on deleting MorphPivot relation. (#50135) * Mark model instanse as not exists on deleting MorphPivot relation. On deleted event, the model is marked as existing. Like in AsPivot::delete() * Update MorphPivot.php commit 09513ac3ee362ff8c1affed4270b4ac8b6439d3b Author: Taylor Otwell Date: Sat Feb 17 09:42:43 2024 -0600 wip commit 296977255e9295cc1c15c85e40d9a71e127a1895 Author: Nuno Maduro Date: Fri Feb 16 21:20:29 2024 +0000 [11.x] Adds `trustProxies` in middleware configuration (#50099) * Adds `trustProxies` in middleware configuration * Apply fixes from StyleCI * Adds `withHeaders` * Apply fixes from StyleCI * Apply fixes from StyleCI * Fixes test suite * formatting --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell commit 91159cbddf95a31da3186255735dca5dfcb64c8b Author: Nuno Maduro Date: Fri Feb 16 17:41:00 2024 +0000 [11.x] Adds `trustHosts` to configuration (#50101) * Adds `trustHosts` to configuration * Apply fixes from StyleCI * formatting --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell commit 2d26eb21dd1b933a2c796969cf7a2c94f4e39e42 Author: Nuno Maduro Date: Fri Feb 16 17:18:38 2024 +0000 [11.x] Adds `convertEmptyStringsToNull` in middleware configuration (#50094) * Improves `convertEmptyStringsToNull` in middleware configuration * Adjusts docs * Apply fixes from StyleCI * Move types file * Apply fixes from StyleCI * Apply fixes from StyleCI --------- Co-authored-by: StyleCI Bot commit f9768bfcf122164aac3cbde5a672ef6127f99135 Author: Nuno Maduro Date: Fri Feb 16 16:24:29 2024 +0000 [11.x] Adds `encryptCookies` to configuration (#50103) * Adds `encryptCookies ` in middleware configuration * Adds missing `flushState` call * formatting --------- Co-authored-by: Taylor Otwell commit cd9a78483d573111b1ab47c03d95fd37f8537eb9 Author: inmanturbo <47095624+inmanturbo@users.noreply.github.com> Date: Fri Feb 16 11:16:27 2024 -0500 [11.x] Removes need to publish queue config in order to configure database queue connection (#50106) * No need to publish config to configure database queue * Update queue.php --------- Co-authored-by: Taylor Otwell commit 8193e12f7b08e426d3b74cbb3a606179af1e8bc7 Author: Mior Muhammad Zaki Date: Sat Feb 17 00:12:02 2024 +0800 [11.x] Remove PHPUnit 9 classes usage (#50107) * [11.x] Remove PHPUnit 9 classes usage. Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki commit cf9513219e80608aded783e33a30f8e8d145f6c2 Author: Dries Vints Date: Fri Feb 16 11:04:27 2024 +0100 Fix warning and deprecation (#50114) commit d5a35ef375cf0969e8d8e5cb0c948dee3471ee07 Author: Graham Campbell Date: Fri Feb 16 09:59:36 2024 +0000 Actually fix fromBase64 return type (#50113) commit 7784c1b8ac17bdd034395eb31e9a03bb6253848e Author: Samson Endale <4sam21@gmail.com> Date: Fri Feb 16 12:44:42 2024 +0300 Minor typo (#50108) commit b9c541a765cc3ebdf963a3d59f2cd16b6c2e6786 Author: Nuno Maduro Date: Thu Feb 15 15:39:31 2024 +0000 [11.x] Improves `trimStrings` in middleware configuration (#50093) * Improves `trimStrings` in middleware configuration * Improves type inference * Apply fixes from StyleCI --------- Co-authored-by: StyleCI Bot commit a606625243443364b5aee036f797f37b6b570978 Author: Noboru Shiroiwa <14008307+nshiro@users.noreply.github.com> Date: Fri Feb 16 00:39:00 2024 +0900 [11.x] Fix appendToGroup method in Middleware.php (#50095) commit 12880db76d6d1d6921b9929e6c2d4257a3c620a2 Author: Joe Dixon Date: Thu Feb 15 15:38:13 2024 +0000 [11.x] Prompts to install Reverb when enabling broadcasting (#50096) * prompt to install reverb * ensure reverb not already installed * formatting --------- Co-authored-by: Taylor Otwell commit 5cd458fb696ead958f7dd158c70172942468261c Author: Joe Dixon Date: Wed Feb 14 17:33:13 2024 +0000 [11.x] Adds Reverb broadcasting driver (#50074) * add reverb broadcasting driver * configure echo for reverb * remove reverb driver commit 0fc01c1a764e7a8b220242f96ca0963f38e9c8d6 Author: Dries Vints Date: Wed Feb 14 16:13:36 2024 +0100 Adjust rules call sequence (#50084) commit cb77035c37dd910eccbca6c9c01bce4597910f38 Merge: a3545a9002 84e7f85769 Author: Dries Vints Date: Wed Feb 14 10:50:45 2024 +0100 Merge branch '10.x' # Conflicts: # CHANGELOG.md # src/Illuminate/Foundation/Application.php # tests/Http/HttpClientTest.php # tests/Support/SupportCollectionTest.php commit 84e7f85769ea268ffec9acab30f7a826223b3d6f Author: Francisco Madeira Date: Tue Feb 13 22:52:00 2024 +0000 [10.x] Add `before` to the `PendingBatch` (#50058) * Add `before` to the `PendingBatch`. * style: fixes * formatting --------- Co-authored-by: Taylor Otwell commit b7ca51d09164674b5eaa0d0ff4273234c279184c Author: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Tue Feb 13 22:55:58 2024 +0100 Allow `Collection::select()` to work on `ArrayAccess` (#50072) commit a3545a90027a42ce72617994dbe3d28ebe4227c8 Author: Nuno Maduro Date: Tue Feb 13 21:53:25 2024 +0000 Moves withProviders to configure method (#50073) commit 1a0c74aa69d3073f374d4de88a6a9886f26a6501 Author: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Wed Feb 14 01:22:37 2024 +0330 update Stringable phpdoc (#50075) commit dc22eb3ce51cc550a68ff934db4a7be6a1eb793c Merge: 033d3a1056 4482ca316f Author: Nuno Maduro Date: Tue Feb 13 20:30:09 2024 +0000 Merge pull request #50076 from jkoop/master [11.x] Update CHANGELOG.md 2023 -> 2024 commit 4482ca316f358ce307d4301d976d55277b7b2263 Author: Joe Koop Date: Tue Feb 13 13:59:18 2024 -0600 update CHANGELOG commit bf4f84be85d23d94c0d7f0f0148a1a0459ecff5b Author: driesvints Date: Tue Feb 13 16:18:15 2024 +0000 Update CHANGELOG commit 1199dbe361787bbe9648131a79f53921b4148cf6 Author: taylorotwell Date: Tue Feb 13 16:01:16 2024 +0000 Update version to v10.44.0 commit af816e5b9d2e9963aafbd2a1081ecad0595aabc6 Author: Oleksandr Prypkhan Date: Tue Feb 13 17:01:42 2024 +0200 [10.x] Fix DB::afterCommit() broken in tests using DatabaseTransactions (#50068) * Add failing test case for test database transactions manager * Fix transaction commit in test causing all callbacks to execute in transaction commit 97e87b64c79813699eebb5145e8f1cc4bc605f4f Author: taylorotwell Date: Tue Feb 13 14:50:39 2024 +0000 Update facade docblocks commit 9f255a7a10bbf067dd779349ba7e47aeae635fb9 Author: Ahmed shamim Date: Tue Feb 13 20:49:50 2024 +0600 [10.x] HTTP retry method can accept array as first param (#50064) * HTTP retry method can accept array as first param * Update PendingRequest.php --------- Co-authored-by: Taylor Otwell commit d3533e175d11ac8777503698f22f235a7ebbc187 Author: MaximAL Date: Tue Feb 13 17:48:28 2024 +0300 Remove regex case insensitivity modifier in UUID detection to speed it up (#50067) commit 67616d54566c49c0f5c681c6caba2ec2867330a3 Author: Marcel Pociot Date: Tue Feb 13 15:47:15 2024 +0100 [10.x] Pass Herd specific env variables to "artisan serve" (#50069) * Add Herd specific env variables * Sort variables commit 033d3a10569674c4f2a2686cad30a47ce2c9ce0a Author: StyleCI Bot Date: Tue Feb 13 10:24:18 2024 +0000 Apply fixes from StyleCI commit 33af530ec9a1abde463e915d8a9f1df17b6d41e8 Merge: 255547315d 52305ed8a8 Author: Nuno Maduro Date: Tue Feb 13 10:23:35 2024 +0000 Merge branch '10.x' commit 255547315d1f7c7a841bb1939ae39a4a50bd96c0 Author: Hafez Divandari Date: Mon Feb 12 20:45:00 2024 +0330 [11.x] Non-default schema names (#50019) * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * fix rename table on sqlsrv * wip * wip * fix rename column and rename index on sqlsrv * fix drop primary on pgsql * wip * wip * wip * wip * wip * wip * enable prefix tests * wip * wip * fix prefix auto-increment starting value on pgsql * use blueprint prefix on sqlite * use wrapTable where ever possible * wip * wip * fix index name with schema name+prefix * fix schema name+prefix * wip * fix wrapping table * fix auto-increment starting value on pgsql * wip * fix tests commit 52305ed8a857b4e0e729c68455d60a608d1a75d3 Author: Guillaume Stoehr Date: Mon Feb 12 17:56:59 2024 +0100 [10.x] fix Validator::validated get nullable array (#50056) * fix Validator::validated get nullable array * Update Validator.php --------- Co-authored-by: Taylor Otwell commit a5f53e5ea8244baf4a9496f26fa22843d7aec6ac Author: Saeed Hosseini <39903221+saeedhosseiinii@users.noreply.github.com> Date: Mon Feb 12 20:24:51 2024 +0330 [11.x] Add assertion for check provider added to provider.php (#50049) * [11.x] Add test for check provider added to file when use make command * fix style commit c3f2a05fd990218cc12b95b9947adf8624fd3959 Author: Nuno Maduro Date: Mon Feb 12 16:48:04 2024 +0000 Fixes default maintenance driver on config stub (#50052) commit 1d2247f95fa6107bbc961acfa052684f29140e85 Author: Nuno Maduro Date: Mon Feb 12 16:47:38 2024 +0000 Improves `once` related tests so they never fail (#50053) commit 7ff37c6403e831482666b149892e02162577f7a5 Author: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Sun Feb 11 22:31:24 2024 +0400 Update reserved names in GeneratorCommand (#50043) Co-authored-by: Xurshudyan commit 67a0e4716ab3cbf92528a41a8515dfb8d8bbaa37 Author: Eliezer Margareten <46111162+emargareten@users.noreply.github.com> Date: Fri Feb 9 17:58:04 2024 -0500 [10.x] Add ScopedBy attribute for models (#50034) * [10.x] Introduce ScopedBy attribute for models * Update HasGlobalScopes.php commit c33547bf6b179bc9c40741be8d5c4d35438b66c9 Author: Ryan Chandler Date: Fri Feb 9 17:52:44 2024 +0000 [11.x] Introduce new `make:enum` command (#50016) * Foundation/Console: Introduce new make:enum command * formatting --------- Co-authored-by: Taylor Otwell commit 9eaffb5468756d5b5e2b3c7892c91df92747e31a Author: Taylor Otwell Date: Fri Feb 9 11:42:27 2024 -0600 make trait command commit 9c3876ce46f4d8b1d5bd5016d35b252ea3e083cb Author: Saeed Hosseini <39903221+saeedhosseiinii@users.noreply.github.com> Date: Fri Feb 9 21:07:13 2024 +0330 [11.x] Add priority config to configuration middleware (#49989) * [11.x] Add priority config to configuration middleware * formatting * formatting --------- Co-authored-by: Taylor Otwell commit 4650d20560c04b8963b414c9c41a6bde93c2ad46 Author: Saeed Hosseini <39903221+saeedhosseiinii@users.noreply.github.com> Date: Fri Feb 9 20:47:15 2024 +0330 [11.x] Remove unused property (#50031) commit 6c9cbc9ba0437bcfe54609ae0af312feefc7e309 Author: Eliezer Margareten <46111162+emargareten@users.noreply.github.com> Date: Fri Feb 9 19:16:23 2024 +0200 [10.x] Introduce Observe attribute for models (#49843) * Introduce Observe attribute for models * fix tests * fix styling * rename file --------- Co-authored-by: Taylor Otwell commit 250959d57b36d40187c50e12bea5f942d49afb86 Author: Taylor Otwell Date: Fri Feb 9 10:55:58 2024 -0600 Formatting commit 7de2fe3f05fb205abd4f59ace74dc7b3f58b77db Merge: ae19aa24c5 644e672a8f Author: Taylor Otwell Date: Fri Feb 9 10:36:52 2024 -0600 Merge branch 'master' of https://github.com/RayDabbah/framework into RayDabbah-master commit ae19aa24c5ee8b191ce545967305b6b1d280ffb9 Merge: 588bcbe252 6176ca74ae Author: Taylor Otwell Date: Fri Feb 9 10:36:27 2024 -0600 Merge branch 'master' of github.com:laravel/framework commit 6176ca74ae1253f5b01b94b3e56c0fe4ad5811ea Author: Günther Debrauwer Date: Fri Feb 9 17:32:19 2024 +0100 [11.x] Allow an update query to have subqueries as values (#50030) * Allow update with builders as values * Small fix * Fix preparing bindings commit 856d1bf7ad4381afff23f84d36177f9c3b035998 Author: Jeremias Wolff Date: Fri Feb 9 17:25:46 2024 +0100 [10.x] Improved Handling of Empty Component Slots with HTML Comments or Line Breaks (#49966) * implement alternative solution for PR#49935 * style ci * style ci * formatting --------- Co-authored-by: Taylor Otwell commit 0a8c91bdc9efeb22b1e2959f19cfaec6b78556dc Author: Ryan Chandler Date: Fri Feb 9 15:56:19 2024 +0000 [10.x] Introduce new `Arr::take()` helper (#50015) * Support/Arr: Introduce new Arr::limit() helper * Arr: Rename limit() to take() for consistency commit 24d0b5bd4efc52c1b093598a50d25c844913d81e Author: lorenzolosa <11164571+lorenzolosa@users.noreply.github.com> Date: Fri Feb 9 10:11:01 2024 -0500 [10.x] Type hinting for conditional validation rules (#50017) * Type hinting for conditional validation rules * change types order commit 6821aa10ba0ee4697a336452a417a8f64205dd74 Author: Mior Muhammad Zaki Date: Fri Feb 9 23:09:37 2024 +0800 [10.x] Fixes missing `Throwable` import and handle if `originalExceptionHandler` or `originalDeprecationHandler` property isn't used by alternative TestCase (#50021) * [10.x] Fixes missing `Throwable` import. Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot commit e18ca8ffe4f3956c51487d53216c2865ab47f0b7 Author: Mior Muhammad Zaki Date: Fri Feb 9 23:09:16 2024 +0800 [10.x] Allows to defer resolving pcntl only if it's available (#50024) * [10.x] Allows to defer resolving pcntl only if it's available fixes #50022 Signed-off-by: Mior Muhammad Zaki * wip --------- Signed-off-by: Mior Muhammad Zaki commit de458584e024f41722fed91d59de6180842033e2 Author: Ralf Langebrake Date: Fri Feb 9 08:23:16 2024 +0100 Fix Octane health up defined check (#50018) commit a507654eadba1682148a41c3458a5c5ab6a85a7f Author: StyleCI Bot Date: Fri Feb 9 02:11:25 2024 +0000 Apply fixes from StyleCI commit f482d53367d9c0a7fc63f2caa611666ddb5db143 Merge: f17a206282 1d0f002172 Author: Mior Muhammad Zaki Date: Fri Feb 9 10:10:44 2024 +0800 Merge branch '10.x' Signed-off-by: Mior Muhammad Zaki commit 644e672a8f0f30a3d272c937c99c05768d8b7ac6 Author: RayDabbah Date: Fri Feb 9 00:02:32 2024 +0000 Update facade docblocks commit 17d5c440c1bc6b2dcd67facd62a86a05e7978094 Author: raydabbah Date: Tue Feb 6 14:15:34 2024 -0500 send mail synchronously even if it implements ShouldQueue commit 1d0f002172d1570389e72fb474d16a6996fc943d Author: Mark Townsend Date: Thu Feb 8 09:54:49 2024 -0600 [10.x] Add `toBase64()` and `fromBase64()` methods to Stringable and Str classes (#49984) * Add toBase64 and fromBase64 methods to Stringable * Update SupportStringableTest.php * Call __toString * Styleci * More styleci shenannys * Adjust test * Add toBase64 & fromBase64 to Str class + tests * Styleci fix * Update Stringable.php * Update Str.php --------- Co-authored-by: Taylor Otwell commit 7fac93b005053fb497df2d11f9d3c8ada4f1a221 Author: Mior Muhammad Zaki Date: Thu Feb 8 23:42:39 2024 +0800 Allows Setup and Teardown actions to be reused in alternative TestCase for Laravel (#49973) * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * formatting --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell commit f17a206282544fe35dd7a455d4e17fc3fbd805f0 Author: Hafez Divandari Date: Thu Feb 8 19:05:34 2024 +0330 [11.x] Add support for specifying schema name on SQL Server (#49965) * fix parsing schema name on sqlsrv and pgsql * fix tests * fix tests commit d4390fe2a937f0094bbcecbc0730477f10de1811 Author: Jaanus Vapper Date: Thu Feb 8 17:28:20 2024 +0200 DB command: add sqlcmd -C flag when 'trust_server_certificate' is set (#49952) commit 0c830e7a70b01ba6149c1795b77f1d32eef8d624 Author: Kyryll Kovalenko Date: Thu Feb 8 16:10:07 2024 +0100 [10.x] Fix in appendExceptionToException method exception type check (#49958) * Fix in appendExceptionToException method exception type check * Update TestResponse.php --------- Co-authored-by: Taylor Otwell commit b474a36bd1b72e33894c6abfb47cc4a2c941bb96 Author: Rijoanul Hasan Shanto Date: Thu Feb 8 20:52:52 2024 +0600 Added setAbly() method for AblyBroadcaster (#49981) commit bad4256e9155b8f2a14bd6d8e1a2ccf32b772ad8 Author: Maarten Buis Date: Thu Feb 8 15:52:35 2024 +0100 [11.x] Allow `withMiddleware` without callback in ApplicationBuilder class (#49982) commit 588bcbe252ce985f3772ecaa9feebf6f4a8cf657 Merge: 75996c833b 0ac72f6597 Author: Taylor Otwell Date: Thu Feb 8 08:49:04 2024 -0600 Merge branch 'master' of github.com:laravel/framework commit 0ac72f65974e523a9734581856f107b483da0c4f Author: Jānis Puriņš-Biezais Date: Thu Feb 8 16:48:53 2024 +0200 [11.x] Allow withRouting with just pages, health and then callback (#49985) commit f84d681a41bb6d01b403fc68a1b1f37da846b972 Author: Daniele Faraglia Date: Thu Feb 8 14:47:17 2024 +0000 fix the phpdoc for replaceMatches in Str and Stringable helpers (#49990) commit a559f76fd36276c23afdca9513865209f27c2ca3 Author: Dries Vints Date: Thu Feb 8 15:45:01 2024 +0100 Fix Octane health up issue (#50012) commit a98b0219e125a0fe463104260f77141192ce6641 Author: Taylor Otwell Date: Thu Feb 8 15:42:42 2024 +0100 Revert "[10.x] fix Before/After validation rules (#49871)" (#50013) This reverts commit cc653b080465072df501a313171acc8fc9615cad. commit d02e0fe03406ff2f56e781cd14630528c223eb84 Author: Mior Muhammad Zaki Date: Thu Feb 8 22:41:32 2024 +0800 [11.x] Check `0001_01_01_000002_create_jobs_table.php` before publishing `job_batches` migration (#49998) * [11.x] Check `0001_01_01_000002_create_jobs_table.php` before publishing `job_batches` migration Signed-off-by: Mior Muhammad Zaki * wip --------- Signed-off-by: Mior Muhammad Zaki commit 02f181014233229bee6e4768725a13786ad927f0 Merge: fa1e70a74c 9448e01699 Author: Dries Vints Date: Thu Feb 8 15:32:11 2024 +0100 Merge branch '10.x' # Conflicts: # composer.json commit 75996c833b55b4de3cd55de5d3b86d1ca86a8d98 Author: Taylor Otwell Date: Wed Feb 7 11:43:14 2024 +0100 add middleware helper method commit fa1e70a74c202d15db8a856eca1d3d17a93350d6 Author: Taylor Otwell Date: Mon Feb 5 12:29:33 2024 +0100 wip commit 4c59d08936ce15476d259226fd82ae47bc2edce4 Merge: 15548bbae0 ec707f96e3 Author: Nuno Maduro Date: Mon Feb 5 10:37:17 2024 +0000 Merge pull request #49978 from me-shaon/remove-unused-import [11.x] Remove unsued import for 'Once' class commit ec707f96e3a85221b5d398ddb7486d5f0bfb16b2 Author: Ahmed shamim Date: Mon Feb 5 16:09:40 2024 +0600 remove unsued import for 'Once' class commit 9448e01699d43761e8bcf3d2ff53d0e3efb37432 Author: Nuno Maduro Date: Mon Feb 5 01:48:05 2024 +0000 Adds PHPUnit 11 as conflict (#49957) commit e0dbf49e06530b8417d18fbeaf8f4f581a0fe724 Author: Mateus Junges Date: Sun Feb 4 22:46:54 2024 -0300 Update call to get grammar (#49972) commit 15548bbae0d0cdb94f6d6ccbc81aa349a025abe8 Author: Nuno Maduro Date: Mon Feb 5 00:35:44 2024 +0000 Fixes test suite commit ccc568238e21a6054de00e2e1f6bbf74d439ad81 Author: Taylor Otwell Date: Sun Feb 4 16:20:35 2024 +0100 fix redirect methods commit 836e7eb538903025eb13eff07b2c4920a03659ca Author: Stephen Rees-Carter Date: Sun Feb 4 11:30:04 2024 +0100 Add missing Encrypter methods from PR #49962 (#49964) commit c3ac147923dcaff6603c161671ba7c112e426a8b Author: Taylor Otwell Date: Fri Feb 2 21:26:32 2024 -0600 update behavior commit 25d16dac61fec8df2a2bd88ec3f8c3168fbf4926 Author: taylorotwell Date: Sat Feb 3 03:22:37 2024 +0000 Update facade docblocks commit 01480fadd3358c99c847405209cfd3593c5cc2ba Author: Taylor Otwell Date: Fri Feb 2 21:21:17 2024 -0600 add accessor commit ba315a97d817c7854b3b0afde1d85e01052cb15a Author: Taylor Otwell Date: Fri Feb 2 21:05:20 2024 -0600 encrypted attributes that are set should always be considered changed due to rotatable keys commit defd5d4bb5423b4a2399a2c1539097928067c68c Author: Taylor Otwell Date: Fri Feb 2 20:08:47 2024 -0600 explode by default commit 254d5430f2226f8397b6b478bdc908cceee7d78e Author: taylorotwell Date: Sat Feb 3 02:00:55 2024 +0000 Update facade docblocks commit 41d1f6015214686210463a373b7823c41e806030 Author: Taylor Otwell Date: Fri Feb 2 20:00:24 2024 -0600 Encryption (#49962) This PR allows you to specify multiple previous_keys which are old encryption keys you are rotating out. All newly encrypted strings will be encrypted using the app.key like normal. On decryption, the current key will be tried first. If that key is not able to decrypt the data, decryption will be attempted with previous keys. Of course, if all keys fail to decrypt the data, the process will fail like normal. commit 4346406d58c0750f501e91f2a8cc9443fc316eef Author: Nuno Maduro Date: Sat Feb 3 00:27:30 2024 +0000 [11.x] Fixes PHPUnit 11 support (#49959) * Fixes PHPUnit 11 support * Apply fixes from StyleCI * Fully reverts to changes like before * Removes non used phpstan error * Fixes test suite --------- Co-authored-by: StyleCI Bot commit 1d89f34d4cda9cc6c4c5562f0ca51bd046c4e49d Author: StyleCI Bot Date: Fri Feb 2 20:26:17 2024 +0000 Apply fixes from StyleCI commit b053cb765ca956a4645b602d47343ceaa13d477c Author: Taylor Otwell Date: Fri Feb 2 12:55:09 2024 -0600 prompt for render or report commit 1e47bb92d0b539711cf5d84083c94789ae07677b Author: Taylor Otwell Date: Fri Feb 2 12:31:10 2024 -0600 fix method commit 9cea448ebc32dedf16e0e704e5de78f86bbb6eb7 Author: Taylor Otwell Date: Fri Feb 2 12:25:31 2024 -0600 rename methods commit 9599189710ee218ce771186048d4300f6adb6324 Author: Taylor Otwell Date: Fri Feb 2 12:16:44 2024 -0600 helper methods for csrf, trim strings, and sigs commit c4f46abce0e69737de35b71439943ac2a24d6ec3 Author: Adrien Foulon <6115458+Tofandel@users.noreply.github.com> Date: Fri Feb 2 17:51:07 2024 +0100 [10.x] Add POSIX compliant cleanup to artisan serve (#49943) * Add POSIX compliant cleanup to php artisan serve Fixes #49941 * Style CI * Move cleanup to a better location * Use built in server abstraction * Update ServeCommand.php --------- Co-authored-by: Taylor Otwell commit e83ee9b8c875df38471d0ec35394c934f8a5e9da Merge: 432f4bb45c 3734ae98a6 Author: Nuno Maduro Date: Fri Feb 2 15:20:10 2024 +0000 Merge branch '10.x' commit 3734ae98a6048c954eec960b46127fb8b9a32bad Author: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Thu Feb 1 21:47:19 2024 +0330 Update UrlGenerator.php (#49944) commit 6ded1b813de45f5663f6f6526d622ebe947a70b5 Author: Dries Vints Date: Thu Feb 1 16:32:22 2024 +0100 Fix releases workflow commit 432f4bb45c32507026bf11f3a2ee2525acbf9436 Author: Kyle Date: Thu Feb 1 14:20:20 2024 +0100 Remove Carbon beta-step now that 3.0 is released as stable (#49940) commit 4d4a76031a8a0f6e33439ffc4edbaadd67970ea6 Author: Craig Morris Date: Thu Feb 1 10:23:24 2024 +1100 [10.x] Add Collection::select() method (#49845) * Add select method to Collection * style * Update Collection.php * Update LazyCollection.php --------- Co-authored-by: Craig Morris <> Co-authored-by: Taylor Otwell commit 8206d96832524c0fc69f4d29bfd6e99dbebc4c80 Author: Dries Vints Date: Wed Jan 31 18:04:49 2024 +0100 Fix empty request for HTTP connection exception (#49924) commit 4c81886f3144d7ac02caaa40908cbd4ca681f195 Author: Hafez Divandari Date: Wed Jan 31 20:25:06 2024 +0330 [11.x] Fix modifying auto-increment columns (#49925) * fix auto-increment handling * fix tests * fix tests * fix tests * fix tests * formatting * fix tests * fix tests * fix tests commit 78e8ff40499883bef4403f0538a6868b710ee4c7 Author: Taylor Otwell Date: Tue Jan 30 13:31:14 2024 -0600 fix spacing commit d65ea42dbec9137b94c2793b5fe06e75e8998764 Author: driesvints Date: Tue Jan 30 16:34:59 2024 +0000 Update CHANGELOG commit 4f7802dfc9993cb57cf69615491ce1a7eb2e9529 Author: Dries Vints Date: Tue Jan 30 17:25:02 2024 +0100 wip commit 5a82302b063296f981cd1e77877f931712cf06a3 Author: Dries Vints Date: Tue Jan 30 17:21:10 2024 +0100 wip commit a2475b17e480e53ac281d0687735fe4e09dd8681 Author: Dries Vints Date: Tue Jan 30 17:18:13 2024 +0100 wip commit 95b6db5b3e267fa6ed485d5fbf7a888e2b3ee0d9 Author: Dries Vints Date: Tue Jan 30 17:09:01 2024 +0100 wip commit 041b733ff662a42c29d4d0b48307cd3d9f4127a6 Author: Taylor Otwell Date: Tue Jan 30 16:07:50 2024 +0000 Update version to v10.43.0 commit 25c4323227e2248a7be5e8bc2dcc74d59015f48c Author: Dries Vints Date: Tue Jan 30 17:06:29 2024 +0100 wip commit cc653b080465072df501a313171acc8fc9615cad Author: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com> Date: Tue Jan 30 21:34:33 2024 +0530 [10.x] fix Before/After validation rules (#49871) * added checks for 2nd date in compareDates * Add validation tests for date comparison * formatting --------- Co-authored-by: Taylor Otwell commit d48d28ea9a20de090b7fa42faddc1880a4864883 Author: Dries Vints Date: Tue Jan 30 16:57:28 2024 +0100 Update Application.php commit fc87e13c8e42190488539f084724452a9aa43473 Author: Taylor Otwell Date: Tue Jan 30 15:56:20 2024 +0000 Update version to v commit 41bd2c15386b80ecc1f8da8419d95dcd18adc49e Author: Victor Schedlyn GUTT <39224143+VicGUTT@users.noreply.github.com> Date: Tue Jan 30 16:55:48 2024 +0100 [10.x] Fix - The `Translator` may incorrectly report the locale of a missing translation key (#49900) * fix: "Translator" returns correct locale for missing keys * chore: variable rename commit 85f06e8b418dbd7eddb43d990b663ca455dfb071 Author: Dries Vints Date: Tue Jan 30 16:52:08 2024 +0100 wip commit fab8b136938611449d9f5bc40359cac8d4bf1e90 Author: jagers <7496717+jagers@users.noreply.github.com> Date: Tue Jan 30 07:48:38 2024 -0800 Fix redis tag cache ttls in past no getting flushed (#49864) Co-authored-by: Jack Gersten commit b1ebf4f8993e05323b195936004f60f3a998d054 Author: Tobias Petry Date: Tue Jan 30 16:46:52 2024 +0100 [10.x] Fix expressions in with-functions doing aggregates (#49912) * support expression in with-functions doing aggregates * apply Laravel docblock rules * typo commit a4dfd9f5c70c8d7d1f2979640f2fbc3b6af94b9f Author: D. Nagy Gergő Date: Tue Jan 30 16:45:44 2024 +0100 [10.x] Fix LazilyRefreshDatabase when testing artisan commands (#49914) commit a80ed25b9ea57b59825a3a82b1b1f5cf6aa00175 Merge: a5bf1bc0b5 af2a7caa70 Author: Nuno Maduro Date: Tue Jan 30 10:25:32 2024 +0000 Merge branch '10.x' commit af2a7caa70ebe2d50c3aff8c222222e30cd83d12 Author: Lito Date: Tue Jan 30 04:11:34 2024 +0100 [10.x] Using the native fopen exception in LockableFile.php (#49895) * [10.x] Using the native fopen exception in LockableFile.php * Removed unused Exception. commit a5bf1bc0b56fa724789fa3084b916279f74bee7a Author: Hafez Divandari Date: Tue Jan 30 06:37:53 2024 +0330 [11.x] Fix modifying columns with default value and other minor schema enhancements (#49897) * add support for srid on mariadb * fix modifying a column with default * fix renaming a column with default on lagacy mysql * use getTableListing * fix phpdoc on column definition * use getServerVersion * formatting commit bc753cfdf139398350e2ce9a30164262cb3dda3e Author: Taylor Otwell Date: Mon Jan 29 17:18:40 2024 -0600 Remove base controller if it doesn't exist (#49902) * remove base controller if it doesn't exist * Apply fixes from StyleCI --------- Co-authored-by: StyleCI Bot commit b63a4e6cc435c6f807f504051099c476c699830c Author: Nuno Maduro Date: Mon Jan 29 19:11:34 2024 +0000 Fixes missing import on exceptions class (#49901) commit b2551ee0fb3d5523ec81b15b1fdc79b760fecc7b Author: Taylor Otwell Date: Mon Jan 29 12:56:30 2024 -0600 add support for arrays commit e1c1ec625b7f1555f55549f05d6b46e98928780b Author: Nuno Maduro Date: Mon Jan 29 16:56:41 2024 +0000 [11.x] Fixes `dd` when content has empty lines (#49898) * Fixes `dd` when content has empty lines * Apply fixes from StyleCI --------- Co-authored-by: StyleCI Bot commit 784d076340d58fd584b1602955e9474a4834a8ca Author: taylorotwell Date: Mon Jan 29 14:56:21 2024 +0000 Update facade docblocks commit e9c67dbd1b98def11ff6c2be843499d5d828af40 Author: Peter Elmered Date: Mon Jan 29 15:55:52 2024 +0100 [10.x] Add support for streamed JSON Response (#49873) * Add support for streamed JSON Response * Add test helper and tests for streamed JSON Responses * Remove unused imports * Remove unused method * Remove unused imports * formatting --------- Co-authored-by: Taylor Otwell commit 334a2173a1e9fdeb861d55638f18bb54297455aa Author: Calvin Jackson <69092766+LogicSatinn@users.noreply.github.com> Date: Mon Jan 29 17:41:50 2024 +0300 [10.x] Allow brick/math 0.12 (#49883) * Allow brick/math version 0.12 * Allow brick/math version 0.12 in database and validation * Revert "Allow brick/math version 0.12 in database and validation" This reverts commit ef5159e070f692edb300bf9c298b198c754c9ed3. * Allow brick/math version 0.12 in database and validation commit 0049bc2da8424e21298bf6ccb9bc54401c847598 Author: Eliezer Margareten <46111162+emargareten@users.noreply.github.com> Date: Mon Jan 29 16:34:55 2024 +0200 [10.x] add addGlobalScopes method (#49880) commit a050fb5a80690f5623f853d0997166fbf02b4707 Author: StyleCI Bot Date: Mon Jan 29 14:34:08 2024 +0000 Apply fixes from StyleCI commit 4fa777d1d8b18f2b9b56e45d03c128f8591a3b99 Author: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Mon Jan 29 09:33:48 2024 -0500 [10.x] Adds `FormRequest@getRules()` method (#49860) * adds getRules method * revert style changes * revert style changes * revert style changes * revert style changes * formatting --------- Co-authored-by: Taylor Otwell commit 706a7d2160d55c676a234a6c5852abf454e8621e Author: foremtehan <53290883+foremtehan@users.noreply.github.com> Date: Mon Jan 29 17:56:07 2024 +0330 Update README.md (#49878) * Update README.md * Update README.md --------- Co-authored-by: Taylor Otwell commit d410d61f90f4aafde972b1279abe88e113c286f5 Author: Mateus Junges Date: Mon Jan 29 11:21:32 2024 -0300 [10.x] Fix validation message used for max file size (#49879) * Use correct validation message * Add tests * Apply styleci fixes * Update src/Illuminate/Validation/Concerns/FormatsMessages.php * Use symfony file to get attribute type * Apply fixes from styleci --------- Co-authored-by: Dries Vints commit fb9d508f62690dbbfc44150dd2831c4bc1412f83 Author: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com> Date: Fri Jan 26 23:50:54 2024 +0530 Refactor password validation rules (#49861) commit 50c96bdb5e8daf0ef8269e1508dcb870a5e21c16 Author: Nuno Maduro Date: Fri Jan 26 18:19:41 2024 +0000 [11.x] Improves `make:test` command (#49862) * Infers testing framework * Apply fixes from StyleCI * Detects testing framework on `handleTestCreation` * Apply fixes from StyleCI * formatting --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell commit 5d7fdda2f2b1cee4133606a849e131f8d9bff733 Author: Nuno Maduro Date: Fri Jan 26 16:13:36 2024 +0000 Fixes test suite commit f0758bce5747d627f4b6ef3dd3dca1aa1cbf7fa7 Author: Nuno Maduro Date: Fri Jan 26 15:25:36 2024 +0000 Uses Carbon `^3.0` instead of `^3.0.0-beta.3@beta` (#49857) commit abc4c985d52f34a65ed1a0af8d97ad6a9a2fadd6 Author: taylorotwell Date: Fri Jan 26 15:04:16 2024 +0000 Update facade docblocks commit c9de94a0146a758214b994206165814c557cc9b7 Author: Pascal Baljet Date: Fri Jan 26 16:03:45 2024 +0100 [10.x] Introducing `beforeStartingTransaction` callback and use it in `LazilyRefreshDatabase` (#49853) * Introduced `beforeStartingTransaction` on `Connection` * Use new `beforeStartingTransaction ` in `LazilyRefreshDatabase` * Fix formatting * formatting --------- Co-authored-by: Taylor Otwell commit b715f4672e678d4dae6fd979f085cc01b2ebd5d1 Author: Kyle Date: Fri Jan 26 13:47:05 2024 +0100 [11.x] Allow Carbon 2 and 3 (#49847) * Revert "Revert "[11.x] Upgrade to Carbon v3 (#49764)" (#49811)" This reverts commit 7dcd191721ce0a996ed7e8803efdfa7810983483. * Allow both Carbon 2 and 3 * Test Carbon 3 with GitHub Actions * Update composer.json * Update src/Illuminate/Support/composer.json --------- Co-authored-by: Dries Vints commit 7a567c41c671362c8b1a3fb55341c1237784b89c Author: Lito Date: Fri Jan 26 12:44:00 2024 +0100 [10.x] Unified Pivot and Model Doc Block `$guarded` (#49851) If the behavior of `$guarded` in Pivot is the same as in Model, they must have the same Doc Block. commit dbb3fd9a796d71ead4df13d9c5dc79a2c256232f Merge: b2ffcc165a 3bbb50ea7e Author: Dries Vints Date: Fri Jan 26 10:28:25 2024 +0100 Merge branch '10.x' # Conflicts: # .github/workflows/tests.yml # CHANGELOG.md # src/Illuminate/Database/DBAL/TimestampType.php # src/Illuminate/Foundation/Application.php # tests/Database/DatabaseSchemaBuilderIntegrationTest.php commit 3bbb50ea7e4d25a6f755749bb69fd545f19a7f34 Author: Dries Vints Date: Fri Jan 26 10:25:35 2024 +0100 Add MariaDb1060Platform (#49848) commit b2ffcc165a386cbd8996f84926c8961f38f5d7ba Author: Dries Vints Date: Fri Jan 26 09:40:26 2024 +0100 Add Resend library to require-dev commit 7802fd9bbf81ad9a3d5d0250662d595129a1f9fa Author: Taylor Otwell Date: Thu Jan 25 19:41:15 2024 -0600 add to suggest commit bb272bad6f94da6a05ae15c2c50df01ea07867e9 Author: Noboru Shiroiwa <14008307+nshiro@users.noreply.github.com> Date: Fri Jan 26 10:40:02 2024 +0900 [11.x] Fix docblock and property initialization (#49844) commit d293d99709d076c5667462a0b5f6e76ef52c65a4 Author: Taylor Otwell Date: Thu Jan 25 17:47:23 2024 -0600 wip commit 779d2bb14a252496e1226b4aed2c654eec049412 Author: Taylor Otwell Date: Thu Jan 25 17:46:31 2024 -0600 wip commit 1729b20389986700a9452166939231a6cc77cbd7 Author: Taylor Otwell Date: Thu Jan 25 13:52:03 2024 -0600 throw transport exception commit 4e4bf1772e6e02aee679671e77533b7b9fd834be Merge: bdd3ffdbaa 8aeb7bec31 Author: Taylor Otwell Date: Thu Jan 25 13:43:35 2024 -0600 Merge branch 'resend' commit 8aeb7bec31a71d16d85b42a4bbbf3865092cce8a Author: Taylor Otwell Date: Thu Jan 25 13:43:29 2024 -0600 resend support commit bdd3ffdbaa01a741c5369cf61ff3153ca7b1c7fd Author: Alex Bouma Date: Thu Jan 25 19:50:57 2024 +0100 [11.x] Expose queue name and delay on job queue events (#49837) * Expose queue name and delay on job queue events * formatting * fix type --------- Co-authored-by: Taylor Otwell commit 4ecc1da5075087ebc98a0f7444c0f4a2d694de70 Author: Dries Vints Date: Thu Jan 25 19:34:44 2024 +0100 [10.x] Release action (#49838) * Release action v1 * Release action v2 * Revert "Release action v2" This reverts commit dba605e9ea2dfe675d6fc34e8eb6ab5abbc2c40e. * wip commit 9e88fe502c6579e9ef103708e8effd3930d41392 Author: Hafez Divandari Date: Thu Jan 25 21:09:41 2024 +0330 make `hasIndex` order-sensative (#49840) commit d79ef09fb8b597ebe3719850fd0261ff17ff91bb Author: Trevor Morris Date: Thu Jan 25 17:00:28 2024 +0000 [10.x] Add `insertOrIgnoreUsing` for Eloquent (#49827) * fix: add insertOrIgnoreUsing method * test: add tests for insertOrIgnoreUsing * test: fix misspelled ignore using methods * test: fix insert or ignore argument count and mock getDatabaseName * test: fix sql quotes for mysql * test: fix binding for mysql insert or ignore using commit fdef48d79a7eb8d60bbf61f06fba7138d93dc900 Author: Julius Kiekbusch Date: Thu Jan 25 11:14:39 2024 +0100 Remove duplicate Checkout (#49828) commit 5115dd8517d64fc96411f2813594e3aa521287b9 Author: Taylor Otwell Date: Wed Jan 24 15:14:54 2024 -0600 add files commit e0d55371c5958d150402347e20ae6895b040a5df Author: Taylor Otwell Date: Wed Jan 24 15:14:48 2024 -0600 simple health endpoint commit 6d4bbb5ee71e85d49d6857f9541fbcdc5ecd869d Author: StyleCI Bot Date: Wed Jan 24 20:06:27 2024 +0000 Apply fixes from StyleCI commit 2080a6cfc751ce6650b74ca69aeaa65e8c1810a4 Author: Taylor Otwell Date: Wed Jan 24 14:06:07 2024 -0600 class make and interface make commit 227b9291dbdab8d7b64f42db17249da9f5b3cf3f Author: Taylor Otwell Date: Wed Jan 24 13:12:24 2024 -0600 add registered method commit a9c916c290b50d4e7ba2fdda2bfc565e63a62b69 Author: Taylor Otwell Date: Wed Jan 24 12:54:52 2024 -0600 add note to generated provider file commit 4187b06be65d17caf3fb9176e77fd1a5cf2d9f5a Author: Hafez Divandari Date: Wed Jan 24 20:46:06 2024 +0330 [11.x] Remove Database Deprecations (#49810) * remove schema deprecations * formatting * force re-run tests commit 4247b594924bc84ec37c5b0a9f0feb92d3de299b Author: Julio J Date: Wed Jan 24 14:15:21 2024 -0300 Check properties on mailables are initialized before sharing with the view (#49813) commit 5f29723a30d0141f2de1b3eb25aa7632a820e829 Author: Mateus Junges Date: Wed Jan 24 14:14:45 2024 -0300 [10.x] Do not touch `BelongsToMany` relation when using `withoutTouching` (#49798) * Do not touch relation when ignore touching is enabled * Add tests * Apply styleci fixes commit 7dcd191721ce0a996ed7e8803efdfa7810983483 Author: Nuno Maduro Date: Wed Jan 24 16:51:42 2024 +0000 Revert "[11.x] Upgrade to Carbon v3 (#49764)" (#49811) This reverts commit 1c56736552d0ae1f6c756aa6000063e6e46aeb1e. commit 1c56736552d0ae1f6c756aa6000063e6e46aeb1e Author: Kyle Date: Wed Jan 24 16:02:30 2024 +0100 [11.x] Upgrade to Carbon v3 (#49764) * Upgrade to Carbon v3 * Use float for Carbon ->total* properties * Make createFromId accepts Uuid and Ulid as per its doc * Update nesbot/carbon to ^3@dev in illuminate/support Signed-off-by: Mior Muhammad Zaki * Upgrade to nesbot/carbon ^3.0.0-beta.3@beta --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Mior Muhammad Zaki commit 43f31a109f904ce06c9643cc6097c9bbafd470fe Author: Nuno Maduro Date: Wed Jan 24 15:01:22 2024 +0000 [11.x] Adjusts default mail environment variables (#49809) * Adjust default email values * Adjusts MAIL_PORT commit cad642e9632b5dd2e400c26842763c3758860710 Author: taylorotwell Date: Wed Jan 24 14:42:01 2024 +0000 Update facade docblocks commit 8fd5c3586c3b76c716c33b20df5b4851eeca6f0f Author: Hafez Divandari Date: Wed Jan 24 18:11:29 2024 +0330 [10.x] Add `hasIndex()` and minor Schema enhancements (#49796) * add hasIndex, getIndexListing and getTableListing * minor schema enhancements * fix tests * Update Builder.php --------- Co-authored-by: Taylor Otwell commit caee0bd8542f8324b2847b1773b8efb00185be61 Author: Eliezer Margareten <46111162+emargareten@users.noreply.github.com> Date: Wed Jan 24 16:33:05 2024 +0200 class-name string argument for global scopes (#49802) commit 740543ce549f0ed270b12c930f61519aa5111dac Author: taylorotwell Date: Wed Jan 24 14:29:53 2024 +0000 Update facade docblocks commit f0edf22543d7faa9d34710416e66dd4d22a99adc Author: Lito Date: Wed Jan 24 15:29:17 2024 +0100 Unify method definition commets with \Psr\Logger\Interface (#49805) Since https://github.com/php-fig/log/blob/master/src/LoggerInterface.php allows to use \Stringable as $message, the definition of the methods is updated to phpstan error reports. commit 76e49317a7c8d4d2058b048e7c5bd553f2f7523f Author: Mikhail Salkov Date: Wed Jan 24 06:56:49 2024 +0500 [10.x] Add storage:unlink command (#49795) * Add storage:delete-links command * Rename to storage:unlink * Update StorageUnlinkCommand.php --------- Co-authored-by: Taylor Otwell commit 882fed2167262881aae16c45e4fce22f9cd5b0b6 Author: driesvints Date: Tue Jan 23 15:27:29 2024 +0000 Update CHANGELOG commit fef1aff874a6749c44f8e142e5764eab8cb96890 Author: Taylor Otwell Date: Tue Jan 23 09:07:56 2024 -0600 version commit 1896c9de5735424e045772467c7fdb94803aeb30 Author: Taylor Otwell Date: Tue Jan 23 08:44:22 2024 -0600 wip commit f1a84988af3b93db266fb9f3d38d97bd481c5414 Author: Taylor Otwell Date: Tue Jan 23 08:43:08 2024 -0600 accept arrays commit a387d124dedfa1d8bd97d829e3c25ee082462466 Author: Mior Muhammad Zaki Date: Tue Jan 23 08:53:22 2024 +0800 [11.x] Fixes database build steps for GitHub Actions (#49786) * [11.x] Fixes database build steps for GitHub Actions Original PR: #49778 Testbench Core changes: orchestral/testbench-core@538920c58aafb6838893c2a555e738cc4226f982 Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki commit d7d19b28e2d096ded382cab55e7ce7a5ece98a41 Merge: 969fb0b919 d7616a176a Author: Mior Muhammad Zaki Date: Tue Jan 23 06:39:29 2024 +0800 Merge branch '10.x' commit d7616a176afc641e9693266920cb11e84c352241 Author: Mior Muhammad Zaki Date: Tue Jan 23 06:38:08 2024 +0800 [10.x] Test Improvements (#49785) extracted from #49764 Signed-off-by: Mior Muhammad Zaki Co-authored-by: kylekatarnls commit 969fb0b9197686bace49f69fb9f0821cc0bb0eed Author: Taylor Otwell Date: Mon Jan 22 16:29:12 2024 -0600 use file driver commit 91bc0f6c0898a311355dff666d640a48f2636ad5 Author: Kyle Date: Mon Jan 22 19:45:06 2024 +0100 Allow Uuid and Ulid in Carbon::createFromId() (#49783) commit eab978710bfa851828e96968c9ad2d3ac4eb9de7 Author: Nuno Maduro Date: Mon Jan 22 18:39:13 2024 +0000 Requires Guzzle by default (#49784) commit cb9983c3a56e65b967cd4ff59ed75cc2a6949f9c Author: Steve Bauman Date: Mon Jan 22 11:23:01 2024 -0500 [10.x] Add `Str::unwrap` (#49779) * Add Str::unwrap * Add "unwrap" to stringable * Add more tests commit 45ea4093f2f9ad431d0184784a7496ce4b561b78 Author: Nuno Maduro Date: Mon Jan 22 15:23:38 2024 +0000 [11.x] Fixes duplicate `replace` on `composer.json` (#49781) * Fixes composer `Warning: Ambiguous class resolution` * Fixes composer `Warning: Ambiguous class resolution` * Fixes wrong conflcit commit dfc24d128b45cc4682dac8ad91e8722608b27447 Author: Taylor Otwell Date: Mon Jan 22 09:04:29 2024 -0600 [11.x] Fake Queue Interactions (#49766) * prototype fake queue interactions * check delay * Apply fixes from StyleCI * fix job id --------- Co-authored-by: StyleCI Bot commit 4c71f54b8e7572471169cbb5efd69cfc41f95c62 Author: Nuno Maduro Date: Mon Jan 22 14:10:05 2024 +0000 Fixes default db envs (#49778) commit cb9d3821bd5dc375db52362d52cc8728235fe793 Author: Mior Muhammad Zaki Date: Mon Jan 22 21:55:38 2024 +0800 [11.x] Update `composer.json` and marked `spatie/once` as replaced. (#49771) * [11.x] Update `composer.json` and marked `spatie/once` as replaced. Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki commit e364c47680724e8b02b3bfabf8441e4957047feb Author: Mior Muhammad Zaki Date: Mon Jan 22 21:55:20 2024 +0800 [10.x] Only use `Carbon` if accessed from Laravel or also uses (#49772) `illuminate/support` fixes #49765 Signed-off-by: Mior Muhammad Zaki commit 5125d5fef09fca764f84b3fdaff557d4d8b56acc Author: Stephen Rees-Carter Date: Mon Jan 22 04:02:32 2024 +0100 Reduce failure rate of Arr::shuffle() tests (#49769) commit d4f9dab018ad41adcf3860b1730410f007e686ea Author: taylorotwell Date: Sun Jan 21 22:58:48 2024 +0000 Update facade docblocks commit b754ca6c258a9622aa2a9fb92f1daa1dbe22549b Author: Tim MacDonald Date: Mon Jan 22 09:58:11 2024 +1100 [10.x] Global default options for the http factory (#49767) * Global default options for the http factory * formatting --------- Co-authored-by: Taylor Otwell commit 1fe128008da509ac90a617be2d497a78dc17ab16 Author: Taylor Otwell Date: Sun Jan 21 16:34:23 2024 -0600 update methods commit 8a7e0e0425e1b6527f2be8b72ec9710728c708f1 Author: taylorotwell Date: Sun Jan 21 21:49:14 2024 +0000 Update facade docblocks commit 808debb3687604a92f1a9cf7bfab248a6259e3cc Author: Taylor Otwell Date: Sun Jan 21 15:48:23 2024 -0600 basePath commit 37eea841d5955ed3902d8660d84c15da400755f1 Author: Maarten Buis Date: Sun Jan 21 18:01:45 2024 +0100 [10x] Sort service providers alphabetically (#49762) commit 7d7c45af2853310173c754db6917a54ff38e9e0e Author: StyleCI Bot Date: Sat Jan 20 22:57:35 2024 +0000 Apply fixes from StyleCI commit 3b149ae0c17a4794c2ceb4c8afecb7511aca0bc3 Author: Taylor Otwell Date: Sat Jan 20 16:57:21 2024 -0600 allow publishing of config-stubs if they exist commit 6d3128a2eae481ba72c8fa7d10577759e211d4b1 Author: Taylor Otwell Date: Sat Jan 20 16:45:40 2024 -0600 shortcut for authenticated sessions commit 284c3416bd2a39c0e464c266bbf1d8d5d948bd4a Author: Taylor Otwell Date: Sat Jan 20 16:17:29 2024 -0600 default to create database if it does not exist commit 4a1b6ea5aace51640bfde97ed2ba24127af82abc Author: Taylor Otwell Date: Sat Jan 20 16:03:03 2024 -0600 update values for an sqlite default world (#49758) commit c20bb3994cd2bcf1090b0f01aef5a4069edd69eb Author: taylorotwell Date: Sat Jan 20 20:03:09 2024 +0000 Update facade docblocks commit e13c92feff6a7aeccf30781c00206eb7f277fd7c Author: StyleCI Bot Date: Sat Jan 20 20:02:39 2024 +0000 Apply fixes from StyleCI commit 346783f89af34831566253aeacabf12c6d22719d Author: Saya <379924+chu121su12@users.noreply.github.com> Date: Sun Jan 21 04:02:20 2024 +0800 [11.x] Send cache name to event (#49754) * Send cache name to event * fix code * formatting --------- Co-authored-by: Taylor Otwell commit ac5bed5b3a1efa194144dee0ce98111aad60c1b9 Author: D. Nagy Gergő Date: Sat Jan 20 20:40:39 2024 +0100 [10.x] Add multiple channels/routes to AnonymousNotifiable at once (#49745) * [10.x] Add multiple channels/routes to AnonymousNotifiable at once * Update Notification.php --------- Co-authored-by: Taylor Otwell commit 95aa29f6c249a72664c3c9ae999ee6dcccfe76a4 Author: Nuno Maduro Date: Fri Jan 19 23:29:06 2024 +0000 [11.x] Adds `once` memoization function (#49744) * Adds `once` * Apply fixes from StyleCI * Fixes code being called from eval * Apply fixes from StyleCI * Improves method name * Fixes auto-completion * Adds support for first class callable * Apply fixes from StyleCI * Adds more tests * Re-enables `Once` on flush * Flushes `Once` instance after test case and after queue job * Improves hash * More tests * formatting --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell commit 24afd8b459a241ab8761e92bdb028c30efceadcf Merge: 8317461b65 443ec4438c Author: Nuno Maduro Date: Fri Jan 19 16:21:37 2024 +0000 Merge branch '10.x' commit 443ec4438c48923c9caa9c2b409a12b84a10033f Author: Jeremy Angele <131715596+angelej@users.noreply.github.com> Date: Fri Jan 19 14:35:56 2024 +0100 Implement "max" validation rule for passwords (#49739) * Implement "max" validation rule for passwords * Update Password.php --------- Co-authored-by: Taylor Otwell commit 8317461b65fe58d4a63e1f5318bc6e64bc548387 Author: Hafez Divandari Date: Thu Jan 18 20:57:15 2024 +0330 fix spatial types (#49634) commit 3acc1d04423d3925a9592b5bae5158b9f9d58905 Author: Roj Vroemen Date: Thu Jan 18 17:43:59 2024 +0100 [10.x] Fix decoding issue in MailLogTransport (#49727) * Handle multipart messages and only decode content * formatting --------- Co-authored-by: Taylor Otwell commit c9cc5aac100d7a4babdf5153b9ef1a1a24b7d851 Author: Günther Debrauwer Date: Thu Jan 18 15:35:30 2024 +0100 [11.x] Add ExcludesPaths middleware trait (#49734) * add "ExcludesPaths" middleware trait * Update ExcludesPaths.php --------- Co-authored-by: Taylor Otwell commit 3b9f90792929c2cfcb14b695e5025b7a2956d5c1 Author: Mior Muhammad Zaki Date: Thu Jan 18 22:22:25 2024 +0800 [11.x] Allow `RouteServiceProvider` to be loaded more than once (#49732) * [11.x] Allow `RouteServiceProvider` to be loaded more than once * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki commit ffc0943af46921dfa8cc9e21d7b57a179272f6ed Author: Nuno Maduro Date: Thu Jan 18 13:00:55 2024 +0000 Slim comments commit 089bb07c4b09a4bc8d6bedfec29c8aa531b6cc5b Merge: 7fcb2f7c31 c62d15ba60 Author: Nuno Maduro Date: Thu Jan 18 12:56:59 2024 +0000 Merge branch '10.x' commit 7fcb2f7c31d11ae2e9fe2db002207715e26a3891 Author: Taylor Otwell Date: Wed Jan 17 17:00:17 2024 -0600 wip commit 5d520eb77240fe17b036d4a5474b9e2b22baf924 Author: Taylor Otwell Date: Wed Jan 17 16:24:21 2024 -0600 format message commit c62d15ba607b423a65da217f4201c1f4840ba1bf Author: Daniel Mason Date: Wed Jan 17 21:20:57 2024 +0000 [10.x] Adds JobQueueing event (#49722) * Adds JobQueueing event * style fixes * fix broken tests * fix broken tests commit d710a0195f3d4cd5cb80290a94475ea0299a031f Author: Daniel Bakan Date: Wed Jan 17 22:16:48 2024 +0100 fix Rule::unless for callable (#49726) commit 72e9965635b736bee440dbcc565bceac9b255c5b Author: Simon Hamp Date: Wed Jan 17 15:07:27 2024 +0000 Switch to hash_equals (#49721) commit 53e3246f1c88d2598bd4c3d4c1eace2769aa9b14 Author: Mior Muhammad Zaki Date: Wed Jan 17 22:57:12 2024 +0800 [11.x] Flush `RegisterProviders` bootstrapper state between tests (#49719) * [11.x] Flush `RegisterProviders` bootstrapper state between tests. Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Update RegisterProviders.php * Update TestCase.php --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Taylor Otwell commit f630b3935edd8bf70f5bae381b2e6002e64777aa Merge: dc8b91cc31 158767fb83 Author: Mior Muhammad Zaki Date: Wed Jan 17 21:06:14 2024 +0800 Merge branch '10.x' Signed-off-by: Mior Muhammad Zaki commit 158767fb835a63540845d266e58d821726fc0baa Author: driesvints Date: Tue Jan 16 17:11:31 2024 +0000 Update CHANGELOG commit dc8b91cc31f36b23f648c158f950cd49b80f9873 Author: StyleCI Bot Date: Tue Jan 16 16:20:03 2024 +0000 Apply fixes from StyleCI commit 6e9f185ef244e1d8c5af7d67b7dad2dd8cbd00d3 Author: Taylor Otwell Date: Tue Jan 16 10:19:38 2024 -0600 adjust literal, add tests commit da31969bd35e6ee0bbcd9e876f88952dc754b012 Author: Taylor Otwell Date: Tue Jan 16 09:23:58 2024 -0600 version commit d056aabc2caffcdbf21927d464ecc1a6d145e401 Author: taylorotwell Date: Tue Jan 16 15:11:48 2024 +0000 Update facade docblocks commit 94d0fd7b74c47d1cb51aa09dad125a415412e445 Author: Jan Stolle Date: Tue Jan 16 16:11:13 2024 +0100 Allow StreamInterface as raw HTTP Client body (#49705) * Allow StreamInterface as raw HTTP Client body Since the body payload is passed through to Guzzle, which in turn can handle Streams as well, this extends the versatility of the HTTP Client * Update PendingRequest.php --------- Co-authored-by: Taylor Otwell commit 03eabd50cf00206bf043f49794f73158fe302b1c Author: Kieran Date: Tue Jan 16 15:08:28 2024 +0000 [10.x] Add exit code to queue:clear, and queue:forget commands (#49707) * [10.x] Add exit code to queue:clear, and queue:forget commands * Update ClearCommand.php --------- Co-authored-by: Taylor Otwell commit 7cbdae560f66df62e02b8e0ced23459af105b88c Author: Dries Vints Date: Tue Jan 16 15:41:20 2024 +0100 Revert "[10.x] Improve numeric comparison for custom casts" (#49702) commit 8d4eaebc25d4349368d326f5fff80e15a0ffdd1d Author: Michael Nabil <46572405+michaelnabil230@users.noreply.github.com> Date: Tue Jan 16 02:22:35 2024 +0200 Use a static function (#49696) commit 637b814c0960132d72496851856a1e2afe1d5733 Author: StyleCI Bot Date: Mon Jan 15 22:48:24 2024 +0000 Apply fixes from StyleCI commit 8f29cc9be17d8ff06826d07007afbeed9dc82452 Author: Jonas Staudenmeir Date: Mon Jan 15 23:48:03 2024 +0100 [11.x] Support eager loading with limit (#49695) * Support eager loading with limit * Fix style * formatting --------- Co-authored-by: Taylor Otwell commit 4d8c49fee5fefc8edb76d0abe60478b9451f17f9 Author: taylorotwell Date: Mon Jan 15 18:53:10 2024 +0000 Update facade docblocks commit 9fec940b5c5382136beb25954ecca060337bb535 Author: Phil Bates Date: Mon Jan 15 18:52:32 2024 +0000 [10.x] Officially support floats in trans_choice (#49693) This already works fine with no further changes needed, so I'm not sure if there's a reason that floats were never officially supported. This is how it is currently, which you can see works perfectly fine: Given: // lang/en/foo.php return [ 'hours' => 'A total of :hours hour|A total of :hours hours', ]; Then: trans_choice('foo.hours', 1, ['hours' => 1]) === 'A total of 1 hour' trans_choice('foo.hours', 1.0, ['hours' => 1.0]) === 'A total of 1 hour' trans_choice('foo.hours', 1.1, ['hours' => 1.1]) === 'A total of 1.1 hours' trans_choice('foo.hours', 0.9, ['hours' => 0.9]) === 'A total of 0.9 hours' However, when running phpstan & larastan on a Laravel project that passes a float to trans_choice when wanting to display text similar to those examples ("A total of X hour[s]") it results in an error because the only documented allowed types are \Countable|int|array. Co-authored-by: Phil Bates commit 11dd6449c3647f41e7c20c0e6dbc1b38b93b6992 Author: Mior Muhammad Zaki Date: Tue Jan 16 00:13:50 2024 +0800 [10.x] Test Improvements (#49679) Signed-off-by: Mior Muhammad Zaki commit c1e78c4ef1631a7c9c81ce22ac00d49b3bbce4dd Author: Dries Vints Date: Mon Jan 15 17:01:20 2024 +0100 Update pull-requests.yml commit 024442ddb828c0db8beb422a931f7c6a35670046 Author: AJ <60591772+devajmeireles@users.noreply.github.com> Date: Mon Jan 15 12:56:04 2024 -0300 code (#49681) commit fc0b6876ab29927a6d664d796b5c05dc70ca045e Author: Dries Vints Date: Mon Jan 15 16:53:03 2024 +0100 Implement draft workflow (#49683) commit c7d5e0ee29b799ea66117be5c36f6b7efc1ca33d Author: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Sun Jan 14 20:08:40 2024 +0330 [10.x] Add tests for Eloquent methods (#49673) * Create EloquentModelLoadMaxTest.php * Create EloquentModelLoadMinTest.php * Create EloquentModelLoadSumTest.php commit 6d2f90b66c6977fc1a19bedbf2a531cfedf28d15 Author: Su Date: Sun Jan 14 01:44:41 2024 +0700 [10.x]: Remove unused class ShouldBeUnique when make a job (#49669) commit f812cf90326e5957584255593c86e85171cce1e9 Author: Piotr Adamczyk Date: Sat Jan 13 19:42:08 2024 +0100 [10.x] feat: add base argument to Stringable->toInteger() (#49670) * feat: add base argument to Stringable->toInteger() * fix: StyleCi commit ec6e1699ce72edafb05083ed4922c90a1cf48ac8 Author: StyleCI Bot Date: Fri Jan 12 18:21:31 2024 +0000 Apply fixes from StyleCI commit 4025748535ebb1c09ea67d2b558276de3f5d4bb3 Author: Andrea Marco Sartori Date: Sat Jan 13 04:21:12 2024 +1000 [11.x] Define global validation logic for Laravel Prompts (#49497) * [10.x] Define global validation logic for Laravel Prompts * [10.x] Update validation logic * [10.x] Infer field name through associative array * [10.x] Require latest version of Laravel Prompts * [10.x] Implement fallback validation * [10.x] Extract logic to instantiate the validator * [10.x] Update methods name * formatting * formatting * formatting and literal --------- Co-authored-by: Dries Vints Co-authored-by: Taylor Otwell commit 47af503723a8e59f4dbe6bbfab78dc92d5344f33 Author: Anas Morahhib Date: Fri Jan 12 18:17:46 2024 +0100 Update pagination tailwind.blade.php (#49665) commit 534872c3fdc6dcea2bd3728b44f9302b952e5cab Author: AJ <60591772+devajmeireles@users.noreply.github.com> Date: Fri Jan 12 11:18:14 2024 -0300 [10.x] Printing Name of The Method that Calls `ensureIntlExtensionIsInstalled` in `Number` class. (#49660) * code * formatting according to style-ci * moving the logic for inside the if condition * Update Number.php --------- Co-authored-by: Taylor Otwell commit 7dff807a162a8559062a1452ee98edf8c92acd3b Author: taylorotwell Date: Fri Jan 12 14:16:03 2024 +0000 Update facade docblocks commit e68a840bbf764d275894b267d31302c6bbb498fe Author: Tim MacDonald Date: Sat Jan 13 01:15:33 2024 +1100 Revert parameter name change (#49659) commit 3ae9c40c399a32dbfe20b220a14f1f85ea596cd8 Merge: d8e5ec4909 dbce9d05c9 Author: Nuno Maduro Date: Fri Jan 12 13:44:56 2024 +0000 Merge branch '10.x' commit dbce9d05c940a4b3e669709d1b7f8792a48fba62 Author: Taylor Otwell Date: Thu Jan 11 16:12:00 2024 -0600 flush shared context commit d8e5ec4909fb9bf963df5ea9759ab9b3760efc1f Author: Stephen Rees-Carter Date: Thu Jan 11 22:16:20 2024 +0100 [11.x] Use secure randomness in Arr::random() and Arr::shuffle() (#49642) * Use Randomizer::pickArrayKeys() within Arr::random() * Use Randomizer::shuffleArray() in Arr::shuffle() * Remove unused $seed from shuffle * Fix failing shuffle tests commit 8bb98e39b6dea70bc0a2e8fea51eb4ed55f6ef8b Author: Frankie Jarrett Date: Thu Jan 11 14:33:23 2024 -0600 dispatchIf() and dispatchUnless() for batches (#49639) commit e75374205ffc4379a1398df713649a368002e48f Author: taylorotwell Date: Thu Jan 11 19:47:33 2024 +0000 Update facade docblocks commit 140248749f461b87426773513306eb4ba86d1303 Author: Taylor Otwell Date: Thu Jan 11 13:46:55 2024 -0600 flush context on all log channels, just not default commit ca52cb3c491de644d53b01c0ab1e4ac02788117b Author: Hafez Divandari Date: Thu Jan 11 19:30:56 2024 +0330 fix adding stored columns on sqlite (#49638) commit 0026a0ab71a3bda4c358555f2a24b72eefd41dd0 Author: Nuno Maduro Date: Thu Jan 11 15:56:43 2024 +0000 [11.x] Avoids `SQLiteConnection::__construct` throwing exceptions (#49653) * Fixes SQLite connection throwing exceptions on constructor * Apply fixes from StyleCI * Removes `@todo` --------- Co-authored-by: StyleCI Bot commit 71d12b58433b4f3ea27a6b5a3858c881d70a5b66 Author: Luke Downing Date: Thu Jan 11 15:55:33 2024 +0000 [11.x] Passes the application instance to `withRouting`'s `then` callable. (#49654) commit 4bd7f25cf225cc2d36ac6e5be469251a3e661fc7 Author: driesvints Date: Thu Jan 11 13:25:26 2024 +0000 Update facade docblocks commit af5ba263b4721e63f8f285ad402b061a49256776 Author: Dries Vints Date: Thu Jan 11 14:24:09 2024 +0100 Fix return type commit fb1d2c0b3e171353a236b6f1f6bcb767f873c8d7 Author: Nuno Maduro Date: Wed Jan 10 15:10:13 2024 +0000 Fixes conflict commit 81d8ba9b486d9814036c661a0074ce46e3366b0e Merge: 23b6adced1 784bf76726 Author: Nuno Maduro Date: Wed Jan 10 15:04:51 2024 +0000 Merge branch '10.x' commit 23b6adced103250dc841767eb167facd7426d1b0 Author: Nuno Maduro Date: Wed Jan 10 15:02:22 2024 +0000 [11.x] Adds PHPUnit 11 support (#49622) commit 784bf76726b16763e172202923dc3d3df55c10cf Author: James <61766491+lioneaglesolutions@users.noreply.github.com> Date: Thu Jan 11 00:51:31 2024 +1000 [11.x] Add additional context to Mailable assertion messages (#49631) * add recipient type to message * fix tests commit e63cea8d3b6013cf607f2ff2c142c7794bc37182 Author: James <61766491+lioneaglesolutions@users.noreply.github.com> Date: Wed Jan 10 23:17:44 2024 +1000 fill empty test (#49632) commit 8f48538ed651173d4dcb3ee42c9c90453adc5869 Author: Frankie Jarrett Date: Tue Jan 9 16:05:18 2024 -0600 dispatchIf() and dispatchUnless() for job chains (#49624) commit 1c496d0d7f7aa86025f5cf82d67134a7abfb838c Author: Taylor Otwell Date: Tue Jan 9 16:02:00 2024 -0600 use base path commit b94408dd54855e1d2a29b3d6e8ec76374be9bd83 Merge: 47c2c6fa91 9d687c9303 Author: Taylor Otwell Date: Tue Jan 9 15:57:41 2024 -0600 Merge branch 'master' of https://github.com/dbhynds/framework into dbhynds-master commit 47c2c6fa91330caaa560f7af3b155bda795118bd Author: Dwight Watson Date: Wed Jan 10 08:33:53 2024 +1100 Fix return value and docblock (#49627) commit 61d100a647e8b99aaef71265fb2497c895e0ece4 Author: Luan Freitas <33601626+luanfreitasdev@users.noreply.github.com> Date: Tue Jan 9 16:24:03 2024 -0300 Revert "[10.x] Make ComponentAttributeBag Arrayable (#49524)" (#49623) This reverts commit a5f661f403bd52feff3dc5d25f55fb8f97b551cc. commit 9ccf0031d1cb8669752bc95e85cdccad20706461 Author: Hafez Divandari Date: Tue Jan 9 22:51:21 2024 +0330 [11.x] Remove Doctrine DBAL (#48864) * remove doctrine dbal from grammar * remove unused imports * fix tests * fix tests * remove mysql 5.7 tests * fix facade doctype * use native column modifying by default * fix tests * fix tests * wip * Revert "remove mysql 5.7 tests" This reverts commit ba31106361cb85493f02f9bb6043ec9aeda5e507. * support native column renaming on MySQL 5.7 * fix style * wip * wip * remove doctrine usage on DatabaseTruncation * wip * rename index on sqlite * remove doctrine/dbal from db commands * fix styles * wip * wip * support renaming columns on legacy MariaDB * remove redundant non-standard tests * add collation modifier to sqlite * support native column modifying on sqlite * fix styles * add user-defined types to db:show * wip * fix styles * fix dropForeign exception on SQLite * fix styles * include generated and hidden columns on sqlite * remove custom doctrine types * remove doctrine change column * styling * remove support for registering custom doctrine types * remove unused config methods and property * remove redundant semicolon * force re-run tests * remove doctrine related conflicts reverts #49438 and #49456 * remove doctrine/dbal from require-dev * Revert "remove doctrine/dbal from require-dev" This reverts commit fc4dd91060ee46f59f0015c216d758419a73f385. * revert unrelated changes * disable foreign key constraints when altering table on SQLite * fix styling * consider backticks when parsing collate on SQLite * update facade docblocks * remove doctrine connection * formatting * fix conflicts * formatting --------- Co-authored-by: Taylor Otwell commit 9d687c93033608adfb89cf64efe53ea4ec945c25 Author: Davo Hynds Date: Tue Jan 9 13:00:30 2024 -0600 Fix styling and doc block commit 0ca166cbac34abc8a194efd09997b7deb80044c7 Author: Nuno Maduro Date: Tue Jan 9 18:06:41 2024 +0000 Uses PHP 8.2 on StyleCI (#49625) commit 94550906944507d496d0e3735eea48efe69f799f Author: Davo Hynds Date: Tue Jan 9 11:35:23 2024 -0600 Allow an array, but fall back to app/Models commit 299aae5462b17319d943879a4abf8b8f61347406 Author: driesvints Date: Tue Jan 9 17:13:05 2024 +0000 Update CHANGELOG commit dcc653fc383a5cbdfd3b39cdc4352b1d93db9173 Author: Caen De Silva Date: Tue Jan 9 17:22:40 2024 +0100 [10.x] Add a `threshold` parameter to the `Number::spell` helper (#49610) * [10.x] Add a `threshold` parameter to the `Number::spell` helper Adds a parameter to limit how high numbers are spelled out. * Add docblock for the threshold parameter * adjust logic * formatting --------- Co-authored-by: Taylor Otwell commit e51f1460084c0efe5a8eca451e79bc87eeee706c Author: Davo Hynds Date: Tue Jan 9 09:23:02 2024 -0600 Change the argument to path commit 7a9470071dac9579ebf29ad1b9d73e4b8eb586fc Author: Taylor Otwell Date: Tue Jan 9 05:46:47 2024 -0600 version commit 1ee9fdb75e9fd9b01e7c090134ec70f891328827 Merge: 3a530f3c96 ab9f7c4cf5 Author: Dries Vints Date: Tue Jan 9 09:38:56 2024 +0100 Merge branch '10.x' # Conflicts: # src/Illuminate/View/ComponentAttributeBag.php # tests/View/Blade/BladeEchoHandlerTest.php commit f08994907db78caa12981bfd285a57231b52a773 Author: Davo Hynds Date: Mon Jan 8 14:27:37 2024 -0600 Add an option to specify the default path to the models directory commit ab9f7c4cf563be95f6e9046bce4a92801627b282 Author: Mior Muhammad Zaki Date: Tue Jan 9 03:10:24 2024 +0800 [10.x] Use locks for queue job popping for PlanetScale's MySQL-compatible Vitess 19 engine (#49561) Signed-off-by: Mior Muhammad Zaki commit 89f78ca4b359fa83ba48206fb1e63fcf0ad03f73 Author: Osman Taha <37361318+Mrjavaci@users.noreply.github.com> Date: Mon Jan 8 18:01:03 2024 +0300 Update Handler.php (#49614) commit 3a530f3c969f438794295337de942e05c0159423 Author: Taylor Otwell Date: Sun Jan 7 11:32:15 2024 -0600 adjust trust proxies commit f357fd868354492a807ed772bc452ac37ea22b5d Author: StyleCI Bot Date: Sun Jan 7 16:54:08 2024 +0000 Apply fixes from StyleCI commit 99ef3614fed7dabad10d2ef8118e7ce1b1d422bd Author: Adam Campbell Date: Sun Jan 7 10:53:44 2024 -0600 [10.x] Add APA style title helper (#49572) * Add APA style title helper * Update Str.php * Update Str.php * Update Str.php * Update src/Illuminate/Support/Str.php * formatting * Update Str.php * formatting * formatting * formatting --------- Co-authored-by: Dries Vints Co-authored-by: Taylor Otwell commit 4083fa5d61eb4f3be347de29cdd305f5db3d543d Author: taylorotwell Date: Sun Jan 7 15:45:28 2024 +0000 Update facade docblocks commit d424ce728d65647f4c2cdf06a25e1a8b4369aab2 Author: Marco Deleu Date: Sun Jan 7 12:44:56 2024 -0300 [10.x] Ability to establish connection without using Config Repository (#49527) * Ability to establish connection without using Config Repository * StyleCI * Update src/Illuminate/Database/DatabaseManager.php Co-authored-by: Dries Vints * FQN * FQN * FQN on tests as well * FQN * formatting --------- Co-authored-by: Dries Vints Co-authored-by: Taylor Otwell commit 29a96ebc55bf5b7b7fe3321afea247927c3c67a9 Author: taylorotwell Date: Sun Jan 7 15:25:44 2024 +0000 Update facade docblocks commit d476191b8ab5393c49160e8a5fe889ccab8a8e24 Author: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Sun Jan 7 19:25:16 2024 +0400 [10.x] Add `assertCount` test helper (#49609) * Add assertCount method * apply StyleCI * Update QueueFake.php --------- Co-authored-by: Taylor Otwell commit 29e904fdf8d718f115b72f38d374b080e30ad1c6 Author: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Sun Jan 7 19:14:46 2024 +0400 Add methods to ProcessResult interface (#49607) commit 08073440c0f7675decf0bfb617784441205c33c3 Author: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Sat Jan 6 23:14:05 2024 +0400 Refactor time handling using InteractsWithTime trait (#49601) commit 1f252ac3dba6f9f022443951f6505320bc4ef806 Author: StyleCI Bot Date: Sat Jan 6 17:18:22 2024 +0000 Apply fixes from StyleCI commit 9cd28c157910cd4ced3ccc0e3afb663bf807de63 Author: tsjason Date: Sat Jan 6 20:18:01 2024 +0300 Allow \Blade::stringable() to be called on native Iterables (#49591) commit 35594970a45c31f90f5d79552a983f5a75daab39 Author: David Martínez Date: Sat Jan 6 18:07:54 2024 +0100 Fix Stringable::convertCase() return type (#49590) Return type was _string_ instead of _static_, broking method chaining on IDEs. commit 840c8d6e76a25ccff80a25f82818f3fb688ee26c Author: lorenzolosa <11164571+lorenzolosa@users.noreply.github.com> Date: Sat Jan 6 11:56:22 2024 -0500 more comprehensive type hinting of the $operator parameter (#49599) commit 3141ef2056213854abb62e2f6f4127525c3fc050 Author: taylorotwell Date: Thu Jan 4 13:29:26 2024 +0000 Update facade docblocks commit 6d124234c6da78c438d15267ebfed58c76c23a8e Author: Tim MacDonald Date: Fri Jan 5 00:28:43 2024 +1100 [10.x] Allow Vite asset path customization (#49437) * Allow Vite asset path customization * lint * lint commit 9109198f5d869a72e479efc706a276f8bbfe0035 Author: Dries Vints Date: Thu Jan 4 14:16:30 2024 +0100 Fix deprecation with null value in cache FileStore (#49578) commit 607bb938f141d5050854e4be27eb028fa79f729d Author: Mior Muhammad Zaki Date: Thu Jan 4 21:15:21 2024 +0800 [11.x] Update suggested/recommended version to match `laravel/laravel` (#49571) Signed-off-by: Mior Muhammad Zaki commit daa24d6a61559ea20a4387550825791b438b9fde Author: Saya <379924+chu121su12@users.noreply.github.com> Date: Thu Jan 4 21:15:05 2024 +0800 [11.x] Allow configuring session.partitioned via env (#49570) * Update session.php * Update session.php --------- Co-authored-by: Taylor Otwell commit 194b7a7c8ea35d9c87a4974def7e976c8673e296 Author: Sabin Chacko <31690816+sabinchacko03@users.noreply.github.com> Date: Thu Jan 4 02:51:06 2024 +0400 [10.x] Update tailwind.blade.php for dark mode (#49515) * Update tailwind.blade.php for dark mode * Update simple-tailwind.blade.php adding dark mode classes. commit bd3161db1c7d52a3f5521240f0609703ca0ffc1e Author: Dwight Watson Date: Thu Jan 4 09:42:32 2024 +1100 [10.x] Add assertViewEmpty to TestView (#49558) * Add assertEmpty to TestView * Update TestView.php --------- Co-authored-by: Taylor Otwell commit 551569ee1064028d32e5ff05f9bc7a4f31f503b4 Author: Grldk <33746490+Grldk@users.noreply.github.com> Date: Wed Jan 3 23:40:30 2024 +0100 Update Builder.php (#49563) `$columns` can be `null` (before any columns are selected commit f434c2623472bcd2df6e3fc3194965e848d1dfe9 Author: Mahmood Dehghani Date: Thu Jan 4 02:10:04 2024 +0330 Remove unused code from tests (#49566) commit 631137702f3bf8459ff8f6d0377ac74d8aefd5d7 Author: taylorotwell Date: Wed Jan 3 14:12:39 2024 +0000 Update facade docblocks commit fa446dc78a17aaf922585a9b25b804181a451914 Author: Kevin Bui Date: Thu Jan 4 01:12:01 2024 +1100 [10.x] Make the Schema Builder macroable (#49547) * make the schema builder macroable * stop passing the parameter to the closure. commit 016ae04ff6b4aefae78e19c64d8271357d911406 Author: D. Nagy Gergő Date: Wed Jan 3 15:07:01 2024 +0100 [10.x] Fix parentOfParameter method (#49548) commit 757848e60340e5da903a4776eeb2c34479d62f69 Author: Tim MacDonald Date: Thu Jan 4 01:05:21 2024 +1100 [10.x] Flush about command during test runs (#49557) * Flush about command during test runs * Update name * Update TestCase.php --------- Co-authored-by: Taylor Otwell commit f493ec577ea5164deb699780c72ac1d6dae30fbd Author: Till Krüss Date: Wed Jan 3 06:02:53 2024 -0800 remove unused code (#49559) commit 84d28db0bb6434e3fb75ce51ad8544db1c2c2221 Author: Till Krüss Date: Wed Jan 3 05:57:39 2024 -0800 don't call `CLIENT SETNAME` on cluster connection (#49560) commit d30807f3acf7a4564efe94789dad386f4d3b36e8 Author: Nuno Maduro Date: Wed Jan 3 13:52:03 2024 +0000 [11.x] Allows deleting `CreatesApplication` trait (#49554) * Improves application builder * Uses composer instead of `debug_backtrace` * Removes non used import * Allows `CreateApplication` to be optional * Fixes non-bootstrapping app * Fixes coding style commit 8e226b38d16860007b484ede25bbce7dc8a9df6d Author: taylorotwell Date: Wed Jan 3 13:51:07 2024 +0000 Update facade docblocks commit 4deacb2cf13c190e8ef11304a6be74d4ffeb5049 Author: Nuno Maduro Date: Wed Jan 3 13:50:32 2024 +0000 [11.x] Improves `Application::configure` method (#49552) * Improves application builder * [11.x] Infers base path from composer instead (#49553) * Uses composer instead of `debug_backtrace` * Removes non used import commit 38fa79eaa22b95446b92db222d89ec04a7ef10c7 Author: pintend Date: Mon Jan 1 10:37:06 2024 -0500 Update AsArrayObject.php use ARRAY_AS_PROPS (#49534) commit 06cd2bc1121eea80d0070c51b7b0b2971c9175c7 Author: Taylor Otwell Date: Mon Jan 1 09:34:40 2024 -0600 wip commit b7eb9a7fe31b3e24e6e7936f9ada1f48d071c86f Author: taylorotwell Date: Mon Jan 1 15:34:40 2024 +0000 Update facade docblocks commit c95c7b6a52ca4f815a8cb759aca53a14c3785664 Author: habibendris <132191359+habibendris@users.noreply.github.com> Date: Mon Jan 1 18:34:01 2024 +0300 [11.x] Add path method to Illuminate\Contracts\Filesystem\Filesystem interfa… (#49525) * Add path method to Illuminate\Contracts\Filesystem\Filesystem interface to prevent error in Storage Facade * Update Filesystem.php --------- Co-authored-by: Taylor Otwell commit 81b0c16f4e80f53a89ee8b31968c2109205c9bd2 Author: StyleCI Bot Date: Mon Jan 1 15:24:30 2024 +0000 Apply fixes from StyleCI commit f8c636087a566309e7f50704440a7f512c71ff44 Author: lovePizza Date: Mon Jan 1 16:24:08 2024 +0100 [10.x] Fix whenAggregated when default is not specified (#49521) * Fix: whenAggregated should return MissingValue when default is not specified * Test: whenAggregated should return MissingValue when default is not specified commit a5f661f403bd52feff3dc5d25f55fb8f97b551cc Author: D. Nagy Gergő Date: Mon Jan 1 16:19:51 2024 +0100 [10.x] Make ComponentAttributeBag Arrayable (#49524) commit ae79bf02349afa5eba33c0f0b661992641e9de18 Author: Michael Nabil <46572405+michaelnabil230@users.noreply.github.com> Date: Mon Jan 1 17:17:18 2024 +0200 Clean Arr (#49530) commit a40c658fb4599a5623c2248d085eb4e145b6a8f5 Author: Maarten Buis Date: Mon Jan 1 15:03:04 2024 +0100 Update ScheduleListCommandTest.php (#49541) commit d73a545b59e2219b2af42a93e471e9e59cea9dcc Merge: edaac12111 f48141df6e Author: Dries Vints Date: Mon Jan 1 14:59:10 2024 +0100 Merge branch '10.x' # Conflicts: # CHANGELOG.md # src/Illuminate/Foundation/Application.php commit f48141df6eefc3f77367fceff616362756af544b Author: Michael Nabil <46572405+michaelnabil230@users.noreply.github.com> Date: Mon Jan 1 15:52:00 2024 +0200 [10.x] Fix Schedule test (#49538) * Fix * Change the date constant commit 06b69216d425ad21d183d21c00f79a36a1c1783f Author: James Brooks Date: Fri Dec 29 22:41:12 2023 +0000 [10.x] Add `Number::clamp` (#49512) * Add Number::clamp * Apply fixes from StyleCI * Update Number.php --------- Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell commit 13f78305055a89253f81f9642c654758efb84602 Author: taylorotwell Date: Fri Dec 29 21:53:50 2023 +0000 Update facade docblocks commit 6de1fead25697b8cf028e18d237a4a729fd5be2b Author: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Sat Dec 30 01:53:12 2023 +0400 Add session except method (#49520) commit 9761bc9907a262061403dffd45e244cd61225d42 Author: Mahmood Dehghani Date: Thu Dec 28 20:17:46 2023 +0330 improve numeric comparison for custom casts (#49504) commit 743c21f74608bf50345cd880c958ca6bfd304690 Author: taylorotwell Date: Thu Dec 28 16:46:56 2023 +0000 Update facade docblocks commit 6cfe0108f7eb97cb6babf9802665ceee4330f2bb Author: Anton Komarev <1849174+antonkomarev@users.noreply.github.com> Date: Thu Dec 28 19:46:25 2023 +0300 [10.x] Expand Gate::allows & Gate::denies signature (#49503) * [10.x] Expand Gate::allows & Gate::denies signature * [10.x] Expand Gate::allows & Gate::denies signature * [10.x] Expand Gate::allows & Gate::denies signature --------- Co-authored-by: Anton Komarev commit bd9b41ea6997b28aca988836953142eebe053516 Author: Kay W Date: Fri Dec 29 00:40:37 2023 +0800 [10.x] Fixes the `Arr::dot()` method to properly handle indexes array (#49507) * Fixes the `Arr::dot()` method to properly handle arrays with integer keys * Update tests/Support/SupportArrTest.php Co-authored-by: Mior Muhammad Zaki --------- Co-authored-by: Mior Muhammad Zaki commit edaac12111a9ad53699bcbda4da7b7c4769dfe4a Author: Mior Muhammad Zaki Date: Fri Dec 29 00:39:10 2023 +0800 [11.x] Use `Illuminate\Filesystem\join_paths` to avoid hard dependencies to (#49508) `Illuminate\Foundation\Application` Signed-off-by: Mior Muhammad Zaki commit ac960a1dcbda131b3262cbf04423d4d85c906ed5 Author: Hafez Divandari Date: Thu Dec 28 20:08:47 2023 +0330 [10.x] Include system versioned tables for MariaDB (#49509) * include system versioned tables for mariadb * fix tests * wip commit 0f1df957b057b64fd58727da5d1f6d7bd552ea95 Author: StyleCI Bot Date: Wed Dec 27 16:35:38 2023 +0000 Apply fixes from StyleCI commit 865f797a95970c14d01e88bec142d5e7fdc1a1da Author: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Wed Dec 27 11:35:13 2023 -0500 [10.x] `Model::preventAccessingMissingAttributes()` raises exception for enums & primitive castable attributes that were not retrieved (#49480) * throw an exception if castable attribute was not retrieved * test name * only for primitive types + enums * style * formatting * formatting --------- Co-authored-by: Taylor Otwell commit 0aff4114c81fafd98a44935a2da05befa5442a81 Author: Taylor Otwell Date: Wed Dec 27 09:57:08 2023 -0600 update mail config commit 7e9e27145ddfe5c73473b37fe9a0169c89c4cb9e Author: driesvints Date: Wed Dec 27 14:37:06 2023 +0000 Update CHANGELOG commit 114926b07bfb5fbf2545c03aa2ce5c8c37be650c Author: Taylor Otwell Date: Wed Dec 27 08:26:28 2023 -0600 minor release commit 148f029bbd0175f3db3d720216afa543a01224c4 Author: Nuno Maduro Date: Wed Dec 27 14:16:11 2023 +0000 Uses `SCHEDULE_CACHE_STORE` (#49492) commit 2acd8b8bdbc8550b84f2e350359cf2f5a85844ab Author: Nuno Maduro Date: Wed Dec 27 14:15:48 2023 +0000 [11.x] Adds missing end-of-line to `ServiceProvider::addProviderToBootstrapFile` (#49494) * Adds missing end-of-line * Apply fixes from StyleCI --------- Co-authored-by: StyleCI Bot commit 1181caba840eb490939999c9ac00799ab99636a1 Author: Graham Campbell Date: Tue Dec 26 14:24:43 2023 +0000 Correct suggested `pda/pheanstalk` version (#49487) commit e01fd834d7a5657a6d6a05a4792067c630ce41c0 Author: StyleCI Bot Date: Mon Dec 25 01:15:21 2023 +0000 Apply fixes from StyleCI commit ef95e4164ed1a20d872df7c461fdac523dbf45ae Merge: 84bab4c777 b184c5ec55 Author: Graham Campbell Date: Mon Dec 25 01:14:45 2023 +0000 Merge branch '10.x' commit b184c5ec5572aaa21fdc13e0c59a209315b90c9d Author: StyleCI Bot Date: Mon Dec 25 01:11:56 2023 +0000 Apply fixes from StyleCI commit 791d44c7f7844ebfc2bf2620b61b44eab49627e6 Merge: 8e6e056935 dc2e925b2d Author: Graham Campbell Date: Mon Dec 25 01:10:57 2023 +0000 Merge branch '9.x' into 10.x commit dc2e925b2d75fc2673a5919c7ad727d68059c08e Author: StyleCI Bot Date: Mon Dec 25 01:10:04 2023 +0000 Apply fixes from StyleCI commit 8e6e056935785727f9b369f4d4bcdd0ed6c6560f Author: Ahmed shamim Date: Sat Dec 23 21:29:25 2023 +0600 [10.x] Add 'Roundrobin' Symfony mailer transport driver (#49435) * add roundrobin symfony mailer transport driver * add test for roundrobin transport driver * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Mior Muhammad Zaki commit a77d6b60fd84ea399c639ce35fd042d0fc5152e2 Author: Andrea Marco Sartori Date: Sun Dec 24 01:26:29 2023 +1000 [10.x] Allow testing prompts validation (#49447) * [10.x] Allow testing prompts validation * [10.x] Move test * [10.x] Throw exception when testing prompt validation * Update ConfiguresPrompts.php --------- Co-authored-by: Taylor Otwell commit 90ed27d8d29cef3523bae195334bbb752eea3ef6 Author: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com> Date: Sat Dec 23 20:39:08 2023 +0530 [10.x] Fix use statement compilation in Blade templates (#49479) * Fix use statement compilation in Blade templates * added one more failing test * fix * fix typo commit 8837c1f9ab8db1634623ecc4ccdcbe688109eeb7 Author: Rogelio Jacinto Date: Sat Dec 23 08:07:45 2023 -0700 [10.x] Avoid TypeError when using json validation rule when PHP < 8.3 (#49474) * test: validateJson should return false when value is null Fails with Laravel Framework 10.38.2 in PHP < 8.3, introduced in #49413 * fix: validateJson should return false when value is null Return false when $value is null. Avoid TypeError: json_validate(): Argument #1 ($json) must be of type string, null given, when using symfony/polyfill-php83 in PHP < 8.3. Avoid deprecation warning: json_validate(): Passing null to parameter #1 ($json) of type string is deprecated, when using PHP 8.3. --------- Co-authored-by: Rogelio Jacinto commit 6c53284a09b77dc7e958a41fd6da998c68ef997e Author: taylorotwell Date: Fri Dec 22 16:42:26 2023 +0000 Update facade docblocks commit 74441ad6e7f8c2846d952aa08685b66b3660b2a2 Author: StyleCI Bot Date: Fri Dec 22 16:41:49 2023 +0000 Apply fixes from StyleCI commit f331b5c344d3e4c4595a7c9512a63ccb96d8e81e Author: Di Date: Fri Dec 22 17:41:29 2023 +0100 [10.x] Dynamic `maxTries` for queued jobs (#49473) * Added new method getJobTries to Queue.php to allow dynamic retry attempts on a job level * formatting --------- Co-authored-by: Taylor Otwell commit 84bab4c777c8cd0c13b7be16d15fd4e903ece477 Author: Nuno Maduro Date: Fri Dec 22 16:05:32 2023 +0000 Uses stable version of Sanctum (#49472) commit f510d4b3ef6449328ac7a099787e779dd297be69 Author: driesvints Date: Fri Dec 22 14:42:33 2023 +0000 Update CHANGELOG commit 43da808391da3540d44a8dfeb4e46da4ad8f5723 Author: Taylor Otwell Date: Fri Dec 22 08:39:10 2023 -0600 patch commit 1d41da5e8b00a66870e06cbaeca10333b69c0b0c Author: driesvints Date: Fri Dec 22 14:37:58 2023 +0000 Update CHANGELOG commit 047edbc3a370ccf8be58f35f2e262d59210a3852 Author: StyleCI Bot Date: Thu Dec 21 15:33:23 2023 +0000 Apply fixes from StyleCI commit d4b727b0223e8dc41c043270e0e26ef165bf28fa Author: Ahmed Badawy <63416196+aabadawy@users.noreply.github.com> Date: Thu Dec 21 17:32:51 2023 +0200 [10.x] Fix missing Validation rules not working with nested array (#49449) * support missing rule with nested array data * fix style commit 0c68ae1ce4c9d02c9ecb9ca0f76652ef480999d5 Author: Tim MacDonald Date: Fri Dec 22 02:32:11 2023 +1100 [10.x] Allow deprecation logging in tests (#49457) * Allow more config to be controllable * Introduce configurable value * Use env, not config * make mocks strict commit 1a84e34a2a910c6da98f74b44992403e2161792f Author: Tyler Date: Thu Dec 21 09:30:21 2023 -0600 [10.x] Illuminate\Filesystem\join_paths(): Argument #2 must be of type string, null given (#49467) * + Added test that reproduces bug * + Added fix that resolves bug * Update src/Illuminate/Filesystem/functions.php Co-authored-by: Mior Muhammad Zaki * Update src/Illuminate/Filesystem/functions.php Co-authored-by: Mior Muhammad Zaki * ~ Don't explicitly test for null --------- Co-authored-by: Mior Muhammad Zaki commit 16526082c6692db1984580cb70d2b7811e99ba4d Author: bastien-phi Date: Thu Dec 21 15:17:35 2023 +0100 Simplify Arr::dot (#49461) commit e5b60d9e16b31ca31ad59870f9f6c7045f7ebf80 Merge: 4acf1df8ea c38ff56058 Author: Mior Muhammad Zaki Date: Thu Dec 21 12:51:19 2023 +0800 Merge branch '10.x' Signed-off-by: Mior Muhammad Zaki commit c38ff56058b74beba86fcfe326751a8f54c46b24 Author: Mior Muhammad Zaki Date: Thu Dec 21 09:49:42 2023 +0800 [10.x] Add `conflict` for `doctrine/dbal` to `illuminate/database` (#49456) Signed-off-by: Mior Muhammad Zaki commit ced4689f495213e9d23995b36098f12a802cc15b Author: Taylor Otwell Date: Wed Dec 20 08:52:12 2023 -0600 patch commit 26281bdf2ddec7aa62cda706370ac76640ca9aea Author: Mior Muhammad Zaki Date: Wed Dec 20 22:50:30 2023 +0800 [10.x] Move `Illuminate\Foundation\Application::joinPaths()` to `Illuminate\Filesystem\join_paths()` (#49433) * Move `Illuminate\Foundation\Application::joinPaths()` to `Illuminate\Filesystem\join_paths()` Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * formatting --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Taylor Otwell commit 6c5af179becd13c300eeff1aef417af3ff5dec5c Author: Tom Janssen Date: Wed Dec 20 15:40:22 2023 +0100 [10.x] Add method to create request (#49446) * add method to create request * formatting --------- Co-authored-by: Taylor Otwell commit c41b5b5866952385b716e53c9400744122d301e8 Author: Tim MacDonald Date: Thu Dec 21 01:28:17 2023 +1100 [10.x] Fix installing DBAL on a fresh app (#49438) * Update composer.json * Update composer.json Co-authored-by: Dries Vints --------- Co-authored-by: Taylor Otwell Co-authored-by: Dries Vints commit cec9e981a52f9a4e2c2b823dc45d2ce66fb7404c Author: Taylor Otwell Date: Wed Dec 20 08:23:33 2023 -0600 Revert "[10.x] Drop the primary key if it exists when adding a new primary key (#49392)" (#49448) This reverts commit be24812761ab23deaea9b8fe1b28933589e89a07. commit b0ed9e1a601d8cbf122c815198d86b333a881f11 Author: Nuno Maduro Date: Tue Dec 19 15:11:55 2023 +0000 [10.x] Adds support for parse callbacks from anonymous classes (#49432) * Adds support for anonymous classes callbacks * Apply fixes from StyleCI --------- Co-authored-by: StyleCI Bot commit ee059bbd5c57995286fd7902075d4658a5db24ef Author: driesvints Date: Tue Dec 19 15:03:53 2023 +0000 Update CHANGELOG commit 531732a17e4d0fa4fc4fb987a72abbdb93537d3a Author: Taylor Otwell Date: Tue Dec 19 08:59:00 2023 -0600 version commit 11580fa3d0991bd19fabd94fb7e8c62766dce3ce Author: taylorotwell Date: Tue Dec 19 14:47:56 2023 +0000 Update facade docblocks commit 4acf1df8ea2b2a1ccb800e44af23f5c3d819a3e1 Author: Julius Kiekbusch Date: Tue Dec 19 15:47:43 2023 +0100 Update session.php (#49430) commit f58a733ea4686df625f2e44ec39e3b2200c88909 Author: Shane Date: Tue Dec 19 22:47:26 2023 +0800 [10.x] Add Conditionable to Pipeline (#49429) * [10.x] Add Conditionable to Pipeline * fix test commit 3e6b81ec8183b5746fe3e42ebec221130a5604fa Author: Mior Muhammad Zaki Date: Tue Dec 19 22:46:56 2023 +0800 [10.x] Test Improvements (#49426) * [10.x] Test Improvements Verify #49389 Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot commit 67022e6a260114770cc43e6ac27b9964e0e628a6 Author: Mior Muhammad Zaki Date: Tue Dec 19 19:11:37 2023 +0800 [11.x] Fixes changes to application skeleton (#49425) * [11.x] Fixes changes to application skeleton Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot commit 631e418aeb3120febe2bed4393fce2d26a49d3c0 Author: Taylor Otwell Date: Mon Dec 18 10:14:14 2023 -0600 update drivers commit 78fa6f2c758e929233889103e2f709d0017c7598 Author: Philip Iezzi <2759561+onlime@users.noreply.github.com> Date: Mon Dec 18 16:56:07 2023 +0100 [10.x] Add `MailMessage` helpers for plain text email notifications (#49407) * Add helper methods to MailMessage for plain text view and raw body * Improve MailMessage view helpers, respecting previous view value * formatting --------- Co-authored-by: Taylor Otwell commit 393476a7e369394b99615f9288393dee539439f1 Author: Hafez Divandari Date: Mon Dec 18 19:13:11 2023 +0330 [10.x] Improve schema builder `getColumns()` method (#49416) * wip * fix tests * wip commit be24812761ab23deaea9b8fe1b28933589e89a07 Author: Kieran Date: Mon Dec 18 14:28:22 2023 +0000 [10.x] Drop the primary key if it exists when adding a new primary key (#49392) * Drop the primary key if it exists when adding a new primary key * Fix failing tests commit bedb8c238d36cdb50ccdc05ea417a402a69f8e0d Author: Mior Muhammad Zaki Date: Sun Dec 17 23:38:54 2023 +0800 [11.x] Export `cache`, `jobs`, `failed_jobs` or `sessions` migration schema if the file doesn't exists based on new skeleton. (#49410) * [11.x] Export `cache`, `jobs`, `failed_jobs` or `sessions` migration schema if the file doesn't exists based on new skeleton. Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki commit 56cf2018490ea0193f50bf2bff59654a46cf52d8 Author: AJ <60591772+devajmeireles@users.noreply.github.com> Date: Sun Dec 17 12:34:19 2023 -0300 [10.x] Introducing `isEmpty` and `isNotEmpty` to `ComponentAttributeBag` (#49408) * code * formatting --------- Co-authored-by: Taylor Otwell commit ddade2c045f983a823420dbef462c98e8ac475c8 Author: James <2078364+gtjamesa@users.noreply.github.com> Date: Sun Dec 17 15:27:37 2023 +0000 Use native json_validate (#49413) commit 9cee0e6910171f237f47e2b6f2071e8b0370cb9d Author: Taylor Otwell Date: Sat Dec 16 14:39:49 2023 -0600 update default cache and session drivers to database commit 5d9cf9ca81f5985b90dcbaa4a9a3f81c2f471e7a Author: Daniele Ambrosino Date: Sat Dec 16 17:47:47 2023 +0100 Add SQLite support for `whereJsonContains` method (#49401) commit 4861acf6fb1fe2c5ca99e1f5ef8982cd2004ca91 Author: Lucas-Schmukas <89397256+Lucas-Schmukas@users.noreply.github.com> Date: Sat Dec 16 16:43:47 2023 +0100 fix parameter type in ManagesFrequencies.php (#49399) The parameter type in the PHPdoc should be string according to the default value of the parameter. Other methods of the class with similar logic are using also string for $time parameter. commit ece8151ee3d3e471dae4367b629c3c461f414915 Author: Chris Morrell Date: Sat Dec 16 10:43:19 2023 -0500 Only set defaultCasters if not previously set (#49402) commit 2a6437217549046019ca16e06215bf6df94e0544 Author: Marco van Oort Date: Sat Dec 16 16:42:44 2023 +0100 Fix assertStatus() parameter order (#49404) commit 03dbd3ab494419d9c72b80e1d16c1663238c9be4 Author: bastien-phi Date: Fri Dec 15 19:25:00 2023 +0100 [10.x] Improve `Arr::dot` performance (#49386) * Improve Arr::dot performance * Add some tests for Arr::dot * Fix cs * Use array_merge spread to increase speed and decrease memory usage * Fix ordering * Change assertEquals to assertSame in order to verify that ordering is consistant * Explicit array creation * Fix cs commit 31bc1bdf1ed7fdd59b07cfe7f82cda481b1b39ce Author: Jared Lewis <17649602+jrd-lewis@users.noreply.github.com> Date: Fri Dec 15 10:13:34 2023 -0500 [10.x] Add Blade `@session` Directive (#49339) * Add Blade `@session` Directive * Fix test * Refactor to `$__sessionPrevious` array for nesting * Unset `$__sessionPrevious` if it is set and empty. * use value --------- Co-authored-by: Taylor Otwell commit c14b82af7d81712272b1e1245053364b041860ee Author: Evan Burrell <44360092+evan-burrell@users.noreply.github.com> Date: Fri Dec 15 14:09:57 2023 +0000 Dynamo Batch Repository - Match Default Horizon Sort (#49391) commit 6c6f84f3d73845edbe8faf8d584485ef3104cb9b Author: Joostb Date: Fri Dec 15 15:08:40 2023 +0100 [10.x] Fix bug in ArrayLock getCurrentOwner (#49393) * Fix bug in ArrayLock getCurrentOwner After making isOwnedByCurrentProcess public, the function getCurrentOwner can be called without checking if the lock exists first. * Fix whitespace issues --------- Co-authored-by: Joost commit 56e941a994a1be4d1731b6827066292a1878c659 Author: José Rodríguez Date: Fri Dec 15 15:07:14 2023 +0100 fix: Resets static $data before gather app info (#49387) Co-authored-by: Jose Rodriguez commit c1ad796aaf2e30be762eaf26e0987b188867820e Author: Guillaume Date: Fri Dec 15 15:05:04 2023 +0100 [10.x] Add `charset` and `collation` method to `Blueprint` (#49396) * Add charset and collation method to Blueprint * formatting * rename var --------- Co-authored-by: Taylor Otwell commit 59f184efac7d9cc59a3ee06a6d881d87f00f9533 Author: Mohammad ALTAWEEL Date: Thu Dec 14 23:09:33 2023 +0300 [10.x] Fix primary key creation for MySQL with `sql_require_primary_key` enabled (#49374) * Fix inline primary key creation for MySQL * Fix styleci issue * Correct comment sentence * Refactor * Add tests * formatting --------- Co-authored-by: Taylor Otwell commit c80d3ebf950ffa7106e24956cc469da1dd4cb442 Author: Perry van der Meer <11609290+PerryvanderMeer@users.noreply.github.com> Date: Thu Dec 14 17:13:49 2023 +0100 Update TableCommand.php (#49379) commit 2519088c4b88b05159a331524589c894419925e4 Author: Saya <379924+chu121su12@users.noreply.github.com> Date: Thu Dec 14 23:03:17 2023 +0800 [10.x] Update import & typo (#49370) * Update BusFake.php * Update UniqueJobTest.php commit baf075f3ae14059e0bc3aab65f3dc6e5039470a4 Author: driesvints Date: Thu Dec 14 08:52:43 2023 +0000 Update CHANGELOG commit 4edd5e24e2aecfc71fc8c90cbac1cf13a88c5737 Author: driesvints Date: Thu Dec 14 08:51:50 2023 +0000 Update CHANGELOG commit 082345d76fc6a55b649572efe10b11b03e279d24 Author: Taylor Otwell Date: Tue Oct 3 08:02:30 2023 -0500 patch commit 850d3c3cad674db29d47d3f3e8621146f1a63213 Author: Taylor Otwell Date: Tue Oct 3 08:01:42 2023 -0500 wip commit 4689321b53e7d55139099c866b5e0e166d325d45 Author: Michal Hubatka Date: Tue Oct 3 15:01:00 2023 +0200 Support for phpredis 6.0.0 (#48362) (#48380) Co-authored-by: Steff Missot --- .github/workflows/databases.yml | 12 +- .github/workflows/pull-requests.yml | 2 +- .github/workflows/queues.yml | 4 +- .github/workflows/releases.yml | 64 + .github/workflows/tests.yml | 22 +- .styleci.yml | 2 +- CHANGELOG.md | 2 +- README.md | 2 +- composer.json | 51 +- config-stubs/app.php | 126 ++ config/app.php | 22 +- config/auth.php | 24 +- config/broadcasting.php | 25 +- config/cache.php | 14 +- config/database.php | 35 +- config/filesystems.php | 6 +- config/logging.php | 17 +- config/mail.php | 30 +- config/queue.php | 17 +- config/services.php | 7 - config/session.php | 46 +- src/Illuminate/Auth/Access/Gate.php | 8 +- .../Auth/Notifications/ResetPassword.php | 4 +- src/Illuminate/Auth/TokenGuard.php | 2 +- .../Broadcasters/AblyBroadcaster.php | 11 + src/Illuminate/Bus/Batchable.php | 6 +- src/Illuminate/Bus/DynamoBatchRepository.php | 1 + src/Illuminate/Bus/PendingBatch.php | 74 +- src/Illuminate/Cache/ArrayLock.php | 4 + src/Illuminate/Cache/CacheManager.php | 33 +- src/Illuminate/Cache/DynamoDbStore.php | 8 +- src/Illuminate/Cache/Events/CacheEvent.php | 11 +- src/Illuminate/Cache/Events/CacheHit.php | 5 +- src/Illuminate/Cache/Events/KeyWritten.php | 5 +- src/Illuminate/Cache/FileStore.php | 8 +- src/Illuminate/Cache/RateLimiter.php | 17 +- src/Illuminate/Cache/RedisTagSet.php | 6 +- src/Illuminate/Cache/RedisTaggedCache.php | 20 +- src/Illuminate/Cache/Repository.php | 39 +- src/Illuminate/Collections/Arr.php | 76 +- src/Illuminate/Collections/Collection.php | 26 +- src/Illuminate/Collections/Enumerable.php | 3 +- src/Illuminate/Collections/LazyCollection.php | 44 +- src/Illuminate/Config/Repository.php | 101 ++ .../Console/Concerns/ConfiguresPrompts.php | 90 +- .../Console/Concerns/CreatesMatchingTest.php | 5 +- .../Console/Concerns/InteractsWithSignals.php | 6 +- src/Illuminate/Console/GeneratorCommand.php | 5 +- .../Console/MigrationGeneratorCommand.php | 4 +- .../Console/PromptValidationException.php | 9 + .../Console/Scheduling/ManagesFrequencies.php | 2 +- src/Illuminate/Console/composer.json | 2 +- src/Illuminate/Contracts/Auth/Access/Gate.php | 8 +- .../Contracts/Encryption/Encrypter.php | 14 + .../Contracts/Filesystem/Filesystem.php | 29 + src/Illuminate/Contracts/Mail/Mailer.php | 10 + .../Contracts/Process/ProcessResult.php | 16 + .../Contracts/Translation/Translator.php | 2 +- src/Illuminate/Cookie/CookieValuePrefix.php | 12 +- .../Cookie/Middleware/EncryptCookies.php | 14 +- .../Database/Concerns/ManagesTransactions.php | 4 + src/Illuminate/Database/Connection.php | 144 +- .../Database/Connectors/ConnectionFactory.php | 3 + .../Database/Connectors/MariaDbConnector.php | 32 + .../Database/Connectors/MySqlConnector.php | 138 +- .../Console/DatabaseInspectionCommand.php | 206 +-- src/Illuminate/Database/Console/DbCommand.php | 13 + .../Console/Migrations/MigrateCommand.php | 8 +- .../Database/Console/MonitorCommand.php | 6 +- .../Database/Console/PruneCommand.php | 15 +- .../Database/Console/ShowCommand.php | 122 +- .../Database/Console/ShowModelCommand.php | 83 +- .../Database/Console/TableCommand.php | 120 +- .../Database/DBAL/TimestampType.php | 94 -- src/Illuminate/Database/DatabaseManager.php | 107 +- .../Database/DatabaseTransactionsManager.php | 3 +- .../Eloquent/Attributes/ObservedBy.php | 19 + .../Database/Eloquent/Attributes/ScopedBy.php | 19 + src/Illuminate/Database/Eloquent/Builder.php | 5 +- .../Database/Eloquent/Casts/AsArrayObject.php | 2 +- .../Eloquent/Concerns/GuardsAttributes.php | 2 +- .../Eloquent/Concerns/HasAttributes.php | 29 +- .../Database/Eloquent/Concerns/HasEvents.php | 27 + .../Eloquent/Concerns/HasGlobalScopes.php | 48 +- .../Concerns/QueriesRelationships.php | 28 +- .../Eloquent/Relations/BelongsToMany.php | 41 + .../Eloquent/Relations/HasManyThrough.php | 37 + .../Eloquent/Relations/HasOneOrMany.php | 28 + .../Eloquent/Relations/MorphPivot.php | 2 + .../Eloquent/Relations/MorphToMany.php | 10 + .../Database/Eloquent/Relations/Pivot.php | 2 +- src/Illuminate/Database/Grammar.php | 52 +- src/Illuminate/Database/MariaDbConnection.php | 94 ++ .../Database/Migrations/Migrator.php | 29 +- src/Illuminate/Database/MySqlConnection.php | 24 +- .../PDO/Concerns/ConnectsToDatabase.php | 28 - src/Illuminate/Database/PDO/Connection.php | 186 --- src/Illuminate/Database/PDO/MySqlDriver.php | 19 - .../Database/PDO/PostgresDriver.php | 19 - src/Illuminate/Database/PDO/SQLiteDriver.php | 19 - .../Database/PDO/SqlServerConnection.php | 152 -- .../Database/PDO/SqlServerDriver.php | 30 - .../Database/PostgresConnection.php | 11 - src/Illuminate/Database/Query/Builder.php | 156 +- .../Database/Query/Grammars/Grammar.php | 107 ++ .../Query/Grammars/MariaDbGrammar.php | 19 + .../Database/Query/Grammars/MySqlGrammar.php | 102 ++ .../Query/Grammars/PostgresGrammar.php | 26 + .../Database/Query/Grammars/SQLiteGrammar.php | 57 + .../Query/Grammars/SqlServerGrammar.php | 31 + .../Database/Query/JoinLateralClause.php | 8 + .../Query/Processors/MariaDbProcessor.php | 8 + .../Query/Processors/MySqlProcessor.php | 17 +- .../Query/Processors/PostgresProcessor.php | 17 +- .../Database/Query/Processors/Processor.php | 13 - .../Query/Processors/SQLiteProcessor.php | 30 +- .../Query/Processors/SqlServerProcessor.php | 15 - src/Illuminate/Database/SQLiteConnection.php | 25 +- src/Illuminate/Database/Schema/Blueprint.php | 159 +- src/Illuminate/Database/Schema/Builder.php | 90 +- .../Database/Schema/ColumnDefinition.php | 14 +- .../Database/Schema/Grammars/ChangeColumn.php | 238 --- .../Database/Schema/Grammars/Grammar.php | 58 +- .../Schema/Grammars/MariaDbGrammar.php | 87 + .../Database/Schema/Grammars/MySqlGrammar.php | 228 +-- .../Schema/Grammars/PostgresGrammar.php | 226 +-- .../Database/Schema/Grammars/RenameColumn.php | 93 -- .../Schema/Grammars/SQLiteGrammar.php | 360 ++-- .../Schema/Grammars/SqlServerGrammar.php | 186 +-- .../Database/Schema/MariaDbBuilder.php | 8 + .../Database/Schema/MariaDbSchemaState.php | 18 + .../Database/Schema/MySqlBuilder.php | 28 - .../Database/Schema/PostgresBuilder.php | 68 +- .../Database/Schema/SQLiteBuilder.php | 26 +- .../Database/Schema/SqlServerBuilder.php | 110 +- .../Database/SqlServerConnection.php | 11 - src/Illuminate/Database/composer.json | 3 +- src/Illuminate/Encryption/Encrypter.php | 81 +- .../Encryption/EncryptionServiceProvider.php | 6 +- src/Illuminate/Filesystem/Filesystem.php | 2 +- .../Filesystem/FilesystemAdapter.php | 2 +- src/Illuminate/Filesystem/LockableFile.php | 7 +- src/Illuminate/Filesystem/composer.json | 5 +- src/Illuminate/Filesystem/functions.php | 25 + src/Illuminate/Foundation/Application.php | 36 +- .../Foundation/Bootstrap/HandleExceptions.php | 71 +- .../Bootstrap/RegisterProviders.php | 12 + .../Foundation/Bus/PendingChain.php | 24 +- .../Configuration/ApplicationBuilder.php | 75 +- .../Foundation/Configuration/Exceptions.php | 26 +- .../Foundation/Configuration/Middleware.php | 225 ++- .../Foundation/Console/AboutCommand.php | 14 + .../Foundation/Console/ApiInstallCommand.php | 2 +- .../Console/BroadcastingInstallCommand.php | 46 +- .../Foundation/Console/ClassMakeCommand.php | 70 + .../Foundation/Console/CliDumper.php | 2 + .../Console/ConfigPublishCommand.php | 4 +- .../Foundation/Console/EnumMakeCommand.php | 109 ++ .../Console/EventGenerateCommand.php | 7 + .../Console/ExceptionMakeCommand.php | 21 + .../Console/InterfaceMakeCommand.php | 65 + src/Illuminate/Foundation/Console/Kernel.php | 8 +- .../Foundation/Console/ServeCommand.php | 11 + .../Console/StorageUnlinkCommand.php | 53 + .../Foundation/Console/StubPublishCommand.php | 5 + .../Foundation/Console/TestMakeCommand.php | 31 +- .../Foundation/Console/TraitMakeCommand.php | 65 + .../Console/VendorPublishCommand.php | 21 + .../Foundation/Console/ViewMakeCommand.php | 20 +- .../Foundation/Console/stubs/api-routes.stub | 11 - .../Console/stubs/broadcasting-routes.stub | 11 - .../Console/stubs/class.invokable.stub | 22 + .../Foundation/Console/stubs/class.stub | 14 + .../Foundation/Console/stubs/echo-js.stub | 13 +- .../Foundation/Console/stubs/enum.backed.stub | 8 + .../Foundation/Console/stubs/enum.stub | 8 + .../Foundation/Console/stubs/interface.stub | 8 + .../Foundation/Console/stubs/job.queued.stub | 1 - .../Foundation/Console/stubs/routes.stub | 11 - .../Foundation/Console/stubs/trait.stub | 8 + .../Foundation/Events/DiagnosingHealth.php | 8 + .../Foundation/Events/DiscoverEvents.php | 2 +- .../Foundation/Exceptions/Handler.php | 28 +- .../Foundation/Http/FormRequest.php | 18 +- src/Illuminate/Foundation/Http/Kernel.php | 15 + .../Middleware/Concerns/ExcludesPaths.php | 44 + .../PreventRequestsDuringMaintenance.php | 58 +- .../Http/Middleware/TrimStrings.php | 2 + .../Http/Middleware/VerifyCsrfToken.php | 46 +- .../Providers/ArtisanServiceProvider.php | 58 + .../Providers/FoundationServiceProvider.php | 10 +- .../Providers/EventServiceProvider.php | 20 +- .../Providers/RouteServiceProvider.php | 10 +- .../InteractsWithTestCaseLifecycle.php | 291 ++++ .../Testing/Concerns/MakesHttpRequests.php | 13 +- .../Foundation/Testing/DatabaseTruncation.php | 2 +- .../Testing/LazilyRefreshDatabase.php | 13 +- .../Foundation/Testing/TestCase.php | 246 +-- src/Illuminate/Foundation/Vite.php | 22 +- src/Illuminate/Foundation/helpers.php | 2 +- .../Foundation/resources/health-up.blade.php | 52 + src/Illuminate/Http/Client/Factory.php | 22 +- src/Illuminate/Http/Client/PendingRequest.php | 20 +- src/Illuminate/Http/Middleware/TrustHosts.php | 41 +- .../Http/Middleware/TrustProxies.php | 71 +- .../ConditionallyLoadsAttributes.php | 16 +- src/Illuminate/Http/composer.json | 4 +- src/Illuminate/Log/LogManager.php | 34 +- src/Illuminate/Mail/MailManager.php | 44 + src/Illuminate/Mail/Mailable.php | 8 +- src/Illuminate/Mail/Mailer.php | 25 +- src/Illuminate/Mail/PendingMail.php | 11 + .../Mail/Transport/LogTransport.php | 40 +- .../Mail/Transport/ResendTransport.php | 129 ++ src/Illuminate/Mail/composer.json | 1 + .../resources/views/html/message.blade.php | 2 +- .../Notifications/Messages/MailMessage.php | 15 + .../resources/views/simple-tailwind.blade.php | 8 +- .../resources/views/tailwind.blade.php | 26 +- src/Illuminate/Pipeline/Pipeline.php | 3 + src/Illuminate/Process/PendingProcess.php | 8 +- .../Queue/Console/BatchesTableCommand.php | 21 + src/Illuminate/Queue/Console/ClearCommand.php | 2 + .../Queue/Console/FailedTableCommand.php | 21 + .../Queue/Console/ForgetFailedCommand.php | 4 +- src/Illuminate/Queue/Console/TableCommand.php | 21 + src/Illuminate/Queue/DatabaseQueue.php | 3 +- src/Illuminate/Queue/Events/JobQueued.php | 30 +- src/Illuminate/Queue/Events/JobQueueing.php | 70 + src/Illuminate/Queue/InteractsWithQueue.php | 91 + src/Illuminate/Queue/Jobs/FakeJob.php | 76 + src/Illuminate/Queue/Queue.php | 64 +- src/Illuminate/Queue/QueueServiceProvider.php | 4 +- src/Illuminate/Queue/composer.json | 2 +- .../Redis/Connectors/PhpRedisConnector.php | 8 +- .../Routing/Console/ControllerMakeCommand.php | 14 +- .../Routing/Controllers/Middleware.php | 24 +- src/Illuminate/Routing/ResponseFactory.php | 15 + src/Illuminate/Routing/Route.php | 11 +- src/Illuminate/Routing/UrlGenerator.php | 4 +- src/Illuminate/Routing/composer.json | 2 +- .../Session/Console/SessionTableCommand.php | 17 + src/Illuminate/Session/Store.php | 11 + src/Illuminate/Support/Carbon.php | 15 +- src/Illuminate/Support/DefaultProviders.php | 2 +- src/Illuminate/Support/Facades/App.php | 3 +- src/Illuminate/Support/Facades/Cache.php | 2 +- src/Illuminate/Support/Facades/Config.php | 5 + src/Illuminate/Support/Facades/Crypt.php | 3 + src/Illuminate/Support/Facades/DB.php | 9 +- src/Illuminate/Support/Facades/Gate.php | 4 +- src/Illuminate/Support/Facades/Http.php | 5 +- src/Illuminate/Support/Facades/Lang.php | 2 +- src/Illuminate/Support/Facades/Log.php | 20 +- src/Illuminate/Support/Facades/Mail.php | 1 + .../Support/Facades/Notification.php | 17 + src/Illuminate/Support/Facades/Pipeline.php | 2 + src/Illuminate/Support/Facades/Process.php | 2 +- src/Illuminate/Support/Facades/Queue.php | 2 + .../Support/Facades/RateLimiter.php | 1 + src/Illuminate/Support/Facades/Response.php | 1 + src/Illuminate/Support/Facades/Schema.php | 8 +- src/Illuminate/Support/Facades/Session.php | 1 + src/Illuminate/Support/Facades/Storage.php | 6 +- src/Illuminate/Support/Facades/Vite.php | 1 + src/Illuminate/Support/Number.php | 48 +- src/Illuminate/Support/Once.php | 100 ++ src/Illuminate/Support/Onceable.php | 78 + src/Illuminate/Support/ServiceProvider.php | 4 +- src/Illuminate/Support/Sleep.php | 2 +- src/Illuminate/Support/Str.php | 113 +- src/Illuminate/Support/Stringable.php | 72 +- .../Support/Testing/Fakes/BusFake.php | 1 + .../Support/Testing/Fakes/MailFake.php | 33 +- .../Support/Testing/Fakes/PendingMailFake.php | 11 + .../Support/Testing/Fakes/QueueFake.php | 18 +- src/Illuminate/Support/composer.json | 5 +- src/Illuminate/Support/helpers.php | 38 + .../Testing/Concerns/RunsInParallel.php | 11 +- .../Testing/Constraints/ArraySubset.php | 15 +- src/Illuminate/Testing/PendingCommand.php | 3 + src/Illuminate/Testing/TestResponse.php | 19 +- src/Illuminate/Testing/TestView.php | 12 + src/Illuminate/Testing/composer.json | 6 +- .../PotentiallyTranslatedString.php | 2 +- src/Illuminate/Translation/Translator.php | 6 +- .../Validation/Concerns/FormatsMessages.php | 17 +- .../Concerns/ValidatesAttributes.php | 8 +- .../Validation/ConditionalRules.php | 8 +- src/Illuminate/Validation/Rule.php | 10 +- src/Illuminate/Validation/Rules/Enum.php | 62 +- src/Illuminate/Validation/Rules/Password.php | 29 +- src/Illuminate/Validation/Validator.php | 9 +- src/Illuminate/Validation/composer.json | 2 +- .../View/Compilers/BladeCompiler.php | 1 + .../View/Compilers/Concerns/CompilesEchos.php | 4 + .../Compilers/Concerns/CompilesSessions.php | 37 + .../Concerns/CompilesUseStatements.php | 2 +- src/Illuminate/View/ComponentAttributeBag.php | 22 +- src/Illuminate/View/ComponentSlot.php | 20 + tests/Auth/AuthAccessGateTest.php | 41 + ...ficationNotificationHandleFunctionTest.php | 5 +- tests/Bus/BusPendingBatchTest.php | 136 +- tests/Cache/CacheArrayStoreTest.php | 32 + tests/Cache/CacheEventsTest.php | 62 +- tests/Cache/CacheManagerTest.php | 4 +- tests/Cache/CacheRateLimiterTest.php | 19 +- tests/Config/RepositoryTest.php | 78 + tests/Database/DatabaseConnectionTest.php | 12 + tests/Database/DatabaseConnectorTest.php | 13 +- ...baseEloquentBelongsToManyAggregateTest.php | 12 + ...EloquentBelongsToManyCreateOrFirstTest.php | 4 + ...oquentBelongsToManyWithoutTouchingTest.php | 65 + .../Database/DatabaseEloquentBuilderTest.php | 51 + .../DatabaseEloquentGlobalScopesTest.php | 58 + ...loquentHasManyThroughCreateOrFirstTest.php | 7 + .../DatabaseEloquentLocalScopesTest.php | 26 + tests/Database/DatabaseEloquentModelTest.php | 498 +++--- .../DatabaseEloquentWithCastsTest.php | 17 + tests/Database/DatabaseMariaDbBuilderTest.php | 48 + .../Database/DatabaseMariaDbProcessorTest.php | 32 + .../DatabaseMariaDbQueryGrammarTest.php | 31 + .../DatabaseMariaDbSchemaBuilderTest.php | 52 + .../DatabaseMariaDbSchemaGrammarTest.php | 1467 +++++++++++++++++ .../DatabaseMariaDbSchemaStateTest.php | 87 + .../DatabaseMySqlSchemaGrammarTest.php | 50 +- .../DatabasePostgresSchemaGrammarTest.php | 59 +- tests/Database/DatabaseQueryBuilderTest.php | 269 ++- .../DatabaseSQLiteSchemaGrammarTest.php | 83 +- .../Database/DatabaseSchemaBlueprintTest.php | 52 +- .../DatabaseSchemaBuilderIntegrationTest.php | 34 + tests/Database/DatabaseSchemaBuilderTest.php | 17 +- .../DatabaseSqlServerSchemaGrammarTest.php | 78 +- tests/Encryption/EncrypterTest.php | 12 + tests/Filesystem/JoinPathsHelperTest.php | 38 + .../Bootstrap/HandleExceptionsTest.php | 162 +- .../Configuration/ExceptionsTest.php | 39 + .../Configuration/MiddlewareTest.php | 283 ++++ .../FoundationApplicationBuilderTest.php | 44 + .../Foundation/FoundationFormRequestTest.php | 34 + tests/Foundation/FoundationViteTest.php | 51 +- .../DatabaseTransactionsManagerTest.php | 17 + tests/Http/HttpClientTest.php | 96 ++ tests/Http/JsonResourceTest.php | 27 + tests/Integration/Cache/RedisStoreTest.php | 22 + .../Console/PromptsValidationTest.php | 106 ++ .../Scheduling/ScheduleListCommandTest.php | 2 +- .../Scheduling/SubMinuteSchedulingTest.php | 6 +- .../ConfigureCustomDoctrineTypeTest.php | 114 -- .../Database/DBAL/TimestampTypeTest.php | 85 - .../Database/DatabaseCacheStoreTest.php | 14 + .../Database/DatabaseConnectionsTest.php | 107 ++ .../Database/DatabaseSchemaBlueprintTest.php | 304 ++-- .../Database/DatabaseSchemaBuilderTest.php | 8 +- .../EloquentEagerLoadingLimitTest.php | 183 ++ .../EloquentModelEncryptedDirtyTest.php | 93 ++ .../Database/EloquentModelLoadMaxTest.php | 104 ++ .../Database/EloquentModelLoadMinTest.php | 104 ++ .../Database/EloquentModelLoadSumTest.php | 103 ++ .../EloquentMorphEagerLoadingTest.php | 15 + .../Database/EloquentWhereHasMorphTest.php | 2 +- .../Database/Fixtures/TinyInteger.php | 38 - ...DatabaseEloquentMariaDbIntegrationTest.php | 84 + ...seEmulatePreparesMariaDbConnectionTest.php | 21 + .../MariaDb/DatabaseMariaDbConnectionTest.php | 147 ++ ...aDbSchemaBuilderAlterTableWithEnumTest.php | 68 + .../DatabaseMariaDbSchemaBuilderTest.php | 32 + .../Database/MariaDb/EloquentCastTest.php | 237 +++ .../Database/MariaDb/EscapeTest.php | 71 + .../Database/MariaDb/FulltextTest.php | 69 + .../Database/MariaDb/MariaDbTestCase.php | 15 + ...SqlSchemaBuilderAlterTableWithEnumTest.php | 19 +- .../Database/MySql/JoinLateralTest.php | 117 ++ .../Database/Postgres/JoinLateralTest.php | 104 ++ .../Database/Queue/QueueTransactionTest.php | 54 + .../Database/SchemaBuilderSchemaNameTest.php | 435 +++++ .../Database/SchemaBuilderTest.php | 316 +++- .../DatabaseSqlServerSchemaBuilderTest.php | 22 +- .../Database/SqlServer/JoinLateralTest.php | 100 ++ .../DatabaseSqliteSchemaBuilderTest.php | 25 +- .../Database/TimestampTypeTest.php | 72 + tests/Integration/Events/ListenerTest.php | 2 + .../ShouldDispatchAfterCommitEventTest.php | 2 + .../Foundation/Console/AboutCommandTest.php | 6 +- .../Foundation/MaintenanceModeTest.php | 9 +- .../RouteServiceProviderHealthTest.php | 34 + .../Providers/RouteServiceProviderTest.php | 75 + .../Support/Providers/fixtures/web.php | 5 + .../Generators/ControllerMakeCommandTest.php | 10 +- .../Generators/EnumMakeCommandTest.php | 41 + .../Generators/ModelMakeCommandTest.php | 4 +- .../Generators/ProviderMakeCommandTest.php | 4 + tests/Integration/Http/ResourceTest.php | 2 - .../Mail/MailRoundRobinTransportTest.php | 27 + ...otificationsViaAnonymousNotifiableTest.php | 14 + tests/Integration/Queue/JobChainingTest.php | 48 + tests/Integration/Queue/RedisQueueTest.php | 27 +- tests/Integration/Queue/UniqueJobTest.php | 2 +- .../Translation/TranslatorTest.php | 13 + tests/Mail/MailLogTransportTest.php | 27 + tests/Mail/MailMailableTest.php | 44 +- tests/Pipeline/PipelineTest.php | 30 + tests/Process/ProcessTest.php | 20 + tests/Queue/QueueBeanstalkdQueueTest.php | 4 +- .../QueueDatabaseQueueIntegrationTest.php | 20 +- tests/Queue/QueueDatabaseQueueUnitTest.php | 4 +- tests/Queue/QueueRedisQueueTest.php | 10 +- tests/Queue/QueueSqsQueueTest.php | 6 +- tests/Session/SessionStoreTest.php | 11 + tests/Support/ConfigurationUrlParserTest.php | 2 +- tests/Support/OnceTest.php | 374 +++++ tests/Support/SleepTest.php | 42 +- tests/Support/SupportArrTest.php | 122 +- tests/Support/SupportCollectionTest.php | 98 +- tests/Support/SupportHelpersTest.php | 7 + tests/Support/SupportNumberTest.php | 25 + tests/Support/SupportStrTest.php | 60 + tests/Support/SupportStringableTest.php | 21 + tests/Support/SupportTestingQueueFakeTest.php | 12 + .../InteractsWithDeprecationHandlingTest.php | 10 +- tests/Testing/TestResponseTest.php | 76 +- .../Translation/TranslationTranslatorTest.php | 12 +- tests/Validation/ValidationEnumRuleTest.php | 88 + tests/Validation/ValidationFileRuleTest.php | 15 + .../Validation/ValidationPasswordRuleTest.php | 9 + tests/Validation/ValidationRuleParserTest.php | 8 + tests/Validation/ValidationValidatorTest.php | 81 +- tests/View/Blade/BladeEchoHandlerTest.php | 26 + tests/View/Blade/BladeSessionTest.php | 27 + tests/View/Blade/BladeUseTest.php | 14 + tests/View/ComponentTest.php | 58 + tests/View/ViewComponentAttributeBagTest.php | 14 + types/Foundation/Configuration/Exceptions.php | 16 + types/Foundation/Configuration/Middleware.php | 36 + types/Support/Helpers.php | 10 + 435 files changed, 14845 insertions(+), 4820 deletions(-) create mode 100644 .github/workflows/releases.yml create mode 100644 config-stubs/app.php create mode 100644 src/Illuminate/Console/PromptValidationException.php create mode 100755 src/Illuminate/Database/Connectors/MariaDbConnector.php delete mode 100644 src/Illuminate/Database/DBAL/TimestampType.php create mode 100644 src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php create mode 100644 src/Illuminate/Database/Eloquent/Attributes/ScopedBy.php create mode 100755 src/Illuminate/Database/MariaDbConnection.php delete mode 100644 src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php delete mode 100644 src/Illuminate/Database/PDO/Connection.php delete mode 100644 src/Illuminate/Database/PDO/MySqlDriver.php delete mode 100644 src/Illuminate/Database/PDO/PostgresDriver.php delete mode 100644 src/Illuminate/Database/PDO/SQLiteDriver.php delete mode 100644 src/Illuminate/Database/PDO/SqlServerConnection.php delete mode 100644 src/Illuminate/Database/PDO/SqlServerDriver.php create mode 100755 src/Illuminate/Database/Query/Grammars/MariaDbGrammar.php create mode 100644 src/Illuminate/Database/Query/JoinLateralClause.php create mode 100644 src/Illuminate/Database/Query/Processors/MariaDbProcessor.php delete mode 100644 src/Illuminate/Database/Schema/Grammars/ChangeColumn.php create mode 100755 src/Illuminate/Database/Schema/Grammars/MariaDbGrammar.php delete mode 100644 src/Illuminate/Database/Schema/Grammars/RenameColumn.php mode change 100755 => 100644 src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php create mode 100755 src/Illuminate/Database/Schema/MariaDbBuilder.php create mode 100644 src/Illuminate/Database/Schema/MariaDbSchemaState.php create mode 100644 src/Illuminate/Filesystem/functions.php create mode 100644 src/Illuminate/Foundation/Console/ClassMakeCommand.php create mode 100644 src/Illuminate/Foundation/Console/EnumMakeCommand.php create mode 100644 src/Illuminate/Foundation/Console/InterfaceMakeCommand.php create mode 100644 src/Illuminate/Foundation/Console/StorageUnlinkCommand.php create mode 100644 src/Illuminate/Foundation/Console/TraitMakeCommand.php create mode 100644 src/Illuminate/Foundation/Console/stubs/class.invokable.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/class.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/enum.backed.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/enum.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/interface.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/trait.stub create mode 100644 src/Illuminate/Foundation/Events/DiagnosingHealth.php create mode 100644 src/Illuminate/Foundation/Http/Middleware/Concerns/ExcludesPaths.php create mode 100644 src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php create mode 100644 src/Illuminate/Foundation/resources/health-up.blade.php create mode 100644 src/Illuminate/Mail/Transport/ResendTransport.php create mode 100644 src/Illuminate/Queue/Events/JobQueueing.php create mode 100644 src/Illuminate/Queue/Jobs/FakeJob.php mode change 100755 => 100644 src/Illuminate/Support/Facades/DB.php create mode 100644 src/Illuminate/Support/Once.php create mode 100644 src/Illuminate/Support/Onceable.php create mode 100644 src/Illuminate/View/Compilers/Concerns/CompilesSessions.php create mode 100644 tests/Database/DatabaseEloquentBelongsToManyWithoutTouchingTest.php create mode 100644 tests/Database/DatabaseMariaDbBuilderTest.php create mode 100644 tests/Database/DatabaseMariaDbProcessorTest.php create mode 100755 tests/Database/DatabaseMariaDbQueryGrammarTest.php create mode 100755 tests/Database/DatabaseMariaDbSchemaBuilderTest.php create mode 100755 tests/Database/DatabaseMariaDbSchemaGrammarTest.php create mode 100644 tests/Database/DatabaseMariaDbSchemaStateTest.php mode change 100755 => 100644 tests/Database/DatabaseSchemaBuilderTest.php create mode 100644 tests/Filesystem/JoinPathsHelperTest.php create mode 100644 tests/Foundation/Configuration/ExceptionsTest.php create mode 100644 tests/Foundation/Configuration/MiddlewareTest.php create mode 100644 tests/Foundation/FoundationApplicationBuilderTest.php create mode 100644 tests/Http/JsonResourceTest.php create mode 100644 tests/Integration/Console/PromptsValidationTest.php delete mode 100644 tests/Integration/Database/ConfigureCustomDoctrineTypeTest.php delete mode 100644 tests/Integration/Database/DBAL/TimestampTypeTest.php create mode 100644 tests/Integration/Database/DatabaseConnectionsTest.php create mode 100644 tests/Integration/Database/EloquentEagerLoadingLimitTest.php create mode 100644 tests/Integration/Database/EloquentModelEncryptedDirtyTest.php create mode 100644 tests/Integration/Database/EloquentModelLoadMaxTest.php create mode 100644 tests/Integration/Database/EloquentModelLoadMinTest.php create mode 100644 tests/Integration/Database/EloquentModelLoadSumTest.php delete mode 100644 tests/Integration/Database/Fixtures/TinyInteger.php create mode 100644 tests/Integration/Database/MariaDb/DatabaseEloquentMariaDbIntegrationTest.php create mode 100755 tests/Integration/Database/MariaDb/DatabaseEmulatePreparesMariaDbConnectionTest.php create mode 100644 tests/Integration/Database/MariaDb/DatabaseMariaDbConnectionTest.php create mode 100644 tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderAlterTableWithEnumTest.php create mode 100644 tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderTest.php create mode 100644 tests/Integration/Database/MariaDb/EloquentCastTest.php create mode 100644 tests/Integration/Database/MariaDb/EscapeTest.php create mode 100644 tests/Integration/Database/MariaDb/FulltextTest.php create mode 100644 tests/Integration/Database/MariaDb/MariaDbTestCase.php create mode 100644 tests/Integration/Database/MySql/JoinLateralTest.php create mode 100644 tests/Integration/Database/Postgres/JoinLateralTest.php create mode 100644 tests/Integration/Database/Queue/QueueTransactionTest.php create mode 100644 tests/Integration/Database/SchemaBuilderSchemaNameTest.php create mode 100644 tests/Integration/Database/SqlServer/JoinLateralTest.php create mode 100644 tests/Integration/Database/TimestampTypeTest.php create mode 100644 tests/Integration/Foundation/Support/Providers/RouteServiceProviderHealthTest.php create mode 100644 tests/Integration/Foundation/Support/Providers/RouteServiceProviderTest.php create mode 100644 tests/Integration/Foundation/Support/Providers/fixtures/web.php create mode 100644 tests/Integration/Generators/EnumMakeCommandTest.php create mode 100644 tests/Integration/Mail/MailRoundRobinTransportTest.php create mode 100644 tests/Support/OnceTest.php create mode 100644 tests/View/Blade/BladeSessionTest.php create mode 100644 types/Foundation/Configuration/Exceptions.php create mode 100644 types/Foundation/Configuration/Middleware.php diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index f4457fd85c52..a42954e826bc 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -16,7 +16,7 @@ jobs: image: mysql:5.7 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: forge + MYSQL_DATABASE: laravel ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 @@ -51,7 +51,6 @@ jobs: run: vendor/bin/phpunit tests/Integration/Database env: DB_CONNECTION: mysql - DB_USERNAME: root MYSQL_COLLATION: utf8mb4_unicode_ci mysql_8: @@ -62,7 +61,7 @@ jobs: image: mysql:8 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: forge + MYSQL_DATABASE: laravel ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 @@ -97,7 +96,6 @@ jobs: run: vendor/bin/phpunit tests/Integration/Database env: DB_CONNECTION: mysql - DB_USERNAME: root mariadb: runs-on: ubuntu-22.04 @@ -107,7 +105,7 @@ jobs: image: mariadb:10 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: forge + MYSQL_DATABASE: laravel ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 @@ -142,7 +140,6 @@ jobs: run: vendor/bin/phpunit tests/Integration/Database env: DB_CONNECTION: mariadb - DB_USERNAME: root pgsql: runs-on: ubuntu-22.04 @@ -151,7 +148,7 @@ jobs: postgresql: image: postgres:14 env: - POSTGRES_DB: forge + POSTGRES_DB: laravel POSTGRES_USER: forge POSTGRES_PASSWORD: password ports: @@ -188,6 +185,7 @@ jobs: run: vendor/bin/phpunit tests/Integration/Database env: DB_CONNECTION: pgsql + DB_USERNAME: forge DB_PASSWORD: password mssql: diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 18b32b3261a9..2aa858fb68e0 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -8,5 +8,5 @@ permissions: pull-requests: write jobs: - uneditable: + pull-requests: uses: laravel/.github/.github/workflows/pull-requests.yml@main diff --git a/.github/workflows/queues.yml b/.github/workflows/queues.yml index dbe5c2bcc1b4..fd583e6c3f8f 100644 --- a/.github/workflows/queues.yml +++ b/.github/workflows/queues.yml @@ -128,10 +128,10 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - - uses: actions/checkout@v3 + - name: Download & Extract beanstalkd run: curl -L https://github.com/beanstalkd/beanstalkd/archive/refs/tags/v1.13.tar.gz | tar xz + - name: Make beanstalkd run: make working-directory: beanstalkd-1.13 diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml new file mode 100644 index 000000000000..e6177b2222a8 --- /dev/null +++ b/.github/workflows/releases.yml @@ -0,0 +1,64 @@ +name: manual release + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to release' + required: true + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Remove optional "v" prefix + id: version + run: | + VERSION=${{ inputs.version }} + echo "version=${VERSION#v}" >> "$GITHUB_OUTPUT" + + - name: Update Application.php version + run: sed -i "s/const VERSION = '.*';/const VERSION = '${{ steps.version.outputs.version }}';/g" src/Illuminate/Foundation/Application.php + + - name: Commit version change + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "Update version to v${{ steps.version.outputs.version }}" + + - name: SSH into splitter server + uses: appleboy/ssh-action@master + with: + host: 104.248.56.26 + username: forge + key: ${{ secrets.SSH_PRIVATE_KEY_SPLITTER }} + script: | + cd laravel-${{ github.ref_name }} + git pull origin ${{ github.ref_name }} + bash ./bin/release.sh v${{ steps.version.outputs.version }} + script_stop: true + + - name: Generate release notes + id: notes + uses: RedCrafter07/release-notes-action@main + with: + tag-name: v${{ steps.version.outputs.version }} + token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref_name }} + + - name: Cleanup release notes + run: | + sed -i '/## What/d' ${{ steps.notes.outputs.release-notes }} + sed -i '/## New Contributors/,$d' ${{ steps.notes.outputs.release-notes }} + + - name: Create release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.version.outputs.version }} + name: v${{ steps.version.outputs.version }} + body: ${{ steps.notes.outputs.release-notes }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 292e06fd86d3..093ad63b7954 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,9 +40,10 @@ jobs: fail-fast: true matrix: php: [8.2, 8.3] + phpunit: ['10.5', '11.0.1'] stability: [prefer-lowest, prefer-stable] - name: PHP ${{ matrix.php }} - ${{ matrix.stability }} + name: PHP ${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - ${{ matrix.stability }} steps: - name: Checkout code @@ -62,13 +63,20 @@ jobs: REDIS_CONFIGURE_OPTS: --enable-redis --enable-redis-igbinary --enable-redis-msgpack --enable-redis-lzf --with-liblzf --enable-redis-zstd --with-libzstd --enable-redis-lz4 --with-liblz4 REDIS_LIBS: liblz4-dev, liblzf-dev, libzstd-dev - - name: Set Minimum PHP 8.2 Versions + - name: Set minimum PHP 8.2 versions uses: nick-fields/retry@v2 with: timeout_minutes: 5 max_attempts: 5 command: composer require guzzlehttp/psr7:^2.4 --no-interaction --no-update + - name: Set PHPUnit + uses: nick-fields/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer require phpunit/phpunit:^${{ matrix.phpunit }} --dev --no-interaction --no-update + - name: Install dependencies uses: nick-fields/retry@v2 with: @@ -101,9 +109,10 @@ jobs: fail-fast: true matrix: php: [8.2, 8.3] + phpunit: ['10.5', '11.0.1'] stability: [prefer-lowest, prefer-stable] - name: PHP ${{ matrix.php }} - ${{ matrix.stability }} - Windows + name: PHP ${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - ${{ matrix.stability }} - Windows steps: - name: Set git to use LF @@ -131,6 +140,13 @@ jobs: max_attempts: 5 command: composer require guzzlehttp/psr7:~2.4 --no-interaction --no-update + - name: Set PHPUnit + uses: nick-fields/retry@v2 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer require phpunit/phpunit:~${{ matrix.phpunit }} --dev --no-interaction --no-update + - name: Install dependencies uses: nick-fields/retry@v2 with: diff --git a/.styleci.yml b/.styleci.yml index 44f7cb91093b..2ed89668bb19 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,6 +1,6 @@ php: preset: laravel - version: 8.1 + version: 8.2 finder: not-name: - bad-syntax-strategy.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd05da46d1f..561b779964bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,6 @@ ## [Unreleased](https://github.com/laravel/framework/compare/v11.0.0..master) -## [v11.0.0 (2023-??-??)](https://github.com/laravel/framework/compare/v11.0.0...master) +## [v11.0.0 (2024-??-??)](https://github.com/laravel/framework/compare/v11.0.0...master) Check the upgrade guide in the [Official Laravel Upgrade Documentation](https://laravel.com/docs/11.x/upgrade). Also you can see some release notes in the [Official Laravel Release Documentation](https://laravel.com/docs/11.x/releases). diff --git a/README.md b/README.md index df935e86ac3f..a6fb7790a95a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Laravel has the most extensive and thorough documentation and video tutorial lib You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. -If you're not in the mood to read, [Laracasts](https://laracasts.com) contains over 1100 video tutorials covering a range of topics including Laravel, modern PHP, unit testing, JavaScript, and more. Boost the skill level of yourself and your entire team by digging into our comprehensive video library. +If you're not in the mood to read, [Laracasts](https://laracasts.com) contains thousands of video tutorials covering a range of topics including Laravel, modern PHP, unit testing, JavaScript, and more. Boost the skill level of yourself and your entire team by digging into our comprehensive video library. ## Contributing diff --git a/composer.json b/composer.json index 8b4bd32294e8..9db96509c853 100644 --- a/composer.json +++ b/composer.json @@ -24,18 +24,19 @@ "ext-session": "*", "ext-tokenizer": "*", "composer-runtime-api": "^2.2", - "brick/math": "^0.9.3|^0.10.2|^0.11", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.3.2", "egulias/email-validator": "^3.2.1|^4.0", "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.12", + "laravel/prompts": "^0.1.15", "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.67", + "nesbot/carbon": "^2.72.2|^3.0", "nunomaduro/termwind": "^2.0", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", @@ -90,42 +91,43 @@ "illuminate/testing": "self.version", "illuminate/translation": "self.version", "illuminate/validation": "self.version", - "illuminate/view": "self.version" + "illuminate/view": "self.version", + "spatie/once": "*" }, "require-dev": { "ext-gmp": "*", "ably/ably-php": "^1.0", "aws/aws-sdk-php": "^3.235.5", - "doctrine/dbal": "^4.0", - "fakerphp/faker": "^1.21", - "guzzlehttp/guzzle": "^7.6", + "fakerphp/faker": "^1.23", "league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-ftp": "^3.0", "league/flysystem-path-prefixing": "^3.3", "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", - "mockery/mockery": "^1.5.1", + "mockery/mockery": "^1.6", "nyholm/psr7": "^1.2", "orchestra/testbench-core": "^9.0", "pda/pheanstalk": "^5.0", "phpstan/phpstan": "^1.4.7", - "phpunit/phpunit": "^10.1", + "phpunit/phpunit": "^10.5|^11.0", "predis/predis": "^2.0.2", + "resend/resend-php": "^0.10.0", "symfony/cache": "^7.0", "symfony/http-client": "^7.0", - "symfony/psr-http-message-bridge": "^v7.0.0-BETA1" + "symfony/psr-http-message-bridge": "^7.0" + }, + "conflict": { + "tightenco/collect": "<5.5.33" }, "provide": { "psr/container-implementation": "1.1|2.0", "psr/simple-cache-implementation": "1.0|2.0|3.0" }, - "conflict": { - "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" ], @@ -163,30 +165,29 @@ "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", - "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^4.0).", + "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", - "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.6).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).", "league/flysystem-read-only": "Required to use read-only disks (^3.3)", "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", - "mockery/mockery": "Required to use mocking (^1.5.1).", + "mockery/mockery": "Required to use mocking (^1.6).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", - "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8|^10.0.7).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5|^11.0).", "predis/predis": "Required to use the predis connector (^2.0.2).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^6.3).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^6.3).", - "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.3).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.3).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.3).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." }, "config": { "sort-packages": true, diff --git a/config-stubs/app.php b/config-stubs/app.php new file mode 100644 index 000000000000..f46726731e4a --- /dev/null +++ b/config-stubs/app.php @@ -0,0 +1,126 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | the application so that it's available within Artisan commands. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => env('APP_TIMEZONE', 'UTC'), + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. + | + */ + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', env('APP_PREVIOUS_KEYS', '')) + ), + ], + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + +]; diff --git a/config/app.php b/config/app.php index cd591805bccd..d4e3180fa6fc 100644 --- a/config/app.php +++ b/config/app.php @@ -12,7 +12,7 @@ | | This value is the name of your application, which will be used when the | framework needs to place the application's name in a notification or - | any other location as required by the application or its packages. + | other UI elements where an application name needs to be displayed. | */ @@ -51,7 +51,7 @@ | | This URL is used by the console to properly generate URLs when using | the Artisan command line tool. You should set this to the root of - | your application so that it is used when running Artisan tasks. + | the application so that it's available within Artisan commands. | */ @@ -80,8 +80,8 @@ |-------------------------------------------------------------------------- | | The application locale determines the default locale that will be used - | by the translation service provider. You are free to set this value - | to any of the locales which will be supported by the application. + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. | */ @@ -119,14 +119,20 @@ |-------------------------------------------------------------------------- | | This key is utilized by Laravel's encryption services and should be set - | to a random, 32 character string or all of the encrypted strings are - | not secure. You should do this prior to deploying the application. + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. | */ + 'cipher' => 'AES-256-CBC', + 'key' => env('APP_KEY'), - 'cipher' => 'AES-256-CBC', + 'previous_keys' => [ + ...array_filter( + explode(',', env('APP_PREVIOUS_KEYS', '')) + ), + ], /* |-------------------------------------------------------------------------- @@ -143,7 +149,7 @@ 'maintenance' => [ 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), - 'store' => env('APP_MAINTENANCE_STORE', 'redis'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), ], /* diff --git a/config/auth.php b/config/auth.php index 7bf212ad24a4..0ba5d5d8f10c 100644 --- a/config/auth.php +++ b/config/auth.php @@ -7,15 +7,15 @@ | Authentication Defaults |-------------------------------------------------------------------------- | - | This option controls the default authentication "guard" and password - | reset options for your application. You may change these defaults + | This option defines the default authentication "guard" and password + | reset "broker" for your application. You may change these values | as required, but they're a perfect start for most applications. | */ 'defaults' => [ 'guard' => env('AUTH_GUARD', 'web'), - 'passwords' => 'users', + 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), ], /* @@ -27,9 +27,9 @@ | Of course, a great default configuration has been defined for you | which utilizes session storage plus the Eloquent user provider. | - | All authentication drivers have a user provider. This defines how the + | All authentication guards have a user provider, which defines how the | users are actually retrieved out of your database or other storage - | mechanisms used by this application to persist your user's data. + | system used by the application. Typically, Eloquent is utilized. | | Supported: "session" | @@ -47,12 +47,12 @@ | User Providers |-------------------------------------------------------------------------- | - | All authentication drivers have a user provider. This defines how the + | All authentication guards have a user provider, which defines how the | users are actually retrieved out of your database or other storage - | mechanisms used by this application to persist your user's data. + | system used by the application. Typically, Eloquent is utilized. | | If you have multiple user tables or models you may configure multiple - | sources which represent each model / table. These sources may then + | providers to represent the model / table. These providers may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" @@ -76,9 +76,9 @@ | Resetting Passwords |-------------------------------------------------------------------------- | - | You may specify multiple password reset configurations if you have more - | than one user table or model in the application and you want to have - | separate password reset settings based on the specific user types. + | These configuration options specify the behavior of Laravel's password + | reset functionality, including the table utilized for token storage + | and the user provider that is invoked to actually retrieve users. | | The expiry time is the number of minutes that each reset token will be | considered valid. This security feature keeps tokens short-lived so @@ -93,7 +93,7 @@ 'passwords' => [ 'users' => [ 'provider' => 'users', - 'table' => 'password_reset_tokens', + 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 'expire' => 60, 'throttle' => 60, ], diff --git a/config/broadcasting.php b/config/broadcasting.php index de5dd4e5ce8c..ebc3fb9cf136 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -11,7 +11,7 @@ | framework when an event needs to be broadcast. You may set this to | any of the connections defined in the "connections" array below. | - | Supported: "pusher", "ably", "redis", "log", "null" + | Supported: "reverb", "pusher", "ably", "redis", "log", "null" | */ @@ -23,13 +23,29 @@ |-------------------------------------------------------------------------- | | Here you may define all of the broadcast connections that will be used - | to broadcast events to other systems or over websockets. Samples of + | to broadcast events to other systems or over WebSockets. Samples of | each available type of connection are provided inside this array. | */ 'connections' => [ + 'reverb' => [ + 'driver' => 'reverb', + 'key' => env('REVERB_APP_KEY'), + 'secret' => env('REVERB_APP_SECRET'), + 'app_id' => env('REVERB_APP_ID'), + 'options' => [ + 'host' => env('REVERB_HOST'), + 'port' => env('REVERB_PORT', 443), + 'scheme' => env('REVERB_SCHEME', 'https'), + 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), @@ -53,11 +69,6 @@ 'key' => env('ABLY_KEY'), ], - 'redis' => [ - 'driver' => 'redis', - 'connection' => env('REDIS_BROADCASTING_CONNECTION', 'default'), - ], - 'log' => [ 'driver' => 'log', ], diff --git a/config/cache.php b/config/cache.php index 4d67e5de8a49..3eb95d106ffa 100644 --- a/config/cache.php +++ b/config/cache.php @@ -15,7 +15,7 @@ | */ - 'default' => env('CACHE_STORE', 'file'), + 'default' => env('CACHE_STORE', 'database'), /* |-------------------------------------------------------------------------- @@ -26,17 +26,13 @@ | well as their drivers. You may even define multiple stores for the | same cache driver to group types of items stored in your caches. | - | Supported drivers: "apc", "array", "database", "file", - | "memcached", "redis", "dynamodb", "octane", "null" + | Supported drivers: "apc", "array", "database", "file", "memcached", + | "redis", "dynamodb", "octane", "null" | */ 'stores' => [ - 'apc' => [ - 'driver' => 'apc', - ], - 'array' => [ 'driver' => 'array', 'serialize' => false, @@ -100,8 +96,8 @@ | Cache Key Prefix |-------------------------------------------------------------------------- | - | When utilizing the APC, database, memcached, Redis, or DynamoDB cache - | stores there might be other applications using the same cache. For + | When utilizing the APC, database, memcached, Redis, and DynamoDB cache + | stores, there might be other applications using the same cache. For | that reason, you may prefix every cache key to avoid collisions. | */ diff --git a/config/database.php b/config/database.php index f030206fc946..9dadea5f2896 100644 --- a/config/database.php +++ b/config/database.php @@ -10,21 +10,22 @@ |-------------------------------------------------------------------------- | | Here you may specify which of the database connections below you wish - | to use as your default connection for all database work. Of course - | you may use many connections at once throughout the application. + | to use as your default connection for database operations. This is + | the connection which will be utilized unless another connection + | is explicitly specified when you execute a query / statement. | */ - 'default' => env('DB_CONNECTION', 'mysql'), + 'default' => env('DB_CONNECTION', 'sqlite'), /* |-------------------------------------------------------------------------- | Database Connections |-------------------------------------------------------------------------- | - | Here are each of the database connections setup for your application. - | Of course, examples of configuring each database platform that is - | supported by Laravel is shown below to assist your development. + | Below are all of the database connections defined for your application. + | An example configuration is provided for each database system which + | is supported by Laravel. You're free to add / remove connections. | */ @@ -43,8 +44,8 @@ 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', @@ -59,12 +60,12 @@ ], 'mariadb' => [ - 'driver' => 'mysql', + 'driver' => 'mariadb', 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', @@ -83,8 +84,8 @@ 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', @@ -98,8 +99,8 @@ 'url' => env('DB_URL'), 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', @@ -117,7 +118,7 @@ | | This table keeps track of all the migrations that have already run for | your application. Using this information, we can determine which of - | the migrations on disk haven't actually been run in the database. + | the migrations on disk haven't actually been run on the database. | */ @@ -133,7 +134,7 @@ | | Redis is an open source, fast, and advanced key-value store that also | provides a richer body of commands than a typical key-value system - | such as APC or Memcached. Laravel makes it easy to dig right in. + | such as Memcached. You may define your connection settings here. | */ diff --git a/config/filesystems.php b/config/filesystems.php index 21ad5c8bdb40..44fe9c828e0e 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -20,9 +20,9 @@ | Filesystem Disks |-------------------------------------------------------------------------- | - | Here you may configure as many filesystem "disks" as you wish, and you - | may even configure multiple disks of the same driver. Defaults have - | been set up for each driver as an example of the required values. + | Below you may configure as many filesystem disks as necessary, and you + | may even configure multiple disks for the same driver. Examples for + | most supported storage drivers are configured here for reference. | | Supported Drivers: "local", "ftp", "sftp", "s3" | diff --git a/config/logging.php b/config/logging.php index 06a666ac78c0..d526b64d75a5 100644 --- a/config/logging.php +++ b/config/logging.php @@ -12,9 +12,9 @@ | Default Log Channel |-------------------------------------------------------------------------- | - | This option defines the default log channel that gets used when writing - | messages to the logs. The name specified in this option should match - | one of the channels defined in the "channels" configuration array. + | This option defines the default log channel that is utilized to write + | messages to your logs. The value provided here should match one of + | the channels present in the list of "channels" configured below. | */ @@ -41,17 +41,17 @@ | Log Channels |-------------------------------------------------------------------------- | - | Here you may configure the log channels for your application. Out of - | the box, Laravel uses the Monolog PHP logging library. This gives - | you a variety of powerful log handlers / formatters to utilize. + | Here you may configure the log channels for your application. Laravel + | utilizes the Monolog PHP logging library, which includes a variety + | of powerful log handlers and formatters that you're free to use. | | Available Drivers: "single", "daily", "slack", "syslog", - | "errorlog", "monolog", - | "custom", "stack" + | "errorlog", "monolog", "custom", "stack" | */ 'channels' => [ + 'stack' => [ 'driver' => 'stack', 'channels' => explode(',', env('LOG_STACK', 'single')), @@ -126,6 +126,7 @@ 'emergency' => [ 'path' => storage_path('logs/laravel.log'), ], + ], ]; diff --git a/config/mail.php b/config/mail.php index 07e449a5fa23..a4b9780e9d42 100644 --- a/config/mail.php +++ b/config/mail.php @@ -7,13 +7,14 @@ | Default Mailer |-------------------------------------------------------------------------- | - | This option controls the default mailer that is used to send any email - | messages sent by your application. Alternative mailers may be setup - | and used as needed; however, this mailer will be used by default. + | This option controls the default mailer that is used to send all email + | messages unless another mailer is explicitly specified when sending + | the message. All additional mailers can be configured within the + | "mailers" array. Examples of each type of mailer are provided. | */ - 'default' => env('MAIL_MAILER', 'smtp'), + 'default' => env('MAIL_MAILER', 'log'), /* |-------------------------------------------------------------------------- @@ -24,21 +25,22 @@ | their respective settings. Several examples have been configured for | you and you are free to add your own as your application requires. | - | Laravel supports a variety of mail "transport" drivers to be used while - | delivering an email. You may specify which one you're using for your - | mailers below. You are free to add additional mailers as required. + | Laravel supports a variety of mail "transport" drivers that can be used + | when delivering an email. You may specify which one you're using for + | your mailers below. You may also add additional mailers if needed. | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", - | "postmark", "log", "array", "failover" + | "postmark", "log", "array", "failover", "roundrobin" | */ 'mailers' => [ + 'smtp' => [ 'transport' => 'smtp', 'url' => env('MAIL_URL'), - 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), - 'port' => env('MAIL_PORT', 587), + 'host' => env('MAIL_HOST', '127.0.0.1'), + 'port' => env('MAIL_PORT', 2525), 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), @@ -50,13 +52,6 @@ 'transport' => 'ses', ], - 'mailgun' => [ - 'transport' => 'mailgun', - // 'client' => [ - // 'timeout' => 5, - // ], - ], - 'postmark' => [ 'transport' => 'postmark', // 'message_stream_id' => null, @@ -86,6 +81,7 @@ 'log', ], ], + ], /* diff --git a/config/queue.php b/config/queue.php index 70c9fcf83a4d..4f689e9c7827 100644 --- a/config/queue.php +++ b/config/queue.php @@ -7,9 +7,9 @@ | Default Queue Connection Name |-------------------------------------------------------------------------- | - | Laravel's queue API supports an assortment of back-ends via a single - | API, giving you convenient access to each back-end using the same - | syntax for every one. Here you may define a default connection. + | Laravel's queue supports a variety of backends via a single, unified + | API, giving you convenient access to each backend using identical + | syntax for each. The default queue connection is defined below. | */ @@ -20,9 +20,9 @@ | Queue Connections |-------------------------------------------------------------------------- | - | Here you may configure the connection information for each server that - | is used by your application. A default configuration has been added - | for each back-end shipped with Laravel. You are free to add more. + | Here you may configure the connection options for every queue backend + | used by your application. An example configuration is provided for + | each backend supported by Laravel. You're also free to add more. | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" | @@ -36,6 +36,7 @@ 'database' => [ 'driver' => 'database', + 'connection' => env('DB_QUEUE_CONNECTION', null), 'table' => env('DB_QUEUE_TABLE', 'jobs'), 'queue' => env('DB_QUEUE', 'default'), 'retry_after' => env('DB_QUEUE_RETRY_AFTER', 90), @@ -85,7 +86,7 @@ */ 'batching' => [ - 'database' => env('DB_CONNECTION', 'mysql'), + 'database' => env('DB_CONNECTION', 'sqlite'), 'table' => 'job_batches', ], @@ -104,7 +105,7 @@ 'failed' => [ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), - 'database' => env('DB_CONNECTION', 'mysql'), + 'database' => env('DB_CONNECTION', 'sqlite'), 'table' => 'failed_jobs', ], diff --git a/config/services.php b/config/services.php index a12fbb266b0b..6bb68f6aece2 100644 --- a/config/services.php +++ b/config/services.php @@ -14,13 +14,6 @@ | */ - 'mailgun' => [ - 'domain' => env('MAILGUN_DOMAIN'), - 'secret' => env('MAILGUN_SECRET'), - 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), - 'scheme' => 'https', - ], - 'postmark' => [ 'token' => env('POSTMARK_TOKEN'), ], diff --git a/config/session.php b/config/session.php index 51c77f90af42..d1dc325d4a16 100644 --- a/config/session.php +++ b/config/session.php @@ -9,16 +9,16 @@ | Default Session Driver |-------------------------------------------------------------------------- | - | This option controls the default session "driver" that will be used by - | incoming requests. Laravel supports a variety of storage drivers to - | choose from for session storage. File storage is used by default. + | This option determines the default session driver that is utilized for + | incoming requests. Laravel supports a variety of storage options to + | persist session data. Database storage is a great default choice. | | Supported: "file", "cookie", "database", "apc", | "memcached", "redis", "dynamodb", "array" | */ - 'driver' => env('SESSION_DRIVER', 'file'), + 'driver' => env('SESSION_DRIVER', 'database'), /* |-------------------------------------------------------------------------- @@ -54,9 +54,9 @@ | Session File Location |-------------------------------------------------------------------------- | - | When utilizing the "file" session driver, we need a spot where session - | files may be stored. A default has been set for you but a different - | location may be specified. This is only needed for file sessions. + | When utilizing the "file" session driver, the session files are placed + | on disk. The default storage location is defined here; however, you + | are free to provide another location where they should be stored. | */ @@ -80,9 +80,9 @@ | Session Database Table |-------------------------------------------------------------------------- | - | When using the "database" session driver, you may specify the table we - | should use to manage the sessions. Of course, a sensible default is - | provided for you; however, you are free to change this as needed. + | When using the "database" session driver, you may specify the table to + | be used to store sessions. Of course, a sensible default is defined + | for you; however, you're welcome to change this to another table. | */ @@ -93,9 +93,9 @@ | Session Cache Store |-------------------------------------------------------------------------- | - | While using one of the framework's cache driven session backends you may - | list a cache store that should be used for these sessions. This value - | must match with one of the application's configured cache "stores". + | When using one of the framework's cache driven session backends, you may + | define the cache store which should be used to store the session data + | between requests. This must match one of your defined cache stores. | | Affects: "apc", "dynamodb", "memcached", "redis" | @@ -121,9 +121,9 @@ | Session Cookie Name |-------------------------------------------------------------------------- | - | Here you may change the name of the cookie used to identify a session - | instance by ID. The name specified here will get used every time a - | new session cookie is created by the framework for every driver. + | Here you may change the name of the session cookie that is created by + | the framework. Typically, you should not need to change this value + | since doing so does not grant a meaningful security improvement. | */ @@ -139,7 +139,7 @@ | | The session cookie path determines the path for which the cookie will | be regarded as available. Typically, this will be the root path of - | your application but you are free to change this when necessary. + | your application, but you're free to change this when necessary. | */ @@ -150,9 +150,9 @@ | Session Cookie Domain |-------------------------------------------------------------------------- | - | Here you may change the domain of the cookie used to identify a session - | in your application. This will determine which domains the cookie is - | available to in your application. A sensible default has been set. + | This value determines the domain and subdomains the session cookie is + | available to. By default, the cookie will be available to the root + | domain and all subdomains. Typically, this shouldn't be changed. | */ @@ -178,7 +178,7 @@ | | Setting this value to true will prevent JavaScript from accessing the | value of the cookie and the cookie will only be accessible through - | the HTTP protocol. You are free to modify this option if needed. + | the HTTP protocol. It's unlikely you should disable this option. | */ @@ -193,6 +193,8 @@ | take place, and can be used to mitigate CSRF attacks. By default, we | will set this value to "lax" since this is a secure default value. | + | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value + | | Supported: "lax", "strict", "none", null | */ @@ -210,6 +212,6 @@ | */ - 'partitioned' => false, + 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), ]; diff --git a/src/Illuminate/Auth/Access/Gate.php b/src/Illuminate/Auth/Access/Gate.php index e8ef93b64271..1f0a72007e9d 100644 --- a/src/Illuminate/Auth/Access/Gate.php +++ b/src/Illuminate/Auth/Access/Gate.php @@ -318,9 +318,9 @@ public function after(callable $callback) } /** - * Determine if the given ability should be granted for the current user. + * Determine if all of the given abilities should be granted for the current user. * - * @param string $ability + * @param iterable|string $ability * @param array|mixed $arguments * @return bool */ @@ -330,9 +330,9 @@ public function allows($ability, $arguments = []) } /** - * Determine if the given ability should be denied for the current user. + * Determine if any of the given abilities should be denied for the current user. * - * @param string $ability + * @param iterable|string $ability * @param array|mixed $arguments * @return bool */ diff --git a/src/Illuminate/Auth/Notifications/ResetPassword.php b/src/Illuminate/Auth/Notifications/ResetPassword.php index 1d8da41bd1a8..efb4573e8be2 100644 --- a/src/Illuminate/Auth/Notifications/ResetPassword.php +++ b/src/Illuminate/Auth/Notifications/ResetPassword.php @@ -25,7 +25,7 @@ class ResetPassword extends Notification /** * The callback that should be used to build the mail message. * - * @var (\Closure(mixed, string): \Illuminate\Notifications\Messages\MailMessage)|null + * @var (\Closure(mixed, string): \Illuminate\Notifications\Messages\MailMessage|\Illuminate\Contracts\Mail\Mailable)|null */ public static $toMailCallback; @@ -114,7 +114,7 @@ public static function createUrlUsing($callback) /** * Set a callback that should be used when building the notification mail message. * - * @param \Closure(mixed, string): \Illuminate\Notifications\Messages\MailMessage $callback + * @param \Closure(mixed, string): (\Illuminate\Notifications\Messages\MailMessage|\Illuminate\Contracts\Mail\Mailable) $callback * @return void */ public static function toMailUsing($callback) diff --git a/src/Illuminate/Auth/TokenGuard.php b/src/Illuminate/Auth/TokenGuard.php index b1aa7a7e5162..7fe5a9f7802a 100644 --- a/src/Illuminate/Auth/TokenGuard.php +++ b/src/Illuminate/Auth/TokenGuard.php @@ -92,7 +92,7 @@ public function user() /** * Get the token for the current request. * - * @return string + * @return string|null */ public function getTokenForRequest() { diff --git a/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php index a1e283a32c96..01c673c22f32 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php @@ -232,4 +232,15 @@ public function getAbly() { return $this->ably; } + + /** + * Set the underlying Ably SDK instance. + * + * @param \Ably\AblyRest $ably + * @return void + */ + public function setAbly($ably) + { + $this->ably = $ably; + } } diff --git a/src/Illuminate/Bus/Batchable.php b/src/Illuminate/Bus/Batchable.php index 0b082700f8a2..5cf5706070e9 100644 --- a/src/Illuminate/Bus/Batchable.php +++ b/src/Illuminate/Bus/Batchable.php @@ -35,7 +35,7 @@ public function batch() } if ($this->batchId) { - return Container::getInstance()->make(BatchRepository::class)->find($this->batchId); + return Container::getInstance()->make(BatchRepository::class)?->find($this->batchId); } } @@ -74,7 +74,7 @@ public function withBatchId(string $batchId) * @param int $failedJobs * @param array $failedJobIds * @param array $options - * @param \Carbon\CarbonImmutable $createdAt + * @param \Carbon\CarbonImmutable|null $createdAt * @param \Carbon\CarbonImmutable|null $cancelledAt * @param \Carbon\CarbonImmutable|null $finishedAt * @return array{0: $this, 1: \Illuminate\Support\Testing\Fakes\BatchFake} @@ -86,7 +86,7 @@ public function withFakeBatch(string $id = '', int $failedJobs = 0, array $failedJobIds = [], array $options = [], - CarbonImmutable $createdAt = null, + ?CarbonImmutable $createdAt = null, ?CarbonImmutable $cancelledAt = null, ?CarbonImmutable $finishedAt = null) { 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/Bus/PendingBatch.php b/src/Illuminate/Bus/PendingBatch.php index 60ff3884c8b8..1b3b01bd061f 100644 --- a/src/Illuminate/Bus/PendingBatch.php +++ b/src/Illuminate/Bus/PendingBatch.php @@ -74,6 +74,31 @@ public function add($jobs) return $this; } + /** + * Add a callback to be executed when the batch is stored. + * + * @param callable $callback + * @return $this + */ + public function before($callback) + { + $this->options['before'][] = $callback instanceof Closure + ? new SerializableClosure($callback) + : $callback; + + return $this; + } + + /** + * Get the "before" callbacks that have been registered with the pending batch. + * + * @return array + */ + public function beforeCallbacks() + { + return $this->options['before'] ?? []; + } + /** * Add a callback to be executed after a job in the batch have executed successfully. * @@ -282,7 +307,7 @@ public function dispatch() $repository = $this->container->make(BatchRepository::class); try { - $batch = $repository->store($this); + $batch = $this->store($repository); $batch = $batch->add($this->jobs); } catch (Throwable $e) { @@ -309,7 +334,7 @@ public function dispatchAfterResponse() { $repository = $this->container->make(BatchRepository::class); - $batch = $repository->store($this); + $batch = $this->store($repository); if ($batch) { $this->container->terminating(function () use ($batch) { @@ -344,4 +369,49 @@ protected function dispatchExistingBatch($batch) new BatchDispatched($batch) ); } + + /** + * Dispatch the batch if the given truth test passes. + * + * @param bool|\Closure $boolean + * @return \Illuminate\Bus\Batch|null + */ + public function dispatchIf($boolean) + { + return value($boolean) ? $this->dispatch() : null; + } + + /** + * Dispatch the batch unless the given truth test passes. + * + * @param bool|\Closure $boolean + * @return \Illuminate\Bus\Batch|null + */ + public function dispatchUnless($boolean) + { + return ! value($boolean) ? $this->dispatch() : null; + } + + /** + * Store the batch using the given repository. + * + * @param \Illuminate\Bus\BatchRepository $repository + * @return \Illuminate\Bus\Batch + */ + protected function store($repository) + { + $batch = $repository->store($this); + + collect($this->beforeCallbacks())->each(function ($handler) use ($batch) { + try { + return $handler($batch); + } catch (Throwable $e) { + if (function_exists('report')) { + report($e); + } + } + }); + + return $batch; + } } 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/Cache/CacheManager.php b/src/Illuminate/Cache/CacheManager.php index e5c9049997d0..a9dcb2b1fa2b 100755 --- a/src/Illuminate/Cache/CacheManager.php +++ b/src/Illuminate/Cache/CacheManager.php @@ -88,6 +88,8 @@ public function resolve($name) throw new InvalidArgumentException("Cache store [{$name}] is not defined."); } + $config = Arr::add($config, 'store', $name); + if (isset($this->customCreators[$config['driver']])) { return $this->callCustomCreator($config); } @@ -122,7 +124,7 @@ protected function createApcDriver(array $config) { $prefix = $this->getPrefix($config); - return $this->repository(new ApcStore(new ApcWrapper, $prefix)); + return $this->repository(new ApcStore(new ApcWrapper, $prefix), $config); } /** @@ -133,7 +135,7 @@ protected function createApcDriver(array $config) */ protected function createArrayDriver(array $config) { - return $this->repository(new ArrayStore($config['serialize'] ?? false)); + return $this->repository(new ArrayStore($config['serialize'] ?? false), $config); } /** @@ -146,7 +148,8 @@ protected function createFileDriver(array $config) { return $this->repository( (new FileStore($this->app['files'], $config['path'], $config['permission'] ?? null)) - ->setLockDirectory($config['lock_path'] ?? null) + ->setLockDirectory($config['lock_path'] ?? null), + $config ); } @@ -167,7 +170,7 @@ protected function createMemcachedDriver(array $config) array_filter($config['sasl'] ?? []) ); - return $this->repository(new MemcachedStore($memcached, $prefix)); + return $this->repository(new MemcachedStore($memcached, $prefix), $config); } /** @@ -177,7 +180,7 @@ protected function createMemcachedDriver(array $config) */ protected function createNullDriver() { - return $this->repository(new NullStore); + return $this->repository(new NullStore, []); } /** @@ -195,7 +198,8 @@ protected function createRedisDriver(array $config) $store = new RedisStore($redis, $this->getPrefix($config), $connection); return $this->repository( - $store->setLockConnection($config['lock_connection'] ?? $connection) + $store->setLockConnection($config['lock_connection'] ?? $connection), + $config ); } @@ -218,9 +222,12 @@ protected function createDatabaseDriver(array $config) $config['lock_timeout'] ?? 86400, ); - return $this->repository($store->setLockConnection( - $this->app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null) - )); + return $this->repository( + $store->setLockConnection( + $this->app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null) + ), + $config + ); } /** @@ -241,7 +248,8 @@ protected function createDynamodbDriver(array $config) $config['attributes']['value'] ?? 'value', $config['attributes']['expiration'] ?? 'expires_at', $this->getPrefix($config) - ) + ), + $config ); } @@ -275,11 +283,12 @@ protected function newDynamodbClient(array $config) * Create a new cache repository with the given implementation. * * @param \Illuminate\Contracts\Cache\Store $store + * @param array $config * @return \Illuminate\Cache\Repository */ - public function repository(Store $store) + public function repository(Store $store, array $config) { - return tap(new Repository($store), function ($repository) { + return tap(new Repository($store, Arr::only($config, ['store'])), function ($repository) { $this->setEventDispatcher($repository); }); } diff --git a/src/Illuminate/Cache/DynamoDbStore.php b/src/Illuminate/Cache/DynamoDbStore.php index 8e2fda7ff55e..31b8dc48ef01 100644 --- a/src/Illuminate/Cache/DynamoDbStore.php +++ b/src/Illuminate/Cache/DynamoDbStore.php @@ -285,7 +285,7 @@ public function add($key, $value, $seconds) ], 'ExpressionAttributeValues' => [ ':now' => [ - 'N' => (string) Carbon::now()->getTimestamp(), + 'N' => (string) $this->currentTime(), ], ], ]); @@ -326,7 +326,7 @@ public function increment($key, $value = 1) ], 'ExpressionAttributeValues' => [ ':now' => [ - 'N' => (string) Carbon::now()->getTimestamp(), + 'N' => (string) $this->currentTime(), ], ':amount' => [ 'N' => (string) $value, @@ -371,7 +371,7 @@ public function decrement($key, $value = 1) ], 'ExpressionAttributeValues' => [ ':now' => [ - 'N' => (string) Carbon::now()->getTimestamp(), + 'N' => (string) $this->currentTime(), ], ':amount' => [ 'N' => (string) $value, @@ -469,7 +469,7 @@ protected function toTimestamp($seconds) { return $seconds > 0 ? $this->availableAt($seconds) - : Carbon::now()->getTimestamp(); + : $this->currentTime(); } /** diff --git a/src/Illuminate/Cache/Events/CacheEvent.php b/src/Illuminate/Cache/Events/CacheEvent.php index 6c9d42c58e59..b6bc49b15c96 100644 --- a/src/Illuminate/Cache/Events/CacheEvent.php +++ b/src/Illuminate/Cache/Events/CacheEvent.php @@ -4,6 +4,13 @@ abstract class CacheEvent { + /** + * The name of the cache store. + * + * @var string|null + */ + public $storeName; + /** * The key of the event. * @@ -21,12 +28,14 @@ abstract class CacheEvent /** * Create a new event instance. * + * @param string|null $storeName * @param string $key * @param array $tags * @return void */ - public function __construct($key, array $tags = []) + public function __construct($storeName, $key, array $tags = []) { + $this->storeName = $storeName; $this->key = $key; $this->tags = $tags; } diff --git a/src/Illuminate/Cache/Events/CacheHit.php b/src/Illuminate/Cache/Events/CacheHit.php index 976c9e4f228b..9802980e3cbe 100644 --- a/src/Illuminate/Cache/Events/CacheHit.php +++ b/src/Illuminate/Cache/Events/CacheHit.php @@ -14,14 +14,15 @@ class CacheHit extends CacheEvent /** * Create a new event instance. * + * @param string|null $storeName * @param string $key * @param mixed $value * @param array $tags * @return void */ - public function __construct($key, $value, array $tags = []) + public function __construct($storeName, $key, $value, array $tags = []) { - parent::__construct($key, $tags); + parent::__construct($storeName, $key, $tags); $this->value = $value; } diff --git a/src/Illuminate/Cache/Events/KeyWritten.php b/src/Illuminate/Cache/Events/KeyWritten.php index 6474dced83b8..49334882cb10 100644 --- a/src/Illuminate/Cache/Events/KeyWritten.php +++ b/src/Illuminate/Cache/Events/KeyWritten.php @@ -21,15 +21,16 @@ class KeyWritten extends CacheEvent /** * Create a new event instance. * + * @param string|null $storeName * @param string $key * @param mixed $value * @param int|null $seconds * @param array $tags * @return void */ - public function __construct($key, $value, $seconds = null, $tags = []) + public function __construct($storeName, $key, $value, $seconds = null, $tags = []) { - parent::__construct($key, $tags); + parent::__construct($storeName, $key, $tags); $this->value = $value; $this->seconds = $seconds; diff --git a/src/Illuminate/Cache/FileStore.php b/src/Illuminate/Cache/FileStore.php index b18e568f6407..9d5e2e95c872 100755 --- a/src/Illuminate/Cache/FileStore.php +++ b/src/Illuminate/Cache/FileStore.php @@ -290,9 +290,11 @@ protected function getPayload($key) // just return null. Otherwise, we'll get the contents of the file and get // the expiration UNIX timestamps from the start of the file's contents. try { - $expire = substr( - $contents = $this->files->get($path, true), 0, 10 - ); + if (is_null($contents = $this->files->get($path, true))) { + return $this->emptyPayload(); + } + + $expire = substr($contents, 0, 10); } catch (Exception) { return $this->emptyPayload(); } diff --git a/src/Illuminate/Cache/RateLimiter.php b/src/Illuminate/Cache/RateLimiter.php index 5f5fac0659b6..afdb9b25a208 100644 --- a/src/Illuminate/Cache/RateLimiter.php +++ b/src/Illuminate/Cache/RateLimiter.php @@ -105,13 +105,26 @@ public function tooManyAttempts($key, $maxAttempts) } /** - * Increment the counter for a given key for a given decay time. + * Increment (by 1) the counter for a given key for a given decay time. * * @param string $key * @param int $decaySeconds * @return int */ public function hit($key, $decaySeconds = 60) + { + return $this->increment($key, $decaySeconds); + } + + /** + * Increment the counter for a given key for a given decay time by a given amount. + * + * @param string $key + * @param int $decaySeconds + * @param int $amount + * @return int + */ + public function increment($key, $decaySeconds = 60, $amount = 1) { $key = $this->cleanRateLimiterKey($key); @@ -121,7 +134,7 @@ public function hit($key, $decaySeconds = 60) $added = $this->cache->add($key, 0, $decaySeconds); - $hits = (int) $this->cache->increment($key); + $hits = (int) $this->cache->increment($key, $amount); if (! $added && $hits == 1) { $this->cache->put($key, 1, $decaySeconds); diff --git a/src/Illuminate/Cache/RedisTagSet.php b/src/Illuminate/Cache/RedisTagSet.php index bf4c53869361..b5fd0e2593bc 100644 --- a/src/Illuminate/Cache/RedisTagSet.php +++ b/src/Illuminate/Cache/RedisTagSet.php @@ -11,13 +11,13 @@ class RedisTagSet extends TagSet * Add a reference entry to the tag set's underlying sorted set. * * @param string $key - * @param int $ttl + * @param int|null $ttl * @param string $updateWhen * @return void */ - public function addEntry(string $key, int $ttl = 0, $updateWhen = null) + public function addEntry(string $key, int $ttl = null, $updateWhen = null) { - $ttl = $ttl > 0 ? Carbon::now()->addSeconds($ttl)->getTimestamp() : -1; + $ttl = is_null($ttl) ? -1 : Carbon::now()->addSeconds($ttl)->getTimestamp(); foreach ($this->tagIds() as $tagKey) { if ($updateWhen) { diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index b8120be95c03..75c1001ce747 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -14,9 +14,19 @@ class RedisTaggedCache extends TaggedCache */ public function add($key, $value, $ttl = null) { + $seconds = null; + + if ($ttl !== null) { + $seconds = $this->getSeconds($ttl); + + if ($seconds <= 0) { + return false; + } + } + $this->tags->addEntry( $this->itemKey($key), - ! is_null($ttl) ? $this->getSeconds($ttl) : 0 + $seconds ); return parent::add($key, $value, $ttl); @@ -36,9 +46,15 @@ public function put($key, $value, $ttl = null) return $this->forever($key, $value); } + $seconds = $this->getSeconds($ttl); + + if ($seconds <= 0) { + return false; + } + $this->tags->addEntry( $this->itemKey($key), - $this->getSeconds($ttl) + $seconds ); return parent::put($key, $value, $ttl); diff --git a/src/Illuminate/Cache/Repository.php b/src/Illuminate/Cache/Repository.php index 44f3181f3f4a..98d3cb315b0f 100755 --- a/src/Illuminate/Cache/Repository.php +++ b/src/Illuminate/Cache/Repository.php @@ -48,15 +48,24 @@ class Repository implements ArrayAccess, CacheContract */ protected $default = 3600; + /** + * The cache store configuration options. + * + * @var array + */ + protected $config = []; + /** * Create a new cache repository instance. * * @param \Illuminate\Contracts\Cache\Store $store + * @param array $config * @return void */ - public function __construct(Store $store) + public function __construct(Store $store, array $config = []) { $this->store = $store; + $this->config = $config; } /** @@ -102,11 +111,11 @@ public function get($key, $default = null): mixed // the default value for this cache value. This default could be a callback // so we will execute the value function which will resolve it if needed. if (is_null($value)) { - $this->event(new CacheMissed($key)); + $this->event(new CacheMissed($this->getName(), $key)); $value = value($default); } else { - $this->event(new CacheHit($key, $value)); + $this->event(new CacheHit($this->getName(), $key, $value)); } return $value; @@ -161,7 +170,7 @@ protected function handleManyResult($keys, $key, $value) // the default value for this cache value. This default could be a callback // so we will execute the value function which will resolve it if needed. if (is_null($value)) { - $this->event(new CacheMissed($key)); + $this->event(new CacheMissed($this->getName(), $key)); return (isset($keys[$key]) && ! array_is_list($keys)) ? value($keys[$key]) : null; } @@ -169,7 +178,7 @@ protected function handleManyResult($keys, $key, $value) // If we found a valid value we will fire the "hit" event and return the value // back from this function. The "hit" event gives developers an opportunity // to listen for every possible cache "hit" throughout this applications. - $this->event(new CacheHit($key, $value)); + $this->event(new CacheHit($this->getName(), $key, $value)); return $value; } @@ -217,7 +226,7 @@ public function put($key, $value, $ttl = null) $result = $this->store->put($this->itemKey($key), $value, $seconds); if ($result) { - $this->event(new KeyWritten($key, $value, $seconds)); + $this->event(new KeyWritten($this->getName(), $key, $value, $seconds)); } return $result; @@ -256,7 +265,7 @@ public function putMany(array $values, $ttl = null) if ($result) { foreach ($values as $key => $value) { - $this->event(new KeyWritten($key, $value, $seconds)); + $this->event(new KeyWritten($this->getName(), $key, $value, $seconds)); } } @@ -367,7 +376,7 @@ public function forever($key, $value) $result = $this->store->forever($this->itemKey($key), $value); if ($result) { - $this->event(new KeyWritten($key, $value)); + $this->event(new KeyWritten($this->getName(), $key, $value)); } return $result; @@ -450,7 +459,7 @@ public function forget($key) { return tap($this->store->forget($this->itemKey($key)), function ($result) use ($key) { if ($result) { - $this->event(new KeyForgotten($key)); + $this->event(new KeyForgotten($this->getName(), $key)); } }); } @@ -509,6 +518,8 @@ public function tags($names) $cache = $this->store->tags(is_array($names) ? $names : func_get_args()); + $cache->config = $this->config; + if (! is_null($this->events)) { $cache->setEventDispatcher($this->events); } @@ -544,6 +555,16 @@ protected function getSeconds($ttl) return (int) ($duration > 0 ? $duration : 0); } + /** + * Get the name of the cache store. + * + * @return string|null + */ + protected function getName() + { + return $this->config['store'] ?? null; + } + /** * Determine if the current store supports tags. * diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index c14465c6b3fa..4e8e57267ccf 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -6,6 +6,7 @@ use ArrayAccess; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; +use Random\Randomizer; class Arr { @@ -225,6 +226,22 @@ public static function last($array, callable $callback = null, $default = null) return static::first(array_reverse($array, true), $callback, $default); } + /** + * Take the first or last {$limit} items from an array. + * + * @param array $array + * @param int $limit + * @return array + */ + public static function take($array, $limit) + { + if ($limit < 0) { + return array_slice($array, $limit, abs($limit)); + } + + return array_slice($array, 0, $limit); + } + /** * Flatten a multi-dimensional array into a single level. * @@ -476,9 +493,7 @@ public static function keyBy($array, $keyBy) */ public static function prependKeysWith($array, $prependWith) { - return Collection::make($array)->mapWithKeys(function ($item, $key) use ($prependWith) { - return [$prependWith.$key => $item]; - })->all(); + return static::mapWithKeys($array, fn ($item, $key) => [$prependWith.$key => $item]); } /** @@ -493,6 +508,32 @@ public static function only($array, $keys) return array_intersect_key($array, array_flip((array) $keys)); } + /** + * Select an array of values from an array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function select($array, $keys) + { + $keys = static::wrap($keys); + + return static::map($array, function ($item) use ($keys) { + $result = []; + + foreach ($keys as $key) { + if (Arr::accessible($item) && Arr::exists($item, $key)) { + $result[$key] = $item[$key]; + } elseif (is_object($item) && isset($item->{$key})) { + $result[$key] = $item->{$key}; + } + } + + return $result; + }); + } + /** * Pluck an array of values from an array. * @@ -663,24 +704,24 @@ public static function random($array, $number = null, $preserveKeys = false) ); } - if (is_null($number)) { - return $array[array_rand($array)]; + if (empty($array) || (! is_null($number) && $number <= 0)) { + return is_null($number) ? null : []; } - if ((int) $number === 0) { - return []; - } + $keys = (new Randomizer)->pickArrayKeys($array, $requested); - $keys = array_rand($array, $number); + if (is_null($number)) { + return $array[$keys[0]]; + } $results = []; if ($preserveKeys) { - foreach ((array) $keys as $key) { + foreach ($keys as $key) { $results[$key] = $array[$key]; } } else { - foreach ((array) $keys as $key) { + foreach ($keys as $key) { $results[] = $array[$key]; } } @@ -732,20 +773,11 @@ public static function set(&$array, $key, $value) * Shuffle the given array and return the result. * * @param array $array - * @param int|null $seed * @return array */ - public static function shuffle($array, $seed = null) + public static function shuffle($array) { - if (is_null($seed)) { - shuffle($array); - } else { - mt_srand($seed); - shuffle($array); - mt_srand(); - } - - return $array; + return (new Randomizer)->shuffleArray($array); } /** diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 7869e1ce65e0..ac0c367c9075 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -918,6 +918,27 @@ public function only($keys) return new static(Arr::only($this->items, $keys)); } + /** + * Select specific values from the items within the collection. + * + * @param \Illuminate\Support\Enumerable|array|string|null $keys + * @return static + */ + public function select($keys) + { + if (is_null($keys)) { + return new static($this->items); + } + + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } + + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::select($this->items, $keys)); + } + /** * Get and remove the last N items from the collection. * @@ -1125,12 +1146,11 @@ public function shift($count = 1) /** * Shuffle the items in the collection. * - * @param int|null $seed * @return static */ - public function shuffle($seed = null) + public function shuffle() { - return new static(Arr::shuffle($this->items, $seed)); + return new static(Arr::shuffle($this->items)); } /** diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 559e2ecbfec7..a92eec560e05 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -889,10 +889,9 @@ public function search($value, $strict = false); /** * Shuffle the items in the collection. * - * @param int|null $seed * @return static */ - public function shuffle($seed = null); + public function shuffle(); /** * Create chunks representing a "sliding window" view of the items in the collection. diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index 7d25e9d899d1..288cdbc2c95e 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -953,6 +953,41 @@ public function only($keys) }); } + /** + * Select specific values from the items within the collection. + * + * @param \Illuminate\Support\Enumerable|array|string $keys + * @return static + */ + public function select($keys) + { + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } elseif (! is_null($keys)) { + $keys = is_array($keys) ? $keys : func_get_args(); + } + + return new static(function () use ($keys) { + if (is_null($keys)) { + yield from $this; + } else { + foreach ($this as $item) { + $result = []; + + foreach ($keys as $key) { + if (Arr::accessible($item) && Arr::exists($item, $key)) { + $result[$key] = $item[$key]; + } elseif (is_object($item) && isset($item->{$key})) { + $result[$key] = $item->{$key}; + } + } + + yield $result; + } + } + }); + } + /** * Push all of the given items onto the collection. * @@ -1058,12 +1093,11 @@ public function search($value, $strict = false) /** * Shuffle the items in the collection. * - * @param int|null $seed * @return static */ - public function shuffle($seed = null) + public function shuffle() { - return $this->passthru('shuffle', func_get_args()); + return $this->passthru('shuffle', []); } /** @@ -1740,6 +1774,8 @@ protected function passthru($method, array $params) */ protected function now() { - return Carbon::now()->timestamp; + return class_exists(Carbon::class) + ? Carbon::now()->timestamp + : time(); } } diff --git a/src/Illuminate/Config/Repository.php b/src/Illuminate/Config/Repository.php index 640d6731bc27..54d33c26aad6 100644 --- a/src/Illuminate/Config/Repository.php +++ b/src/Illuminate/Config/Repository.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Config\Repository as ConfigContract; use Illuminate\Support\Arr; use Illuminate\Support\Traits\Macroable; +use InvalidArgumentException; class Repository implements ArrayAccess, ConfigContract { @@ -77,6 +78,106 @@ public function getMany($keys) return $config; } + /** + * Get the specified string configuration value. + * + * @param string $key + * @param mixed $default + * @return string + */ + public function string(string $key, $default = null): string + { + $value = $this->get($key, $default); + + if (! is_string($value)) { + throw new InvalidArgumentException( + sprintf('Configuration value for key [%s] must be a string, %s given.', $key, gettype($value)) + ); + } + + return $value; + } + + /** + * Get the specified integer configuration value. + * + * @param string $key + * @param mixed $default + * @return int + */ + public function integer(string $key, $default = null): int + { + $value = $this->get($key, $default); + + if (! is_int($value)) { + throw new InvalidArgumentException( + sprintf('Configuration value for key [%s] must be an integer, %s given.', $key, gettype($value)) + ); + } + + return $value; + } + + /** + * Get the specified float configuration value. + * + * @param string $key + * @param mixed $default + * @return float + */ + public function float(string $key, $default = null): float + { + $value = $this->get($key, $default); + + if (! is_float($value)) { + throw new InvalidArgumentException( + sprintf('Configuration value for key [%s] must be a float, %s given.', $key, gettype($value)) + ); + } + + return $value; + } + + /** + * Get the specified boolean configuration value. + * + * @param string $key + * @param mixed $default + * @return bool + */ + public function boolean(string $key, $default = null): bool + { + $value = $this->get($key, $default); + + if (! is_bool($value)) { + throw new InvalidArgumentException( + sprintf('Configuration value for key [%s] must be a boolean, %s given.', $key, gettype($value)) + ); + } + + return $value; + } + + /** + * Get the specified array configuration value. + * + * @param string $key + * @param mixed $default + * @return array + */ + public function array(string $key, $default = null): array + { + $value = $this->get($key, $default); + + if (! is_array($value)) { + throw new InvalidArgumentException( + sprintf('Configuration value for key [%s] must be an array, %s given.', $key, gettype($value)) + ); + } + + return $value; + } + /** * Set a given configuration value. * diff --git a/src/Illuminate/Console/Concerns/ConfiguresPrompts.php b/src/Illuminate/Console/Concerns/ConfiguresPrompts.php index 7bca27f45376..8ebd3faf7207 100644 --- a/src/Illuminate/Console/Concerns/ConfiguresPrompts.php +++ b/src/Illuminate/Console/Concerns/ConfiguresPrompts.php @@ -2,6 +2,7 @@ namespace Illuminate\Console\Concerns; +use Illuminate\Console\PromptValidationException; use Laravel\Prompts\ConfirmPrompt; use Laravel\Prompts\MultiSearchPrompt; use Laravel\Prompts\MultiSelectPrompt; @@ -11,6 +12,7 @@ use Laravel\Prompts\SelectPrompt; use Laravel\Prompts\SuggestPrompt; use Laravel\Prompts\TextPrompt; +use stdClass; use Symfony\Component\Console\Input\InputInterface; trait ConfiguresPrompts @@ -27,6 +29,8 @@ protected function configurePrompts(InputInterface $input) Prompt::interactive(($input->isInteractive() && defined('STDIN') && stream_isatty(STDIN)) || $this->laravel->runningUnitTests()); + Prompt::validateUsing(fn (Prompt $prompt) => $this->validatePrompt($prompt->value(), $prompt->validate)); + Prompt::fallbackWhen(windows_os() || $this->laravel->runningUnitTests()); TextPrompt::fallbackUsing(fn (TextPrompt $prompt) => $this->promptUntilValid( @@ -132,15 +136,21 @@ protected function promptUntilValid($prompt, $required, $validate) if ($required && ($result === '' || $result === [] || $result === false)) { $this->components->error(is_string($required) ? $required : 'Required.'); - continue; + if ($this->laravel->runningUnitTests()) { + throw new PromptValidationException; + } else { + continue; + } } - if ($validate) { - $error = $validate($result); + $error = is_callable($validate) ? $validate($result) : $this->validatePrompt($result, $validate); - if (is_string($error) && strlen($error) > 0) { - $this->components->error($error); + if (is_string($error) && strlen($error) > 0) { + $this->components->error($error); + if ($this->laravel->runningUnitTests()) { + throw new PromptValidationException; + } else { continue; } } @@ -149,6 +159,76 @@ protected function promptUntilValid($prompt, $required, $validate) } } + /** + * Validate the given prompt value using the validator. + * + * @param mixed $value + * @param mixed $rules + * @return ?string + */ + protected function validatePrompt($value, $rules) + { + if ($rules instanceof stdClass) { + $messages = $rules->messages ?? []; + $attributes = $rules->attributes ?? []; + $rules = $rules->rules ?? null; + } + + if (! $rules) { + return; + } + + $field = 'answer'; + + if (is_array($rules) && ! array_is_list($rules)) { + [$field, $rules] = [key($rules), current($rules)]; + } + + return $this->getPromptValidatorInstance( + $field, $value, $rules, $messages ?? [], $attributes ?? [] + )->errors()->first(); + } + + /** + * Get the validator instance that should be used to validate prompts. + * + * @param string $value + * @param mixed $value + * @param mixed $rules + * @param array $messages + * @param array $attributes + * @return \Illuminate\Validation\Validator + */ + protected function getPromptValidatorInstance($field, $value, $rules, array $messages = [], array $attributes = []) + { + return $this->laravel['validator']->make( + [$field => $value], + [$field => $rules], + empty($messages) ? $this->validationMessages() : $messages, + empty($attributes) ? $this->validationAttributes() : $attributes, + ); + } + + /** + * Get the validation messages that should be used during prompt validation. + * + * @return array + */ + protected function validationMessages() + { + return []; + } + + /** + * Get the validation attributes that should be used during prompt validation. + * + * @return array + */ + protected function validationAttributes() + { + return []; + } + /** * Restore the prompts output. * diff --git a/src/Illuminate/Console/Concerns/CreatesMatchingTest.php b/src/Illuminate/Console/Concerns/CreatesMatchingTest.php index 4182a007ed3a..b36a8c5ca566 100644 --- a/src/Illuminate/Console/Concerns/CreatesMatchingTest.php +++ b/src/Illuminate/Console/Concerns/CreatesMatchingTest.php @@ -14,7 +14,7 @@ trait CreatesMatchingTest */ protected function addTestOptions() { - foreach (['test' => 'PHPUnit', 'pest' => 'Pest'] as $option => $name) { + foreach (['test' => 'Test', 'pest' => 'Pest', 'phpunit' => 'PHPUnit'] as $option => $name) { $this->getDefinition()->addOption(new InputOption( $option, null, @@ -32,13 +32,14 @@ protected function addTestOptions() */ protected function handleTestCreation($path) { - if (! $this->option('test') && ! $this->option('pest')) { + if (! $this->option('test') && ! $this->option('pest') && ! $this->option('phpunit')) { return false; } return $this->callSilent('make:test', [ 'name' => Str::of($path)->after($this->laravel['path'])->beforeLast('.php')->append('Test')->replace('\\', '/'), '--pest' => $this->option('pest'), + '--phpunit' => $this->option('phpunit'), ]) == 0; } } diff --git a/src/Illuminate/Console/Concerns/InteractsWithSignals.php b/src/Illuminate/Console/Concerns/InteractsWithSignals.php index 895072c15c72..c93b98dc4e6d 100644 --- a/src/Illuminate/Console/Concerns/InteractsWithSignals.php +++ b/src/Illuminate/Console/Concerns/InteractsWithSignals.php @@ -17,7 +17,9 @@ trait InteractsWithSignals /** * Define a callback to be run when the given signal(s) occurs. * - * @param iterable|int $signals + * @template TSignals of iterable|int + * + * @param (\Closure():(TSignals))|TSignals $signals * @param callable(int $signal): void $callback * @return void */ @@ -28,7 +30,7 @@ public function trap($signals, $callback) $this->getApplication()->getSignalRegistry(), ); - collect(Arr::wrap($signals)) + collect(Arr::wrap(value($signals))) ->each(fn ($signal) => $this->signals->register($signal, $callback)); }); } diff --git a/src/Illuminate/Console/GeneratorCommand.php b/src/Illuminate/Console/GeneratorCommand.php index f061dc67d384..1dc5ed792ee2 100644 --- a/src/Illuminate/Console/GeneratorCommand.php +++ b/src/Illuminate/Console/GeneratorCommand.php @@ -84,6 +84,7 @@ abstract class GeneratorCommand extends Command implements PromptsForMissingInpu 'namespace', 'new', 'or', + 'parent', 'print', 'private', 'protected', @@ -249,7 +250,7 @@ protected function possibleModels() { $modelPath = is_dir(app_path('Models')) ? app_path('Models') : app_path(); - return collect((new Finder)->files()->depth(0)->in($modelPath)) + return collect(Finder::create()->files()->depth(0)->in($modelPath)) ->map(fn ($file) => $file->getBasename('.php')) ->sort() ->values() @@ -269,7 +270,7 @@ protected function possibleEvents() return []; } - return collect((new Finder)->files()->depth(0)->in($eventPath)) + return collect(Finder::create()->files()->depth(0)->in($eventPath)) ->map(fn ($file) => $file->getBasename('.php')) ->sort() ->values() 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/PromptValidationException.php b/src/Illuminate/Console/PromptValidationException.php new file mode 100644 index 000000000000..218720967a0b --- /dev/null +++ b/src/Illuminate/Console/PromptValidationException.php @@ -0,0 +1,9 @@ +validateArray($key, $value) - : CookieValuePrefix::validate($key, $value, $this->encrypter->getKey()); + : CookieValuePrefix::validate($key, $value, $this->encrypter->getAllKeys()); } /** @@ -240,4 +240,16 @@ public static function serialized($name) { return static::$serialize; } + + /** + * Flush the middleware's global state. + * + * @return void + */ + public static function flushState() + { + static::$neverEncrypt = []; + + static::$serialize = false; + } } diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php index 99670cf0949c..df60c61b6d87 100644 --- a/src/Illuminate/Database/Concerns/ManagesTransactions.php +++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php @@ -119,6 +119,10 @@ protected function handleTransactionException(Throwable $e, $currentAttempt, $ma */ public function beginTransaction() { + foreach ($this->beforeStartingTransaction as $callback) { + $callback($this); + } + $this->createTransaction(); $this->transactions++; diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index c046b00a3fad..9aa5fb61a861 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -5,8 +5,6 @@ use Carbon\CarbonInterval; use Closure; use DateTimeInterface; -use Doctrine\DBAL\Connection as DoctrineConnection; -use Doctrine\DBAL\Types\Type; use Exception; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Events\QueryExecuted; @@ -190,25 +188,18 @@ class Connection implements ConnectionInterface protected $pretending = false; /** - * All of the callbacks that should be invoked before a query is executed. + * All of the callbacks that should be invoked before a transaction is started. * * @var \Closure[] */ - protected $beforeExecutingCallbacks = []; - - /** - * The instance of Doctrine connection. - * - * @var \Doctrine\DBAL\Connection - */ - protected $doctrineConnection; + protected $beforeStartingTransaction = []; /** - * Type mappings that should be registered with new Doctrine connections. + * All of the callbacks that should be invoked before a query is executed. * - * @var array + * @var \Closure[] */ - protected $doctrineTypeMappings = []; + protected $beforeExecutingCallbacks = []; /** * The connection resolvers. @@ -1024,8 +1015,6 @@ protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $ public function reconnect() { if (is_callable($this->reconnector)) { - $this->doctrineConnection = null; - return call_user_func($this->reconnector, $this); } @@ -1052,8 +1041,19 @@ public function reconnectIfMissingConnection() public function disconnect() { $this->setPdo(null)->setReadPdo(null); + } + + /** + * Register a hook to be run just before a database transaction is started. + * + * @param \Closure $callback + * @return $this + */ + public function beforeStartingTransaction(Closure $callback) + { + $this->beforeStartingTransaction[] = $callback; - $this->doctrineConnection = null; + return $this; } /** @@ -1243,106 +1243,6 @@ public function useWriteConnectionWhenReading($value = true) return $this; } - /** - * Is Doctrine available? - * - * @return bool - */ - public function isDoctrineAvailable() - { - return class_exists('Doctrine\DBAL\Connection'); - } - - /** - * Indicates whether native alter operations will be used when dropping, renaming, or modifying columns, even if Doctrine DBAL is installed. - * - * @return bool - */ - public function usingNativeSchemaOperations() - { - return ! $this->isDoctrineAvailable() || SchemaBuilder::$alwaysUsesNativeSchemaOperationsIfPossible; - } - - /** - * Get a Doctrine Schema Column instance. - * - * @param string $table - * @param string $column - * @return \Doctrine\DBAL\Schema\Column - */ - public function getDoctrineColumn($table, $column) - { - $schema = $this->getDoctrineSchemaManager(); - - return $schema->introspectTable($table)->getColumn($column); - } - - /** - * Get the Doctrine DBAL schema manager for the connection. - * - * @return \Doctrine\DBAL\Schema\AbstractSchemaManager - */ - public function getDoctrineSchemaManager() - { - $connection = $this->getDoctrineConnection(); - - return $connection->createSchemaManager(); - } - - /** - * Get the Doctrine DBAL database connection instance. - * - * @return \Doctrine\DBAL\Connection - */ - public function getDoctrineConnection() - { - if (is_null($this->doctrineConnection)) { - $driver = $this->getDoctrineDriver(); - - $this->doctrineConnection = new DoctrineConnection(array_filter([ - 'pdo' => $this->getPdo(), - 'dbname' => $this->getDatabaseName(), - 'driver' => $driver->getName(), - 'serverVersion' => $this->getConfig('server_version'), - ]), $driver); - - foreach ($this->doctrineTypeMappings as $name => $type) { - $this->doctrineConnection - ->getDatabasePlatform() - ->registerDoctrineTypeMapping($type, $name); - } - } - - return $this->doctrineConnection; - } - - /** - * Register a custom Doctrine mapping type. - * - * @param Type|class-string $class - * @param string $name - * @param string $type - * @return void - * - * @throws \Doctrine\DBAL\Exception - * @throws \RuntimeException - */ - public function registerDoctrineType(Type|string $class, string $name, string $type): void - { - if (! $this->isDoctrineAvailable()) { - throw new RuntimeException( - 'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).' - ); - } - - if (! Type::hasType($name)) { - Type::getTypeRegistry() - ->register($name, is_string($class) ? new $class() : $class); - } - - $this->doctrineTypeMappings[$name] = $type; - } - /** * Get the current PDO connection. * @@ -1757,6 +1657,16 @@ public function withTablePrefix(Grammar $grammar) return $grammar; } + /** + * Get the server version for the connection. + * + * @return string + */ + public function getServerVersion(): string + { + return $this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); + } + /** * Register a connection resolver. * diff --git a/src/Illuminate/Database/Connectors/ConnectionFactory.php b/src/Illuminate/Database/Connectors/ConnectionFactory.php index 80b25d0223a6..f63123652747 100755 --- a/src/Illuminate/Database/Connectors/ConnectionFactory.php +++ b/src/Illuminate/Database/Connectors/ConnectionFactory.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Container\Container; use Illuminate\Database\Connection; +use Illuminate\Database\MariaDbConnection; use Illuminate\Database\MySqlConnection; use Illuminate\Database\PostgresConnection; use Illuminate\Database\SQLiteConnection; @@ -241,6 +242,7 @@ public function createConnector(array $config) return match ($config['driver']) { 'mysql' => new MySqlConnector, + 'mariadb' => new MariaDbConnector, 'pgsql' => new PostgresConnector, 'sqlite' => new SQLiteConnector, 'sqlsrv' => new SqlServerConnector, @@ -268,6 +270,7 @@ protected function createConnection($driver, $connection, $database, $prefix = ' return match ($driver) { 'mysql' => new MySqlConnection($connection, $database, $prefix, $config), + 'mariadb' => new MariaDbConnection($connection, $database, $prefix, $config), 'pgsql' => new PostgresConnection($connection, $database, $prefix, $config), 'sqlite' => new SQLiteConnection($connection, $database, $prefix, $config), 'sqlsrv' => new SqlServerConnection($connection, $database, $prefix, $config), diff --git a/src/Illuminate/Database/Connectors/MariaDbConnector.php b/src/Illuminate/Database/Connectors/MariaDbConnector.php new file mode 100755 index 000000000000..560e31c96916 --- /dev/null +++ b/src/Illuminate/Database/Connectors/MariaDbConnector.php @@ -0,0 +1,32 @@ +exec("use `{$config['database']}`;"); } - $this->configureIsolationLevel($connection, $config); - - $this->configureEncoding($connection, $config); - - // Next, we will check to see if a timezone has been specified in this config - // and if it has we will issue a statement to modify the timezone with the - // database. Setting this DB timezone is an optional configuration item. - $this->configureTimezone($connection, $config); - - $this->setModes($connection, $config); + $this->configureConnection($connection, $config); return $connection; } - /** - * Set the connection transaction isolation level. - * - * @param \PDO $connection - * @param array $config - * @return void - */ - protected function configureIsolationLevel($connection, array $config) - { - if (! isset($config['isolation_level'])) { - return; - } - - $connection->prepare( - "SET SESSION TRANSACTION ISOLATION LEVEL {$config['isolation_level']}" - )->execute(); - } - - /** - * Set the connection character set and collation. - * - * @param \PDO $connection - * @param array $config - * @return void|\PDO - */ - protected function configureEncoding($connection, array $config) - { - if (! isset($config['charset'])) { - return $connection; - } - - $connection->prepare( - "set names '{$config['charset']}'".$this->getCollation($config) - )->execute(); - } - - /** - * Get the collation for the configuration. - * - * @param array $config - * @return string - */ - protected function getCollation(array $config) - { - return isset($config['collation']) ? " collate '{$config['collation']}'" : ''; - } - - /** - * Set the timezone on the connection. - * - * @param \PDO $connection - * @param array $config - * @return void - */ - protected function configureTimezone($connection, array $config) - { - if (isset($config['timezone'])) { - $connection->prepare('set time_zone="'.$config['timezone'].'"')->execute(); - } - } - /** * Create a DSN string from a configuration. * @@ -155,54 +85,70 @@ protected function getHostDsn(array $config) } /** - * Set the modes for the connection. + * Configure the given PDO connection. * * @param \PDO $connection * @param array $config * @return void */ - protected function setModes(PDO $connection, array $config) + protected function configureConnection(PDO $connection, array $config) { - if (isset($config['modes'])) { - $this->setCustomModes($connection, $config); - } elseif (isset($config['strict'])) { - if ($config['strict']) { - $connection->prepare($this->strictMode($connection, $config))->execute(); + $statements = []; + + if (isset($config['isolation_level'])) { + $statements[] = sprintf('SESSION TRANSACTION ISOLATION LEVEL %s', $config['isolation_level']); + } + + if (isset($config['charset'])) { + if (isset($config['collation'])) { + $statements[] = sprintf("NAMES '%s' COLLATE '%s'", $config['charset'], $config['collation']); } else { - $connection->prepare("set session sql_mode='NO_ENGINE_SUBSTITUTION'")->execute(); + $statements[] = sprintf("NAMES '%s'", $config['charset']); } } - } - /** - * Set the custom modes on the connection. - * - * @param \PDO $connection - * @param array $config - * @return void - */ - protected function setCustomModes(PDO $connection, array $config) - { - $modes = implode(',', $config['modes']); + if (isset($config['timezone'])) { + $statements[] = sprintf("time_zone='%s'", $config['timezone']); + } - $connection->prepare("set session sql_mode='{$modes}'")->execute(); + $sqlMode = $this->getSqlMode($connection, $config); + + if ($sqlMode !== null) { + $statements[] = sprintf("SESSION sql_mode='%s'", $sqlMode); + } + + if ($statements !== []) { + $connection->exec(sprintf('SET %s;', implode(', ', $statements))); + } } /** - * Get the query to enable strict mode. + * Get the sql_mode value. * * @param \PDO $connection * @param array $config - * @return string + * @return string|null */ - protected function strictMode(PDO $connection, $config) + protected function getSqlMode(PDO $connection, array $config) { + if (isset($config['modes'])) { + return implode(',', $config['modes']); + } + + if (! isset($config['strict'])) { + return null; + } + + if (! $config['strict']) { + return 'NO_ENGINE_SUBSTITUTION'; + } + $version = $config['version'] ?? $connection->getAttribute(PDO::ATTR_SERVER_VERSION); if (version_compare($version, '8.0.11') >= 0) { - return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'"; + return 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; } - return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'"; + return 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; } } diff --git a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php index 42568fc2c02d..f5ba9036e6ef 100644 --- a/src/Illuminate/Database/Console/DatabaseInspectionCommand.php +++ b/src/Illuminate/Database/Console/DatabaseInspectionCommand.php @@ -2,169 +2,37 @@ namespace Illuminate\Database\Console; -use Doctrine\DBAL\Platforms\AbstractPlatform; use Illuminate\Console\Command; use Illuminate\Database\ConnectionInterface; +use Illuminate\Database\MariaDbConnection; use Illuminate\Database\MySqlConnection; use Illuminate\Database\PostgresConnection; -use Illuminate\Database\QueryException; use Illuminate\Database\SQLiteConnection; use Illuminate\Database\SqlServerConnection; use Illuminate\Support\Arr; -use Illuminate\Support\Composer; -use Symfony\Component\Process\Exception\ProcessSignaledException; -use Symfony\Component\Process\Exception\RuntimeException; -use Symfony\Component\Process\Process; - -use function Laravel\Prompts\confirm; abstract class DatabaseInspectionCommand extends Command { /** - * A map of database column types. - * - * @var array - */ - protected $typeMappings = [ - 'bit' => 'string', - 'citext' => 'string', - 'enum' => 'string', - 'geometry' => 'string', - 'geomcollection' => 'string', - 'linestring' => 'string', - 'ltree' => 'string', - 'multilinestring' => 'string', - 'multipoint' => 'string', - 'multipolygon' => 'string', - 'point' => 'string', - 'polygon' => 'string', - 'sysname' => 'string', - ]; - - /** - * The Composer instance. - * - * @var \Illuminate\Support\Composer - */ - protected $composer; - - /** - * Create a new command instance. - * - * @param \Illuminate\Support\Composer|null $composer - * @return void - */ - public function __construct(Composer $composer = null) - { - parent::__construct(); - - $this->composer = $composer ?? $this->laravel->make(Composer::class); - } - - /** - * Register the custom Doctrine type mappings for inspection commands. - * - * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform - * @return void - */ - protected function registerTypeMappings(AbstractPlatform $platform) - { - foreach ($this->typeMappings as $type => $value) { - $platform->registerDoctrineTypeMapping($type, $value); - } - } - - /** - * Get a human-readable platform name for the given platform. + * Get a human-readable name for the given connection. * - * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * @param \Illuminate\Database\ConnectionInterface $connection * @param string $database * @return string */ - protected function getPlatformName(AbstractPlatform $platform, $database) - { - return match (class_basename($platform)) { - 'MySQLPlatform' => 'MySQL <= 5', - 'MySQL57Platform' => 'MySQL 5.7', - 'MySQL80Platform' => 'MySQL 8', - 'PostgreSQL100Platform', 'PostgreSQLPlatform' => 'Postgres', - 'SqlitePlatform' => 'SQLite', - 'SQLServerPlatform' => 'SQL Server', - 'SQLServer2012Platform' => 'SQL Server 2012', - default => $database, - }; - } - - /** - * Get the size of a table in bytes. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param string $table - * @return int|null - */ - protected function getTableSize(ConnectionInterface $connection, string $table) + protected function getConnectionName(ConnectionInterface $connection, $database) { return match (true) { - $connection instanceof MySqlConnection => $this->getMySQLTableSize($connection, $table), - $connection instanceof PostgresConnection => $this->getPostgresTableSize($connection, $table), - $connection instanceof SQLiteConnection => $this->getSqliteTableSize($connection, $table), - default => null, + $connection instanceof MySqlConnection && $connection->isMaria() => 'MariaDB', + $connection instanceof MySqlConnection => 'MySQL', + $connection instanceof MariaDbConnection => 'MariaDB', + $connection instanceof PostgresConnection => 'PostgreSQL', + $connection instanceof SQLiteConnection => 'SQLite', + $connection instanceof SqlServerConnection => 'SQL Server', + default => $database, }; } - /** - * Get the size of a MySQL table in bytes. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param string $table - * @return mixed - */ - protected function getMySQLTableSize(ConnectionInterface $connection, string $table) - { - $result = $connection->selectOne('SELECT (data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = ? AND table_name = ?', [ - $connection->getDatabaseName(), - $table, - ]); - - return Arr::wrap((array) $result)['size']; - } - - /** - * Get the size of a Postgres table in bytes. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param string $table - * @return mixed - */ - protected function getPostgresTableSize(ConnectionInterface $connection, string $table) - { - $result = $connection->selectOne('SELECT pg_total_relation_size(?) AS size;', [ - $table, - ]); - - return Arr::wrap((array) $result)['size']; - } - - /** - * Get the size of a SQLite table in bytes. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param string $table - * @return mixed - */ - protected function getSqliteTableSize(ConnectionInterface $connection, string $table) - { - try { - $result = $connection->selectOne('SELECT SUM(pgsize) AS size FROM dbstat WHERE name=?', [ - $table, - ]); - - return Arr::wrap((array) $result)['size']; - } catch (QueryException) { - return null; - } - } - /** * Get the number of open connections for a database. * @@ -175,8 +43,8 @@ protected function getConnectionCount(ConnectionInterface $connection) { $result = match (true) { $connection instanceof MySqlConnection => $connection->selectOne('show status where variable_name = "threads_connected"'), - $connection instanceof PostgresConnection => $connection->selectOne('select count(*) AS "Value" from pg_stat_activity'), - $connection instanceof SqlServerConnection => $connection->selectOne('SELECT COUNT(*) Value FROM sys.dm_exec_sessions WHERE status = ?', ['running']), + $connection instanceof PostgresConnection => $connection->selectOne('select count(*) as "Value" from pg_stat_activity'), + $connection instanceof SqlServerConnection => $connection->selectOne('select count(*) Value from sys.dm_exec_sessions where status = ?', ['running']), default => null, }; @@ -201,48 +69,18 @@ protected function getConfigFromDatabase($database) } /** - * Ensure the dependencies for the database commands are available. - * - * @return bool - */ - protected function ensureDependenciesExist() - { - return tap(interface_exists('Doctrine\DBAL\Driver'), function ($dependenciesExist) { - if (! $dependenciesExist && confirm('Inspecting database information requires the Doctrine DBAL (doctrine/dbal) package. Would you like to install it?', default: false)) { - $this->installDependencies(); - } - }); - } - - /** - * Install the command's dependencies. - * - * @return void + * Remove the table prefix from a table name, if it exists. * - * @throws \Symfony\Component\Process\Exception\ProcessSignaledException + * @param \Illuminate\Database\ConnectionInterface $connection + * @param string $table + * @return string */ - protected function installDependencies() + protected function withoutTablePrefix(ConnectionInterface $connection, string $table) { - $command = collect($this->composer->findComposer()) - ->push('require doctrine/dbal:^3.5.1') - ->implode(' '); - - $process = Process::fromShellCommandline($command, null, null, null, null); + $prefix = $connection->getTablePrefix(); - if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { - try { - $process->setTty(true); - } catch (RuntimeException $e) { - $this->components->warn($e->getMessage()); - } - } - - try { - $process->run(fn ($type, $line) => $this->output->write($line)); - } catch (ProcessSignaledException $e) { - if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) { - throw $e; - } - } + return str_starts_with($table, $prefix) + ? substr($table, strlen($prefix)) + : $table; } } diff --git a/src/Illuminate/Database/Console/DbCommand.php b/src/Illuminate/Database/Console/DbCommand.php index caecafe3a644..dce6bc32dd17 100644 --- a/src/Illuminate/Database/Console/DbCommand.php +++ b/src/Illuminate/Database/Console/DbCommand.php @@ -131,6 +131,7 @@ public function getCommand(array $connection) { return [ 'mysql' => 'mysql', + 'mariadb' => 'mysql', 'pgsql' => 'psql', 'sqlite' => 'sqlite3', 'sqlsrv' => 'sqlcmd', @@ -156,6 +157,17 @@ protected function getMysqlArguments(array $connection) ], $connection), [$connection['database']]); } + /** + * Get the arguments for the MariaDB CLI. + * + * @param array $connection + * @return array + */ + protected function getMariaDbArguments(array $connection) + { + return $this->getMysqlArguments($connection); + } + /** * Get the arguments for the Postgres CLI. * @@ -192,6 +204,7 @@ protected function getSqlsrvArguments(array $connection) 'password' => ['-P', $connection['password']], 'host' => ['-S', 'tcp:'.$connection['host'] .($connection['port'] ? ','.$connection['port'] : ''), ], + 'trust_server_certificate' => ['-C'], ], $connection)); } diff --git a/src/Illuminate/Database/Console/Migrations/MigrateCommand.php b/src/Illuminate/Database/Console/Migrations/MigrateCommand.php index d7a8c9e35a11..5f7e1c87b10f 100755 --- a/src/Illuminate/Database/Console/Migrations/MigrateCommand.php +++ b/src/Illuminate/Database/Console/Migrations/MigrateCommand.php @@ -148,7 +148,7 @@ protected function repositoryExists() if ( $e->getPrevious() instanceof PDOException && $e->getPrevious()->getCode() === 1049 && - $connection->getDriverName() === 'mysql') { + in_array($connection->getDriverName(), ['mysql', 'mariadb'])) { return $this->createMissingMysqlDatabase($connection); } @@ -175,9 +175,9 @@ protected function createMissingSqliteDatabase($path) return false; } - $this->components->warn('The SQLite database does not exist: '.$path); + $this->components->warn('The SQLite database configured for this application does not exist: '.$path); - if (! confirm('Would you like to create it?', default: false)) { + if (! confirm('Would you like to create it?', default: true)) { return false; } @@ -202,7 +202,7 @@ protected function createMissingMysqlDatabase($connection) if (! $this->option('force') && ! $this->option('no-interaction')) { $this->components->warn("The database '{$connection->getDatabaseName()}' does not exist on the '{$connection->getName()}' connection."); - if (! confirm('Would you like to create it?', default: false)) { + if (! confirm('Would you like to create it?', default: true)) { return false; } } diff --git a/src/Illuminate/Database/Console/MonitorCommand.php b/src/Illuminate/Database/Console/MonitorCommand.php index 3dff3158268c..d87a441c015c 100644 --- a/src/Illuminate/Database/Console/MonitorCommand.php +++ b/src/Illuminate/Database/Console/MonitorCommand.php @@ -5,7 +5,6 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Events\DatabaseBusy; -use Illuminate\Support\Composer; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'db:monitor')] @@ -46,11 +45,10 @@ class MonitorCommand extends DatabaseInspectionCommand * * @param \Illuminate\Database\ConnectionResolverInterface $connection * @param \Illuminate\Contracts\Events\Dispatcher $events - * @param \Illuminate\Support\Composer $composer */ - public function __construct(ConnectionResolverInterface $connection, Dispatcher $events, Composer $composer) + public function __construct(ConnectionResolverInterface $connection, Dispatcher $events) { - parent::__construct($composer); + parent::__construct(); $this->connection = $connection; $this->events = $events; diff --git a/src/Illuminate/Database/Console/PruneCommand.php b/src/Illuminate/Database/Console/PruneCommand.php index 0c51beb345b2..23875d1187b9 100644 --- a/src/Illuminate/Database/Console/PruneCommand.php +++ b/src/Illuminate/Database/Console/PruneCommand.php @@ -24,6 +24,7 @@ class PruneCommand extends Command protected $signature = 'model:prune {--model=* : Class names of the models to be pruned} {--except=* : Class names of the models to be excluded from pruning} + {--path=* : Absolute path(s) to directories where models are located} {--chunk=1000 : The number of models to retrieve per chunk of models to be deleted} {--pretend : Display the number of prunable records found instead of deleting them}'; @@ -125,7 +126,7 @@ protected function models() throw new InvalidArgumentException('The --models and --except options cannot be combined.'); } - return collect((new Finder)->in($this->getDefaultPath())->files()->name('*.php')) + return collect(Finder::create()->in($this->getPath())->files()->name('*.php')) ->map(function ($model) { $namespace = $this->laravel->getNamespace(); @@ -146,12 +147,18 @@ protected function models() } /** - * Get the default path where models are located. + * Get the path where models are located. * - * @return string|string[] + * @return string[]|string */ - protected function getDefaultPath() + protected function getPath() { + if (! empty($path = $this->option('path'))) { + return collect($path)->map(function ($path) { + return base_path($path); + })->all(); + } + return app_path('Models'); } diff --git a/src/Illuminate/Database/Console/ShowCommand.php b/src/Illuminate/Database/Console/ShowCommand.php index c125c38820e4..d711a01bc685 100644 --- a/src/Illuminate/Database/Console/ShowCommand.php +++ b/src/Illuminate/Database/Console/ShowCommand.php @@ -2,12 +2,11 @@ namespace Illuminate\Database\Console; -use Doctrine\DBAL\Schema\AbstractSchemaManager; -use Doctrine\DBAL\Schema\Table; -use Doctrine\DBAL\Schema\View; use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionResolverInterface; +use Illuminate\Database\Schema\Builder; use Illuminate\Support\Arr; +use Illuminate\Support\Number; use Symfony\Component\Console\Attribute\AsCommand; #[AsCommand(name: 'db:show')] @@ -20,8 +19,9 @@ class ShowCommand extends DatabaseInspectionCommand */ protected $signature = 'db:show {--database= : The database connection} {--json : Output the database information as JSON} - {--counts : Show the table row count Note: This can be slow on large databases }; - {--views : Show the database views Note: This can be slow on large databases }'; + {--counts : Show the table row count Note: This can be slow on large databases } + {--views : Show the database views Note: This can be slow on large databases } + {--types : Show the user defined types}'; /** * The console command description. @@ -38,28 +38,26 @@ class ShowCommand extends DatabaseInspectionCommand */ public function handle(ConnectionResolverInterface $connections) { - if (! $this->ensureDependenciesExist()) { - return 1; - } - $connection = $connections->connection($database = $this->input->getOption('database')); - $doctrineConnection = $connection->getDoctrineConnection(); - $schema = $connection->getDoctrineSchemaManager(); - - $this->registerTypeMappings($doctrineConnection->getDatabasePlatform()); + $schema = $connection->getSchemaBuilder(); $data = [ 'platform' => [ 'config' => $this->getConfigFromDatabase($database), - 'name' => $this->getPlatformName($doctrineConnection->getDatabasePlatform(), $database), + 'name' => $this->getConnectionName($connection, $database), + 'version' => $connection->getServerVersion(), 'open_connections' => $this->getConnectionCount($connection), ], 'tables' => $this->tables($connection, $schema), ]; if ($this->option('views')) { - $data['views'] = $this->collectViews($connection, $schema); + $data['views'] = $this->views($connection, $schema); + } + + if ($this->option('types')) { + $data['types'] = $this->types($connection, $schema); } $this->display($data); @@ -71,17 +69,19 @@ public function handle(ConnectionResolverInterface $connections) * Get information regarding the tables within the database. * * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @param \Illuminate\Database\Schema\Builder $schema * @return \Illuminate\Support\Collection */ - protected function tables(ConnectionInterface $connection, AbstractSchemaManager $schema) + protected function tables(ConnectionInterface $connection, Builder $schema) { - return collect($schema->listTables())->map(fn (Table $table, $index) => [ - 'table' => $table->getName(), - 'size' => $this->getTableSize($connection, $table->getName()), + return collect($schema->getTables())->map(fn ($table) => [ + 'table' => $table['name'], + 'schema' => $table['schema'], + 'size' => $table['size'], 'rows' => $this->option('counts') ? $connection->table($table->getName())->count() : null, - 'engine' => rescue(fn () => $table->getOption('engine'), null, false), - 'comment' => $table->getComment(), + 'engine' => $table['engine'], + 'collation' => $table['collation'], + 'comment' => $table['comment'], ]); } @@ -89,20 +89,38 @@ protected function tables(ConnectionInterface $connection, AbstractSchemaManager * Get information regarding the views within the database. * * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @param \Illuminate\Database\Schema\Builder $schema * @return \Illuminate\Support\Collection */ - protected function collectViews(ConnectionInterface $connection, AbstractSchemaManager $schema) + protected function views(ConnectionInterface $connection, Builder $schema) { - return collect($schema->listViews()) - ->reject(fn (View $view) => str($view->getName()) - ->startsWith(['pg_catalog', 'information_schema', 'spt_'])) - ->map(fn (View $view) => [ - 'view' => $view->getName(), + return collect($schema->getViews()) + ->reject(fn ($view) => str($view['name'])->startsWith(['pg_catalog', 'information_schema', 'spt_'])) + ->map(fn ($view) => [ + 'view' => $view['name'], + 'schema' => $view['schema'], 'rows' => $connection->table($view->getName())->count(), ]); } + /** + * Get information regarding the user-defined types within the database. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\Schema\Builder $schema + * @return \Illuminate\Support\Collection + */ + protected function types(ConnectionInterface $connection, Builder $schema) + { + return collect($schema->getTypes()) + ->map(fn ($type) => [ + 'name' => $type['name'], + 'schema' => $type['schema'], + 'type' => $type['type'], + 'category' => $type['category'], + ]); + } + /** * Render the database information. * @@ -136,10 +154,11 @@ protected function displayForCli(array $data) $platform = $data['platform']; $tables = $data['tables']; $views = $data['views'] ?? null; + $types = $data['types'] ?? null; $this->newLine(); - $this->components->twoColumnDetail(''.$platform['name'].''); + $this->components->twoColumnDetail(''.$platform['name'].'', $platform['version']); $this->components->twoColumnDetail('Database', Arr::get($platform['config'], 'database')); $this->components->twoColumnDetail('Host', Arr::get($platform['config'], 'host')); $this->components->twoColumnDetail('Port', Arr::get($platform['config'], 'port')); @@ -149,22 +168,27 @@ protected function displayForCli(array $data) $this->components->twoColumnDetail('Tables', $tables->count()); if ($tableSizeSum = $tables->sum('size')) { - $this->components->twoColumnDetail('Total Size', number_format($tableSizeSum / 1024 / 1024, 2).'MiB'); + $this->components->twoColumnDetail('Total Size', Number::fileSize($tableSizeSum, 2)); } $this->newLine(); if ($tables->isNotEmpty()) { - $this->components->twoColumnDetail('Table', 'Size (MiB)'.($this->option('counts') ? ' / Rows' : '')); + $hasSchema = ! is_null($tables->first()['schema']); + + $this->components->twoColumnDetail( + ($hasSchema ? 'Schema / ' : '').'Table', + 'Size'.($this->option('counts') ? ' / Rows' : '') + ); $tables->each(function ($table) { if ($tableSize = $table['size']) { - $tableSize = number_format($tableSize / 1024 / 1024, 2); + $tableSize = Number::fileSize($tableSize, 2); } $this->components->twoColumnDetail( - $table['table'].($this->output->isVerbose() ? ' '.$table['engine'].'' : null), - ($tableSize ? $tableSize : '—').($this->option('counts') ? ' / '.number_format($table['rows']).'' : '') + ($table['schema'] ? $table['schema'].' / ' : '').$table['table'].($this->output->isVerbose() ? ' '.$table['engine'].'' : null), + ($tableSize ?: '—').($this->option('counts') ? ' / '.Number::format($table['rows']).'' : '') ); if ($this->output->isVerbose()) { @@ -180,9 +204,33 @@ protected function displayForCli(array $data) } if ($views && $views->isNotEmpty()) { - $this->components->twoColumnDetail('View', 'Rows'); + $hasSchema = ! is_null($views->first()['schema']); + + $this->components->twoColumnDetail( + ($hasSchema ? 'Schema / ' : '').'View', + 'Rows' + ); + + $views->each(fn ($view) => $this->components->twoColumnDetail( + ($view['schema'] ? $view['schema'].' / ' : '').$view['view'], + Number::format($view['rows']) + )); + + $this->newLine(); + } + + if ($types && $types->isNotEmpty()) { + $hasSchema = ! is_null($types->first()['schema']); + + $this->components->twoColumnDetail( + ($hasSchema ? 'Schema / ' : '').'Type', + 'Type / Category' + ); - $views->each(fn ($view) => $this->components->twoColumnDetail($view['view'], number_format($view['rows']))); + $types->each(fn ($type) => $this->components->twoColumnDetail( + ($type['schema'] ? $type['schema'].' / ' : '').$type['name'], + $type['type'].' / '.$type['category'] + )); $this->newLine(); } diff --git a/src/Illuminate/Database/Console/ShowModelCommand.php b/src/Illuminate/Database/Console/ShowModelCommand.php index 3ef912004e8c..4ab262546c15 100644 --- a/src/Illuminate/Database/Console/ShowModelCommand.php +++ b/src/Illuminate/Database/Console/ShowModelCommand.php @@ -3,9 +3,6 @@ namespace Illuminate\Database\Console; use BackedEnum; -use Doctrine\DBAL\Schema\Column; -use Doctrine\DBAL\Schema\Index; -use Doctrine\DBAL\Types\DecimalType; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; @@ -70,10 +67,6 @@ class ShowModelCommand extends DatabaseInspectionCommand */ public function handle() { - if (! $this->ensureDependenciesExist()) { - return 1; - } - $class = $this->qualifyModel($this->argument('model')); try { @@ -81,7 +74,9 @@ public function handle() $class = get_class($model); } catch (BindingResolutionException $e) { - return $this->components->error($e->getMessage()); + $this->components->error($e->getMessage()); + + return 1; } if ($this->option('database')) { @@ -97,6 +92,8 @@ public function handle() $this->getRelations($model), $this->getObservers($model), ); + + return 0; } /** @@ -121,25 +118,23 @@ protected function getPolicy($model) protected function getAttributes($model) { $connection = $model->getConnection(); - $schema = $connection->getDoctrineSchemaManager(); - $this->registerTypeMappings($connection->getDoctrineConnection()->getDatabasePlatform()); - $table = $model->getConnection()->getTablePrefix().$model->getTable(); - $columns = $schema->listTableColumns($table); - $indexes = $schema->listTableIndexes($table); + $schema = $connection->getSchemaBuilder(); + $table = $model->getTable(); + $columns = $schema->getColumns($table); + $indexes = $schema->getIndexes($table); return collect($columns) - ->values() - ->map(fn (Column $column) => [ - 'name' => $column->getName(), - 'type' => $this->getColumnType($column), - 'increments' => $column->getAutoincrement(), - 'nullable' => ! $column->getNotnull(), + ->map(fn ($column) => [ + 'name' => $column['name'], + 'type' => $column['type'], + 'increments' => $column['auto_increment'], + 'nullable' => $column['nullable'], 'default' => $this->getColumnDefault($column, $model), - 'unique' => $this->columnIsUnique($column->getName(), $indexes), - 'fillable' => $model->isFillable($column->getName()), - 'hidden' => $this->attributeIsHidden($column->getName(), $model), + 'unique' => $this->columnIsUnique($column['name'], $indexes), + 'fillable' => $model->isFillable($column['name']), + 'hidden' => $this->attributeIsHidden($column['name'], $model), 'appended' => null, - 'cast' => $this->getCastType($column->getName(), $model), + 'cast' => $this->getCastType($column['name'], $model), ]) ->merge($this->getVirtualAttributes($model, $columns)); } @@ -148,7 +143,7 @@ protected function getAttributes($model) * Get the virtual (non-column) attributes for the given model. * * @param \Illuminate\Database\Eloquent\Model $model - * @param \Doctrine\DBAL\Schema\Column[] $columns + * @param array $columns * @return \Illuminate\Support\Collection */ protected function getVirtualAttributes($model, $columns) @@ -170,7 +165,7 @@ protected function getVirtualAttributes($model, $columns) return []; } }) - ->reject(fn ($cast, $name) => collect($columns)->has($name)) + ->reject(fn ($cast, $name) => collect($columns)->contains('name', $name)) ->map(fn ($cast, $name) => [ 'name' => $name, 'type' => null, @@ -428,45 +423,21 @@ protected function getCastsWithDates($model) ->merge($model->getCasts()); } - /** - * Get the type of the given column. - * - * @param \Doctrine\DBAL\Schema\Column $column - * @return string - */ - protected function getColumnType($column) - { - $name = $column->getType()->getName(); - - $unsigned = $column->getUnsigned() ? ' unsigned' : ''; - - $details = match (get_class($column->getType())) { - DecimalType::class => $column->getPrecision().','.$column->getScale(), - default => $column->getLength(), - }; - - if ($details) { - return sprintf('%s(%s)%s', $name, $details, $unsigned); - } - - return sprintf('%s%s', $name, $unsigned); - } - /** * Get the default value for the given column. * - * @param \Doctrine\DBAL\Schema\Column $column + * @param array $column * @param \Illuminate\Database\Eloquent\Model $model * @return mixed|null */ protected function getColumnDefault($column, $model) { - $attributeDefault = $model->getAttributes()[$column->getName()] ?? null; + $attributeDefault = $model->getAttributes()[$column['name']] ?? null; return match (true) { $attributeDefault instanceof BackedEnum => $attributeDefault->value, $attributeDefault instanceof UnitEnum => $attributeDefault->name, - default => $attributeDefault ?? $column->getDefault(), + default => $attributeDefault ?? $column['default'], }; } @@ -494,14 +465,14 @@ protected function attributeIsHidden($attribute, $model) * Determine if the given attribute is unique. * * @param string $column - * @param \Doctrine\DBAL\Schema\Index[] $indexes + * @param array $indexes * @return bool */ protected function columnIsUnique($column, $indexes) { - return collect($indexes) - ->filter(fn (Index $index) => count($index->getColumns()) === 1 && $index->getColumns()[0] === $column) - ->contains(fn (Index $index) => $index->isUnique()); + return collect($indexes)->contains( + fn ($index) => count($index['columns']) === 1 && $index['columns'][0] === $column && $index['unique'] + ); } /** diff --git a/src/Illuminate/Database/Console/TableCommand.php b/src/Illuminate/Database/Console/TableCommand.php index 1a7407d5791b..0bd67e1fdba1 100644 --- a/src/Illuminate/Database/Console/TableCommand.php +++ b/src/Illuminate/Database/Console/TableCommand.php @@ -2,12 +2,10 @@ namespace Illuminate\Database\Console; -use Doctrine\DBAL\Schema\Column; -use Doctrine\DBAL\Schema\ForeignKeyConstraint; -use Doctrine\DBAL\Schema\Index; -use Doctrine\DBAL\Schema\Table; use Illuminate\Database\ConnectionResolverInterface; -use Illuminate\Support\Str; +use Illuminate\Database\Schema\Builder; +use Illuminate\Support\Arr; +use Illuminate\Support\Number; use Symfony\Component\Console\Attribute\AsCommand; use function Laravel\Prompts\select; @@ -39,36 +37,34 @@ class TableCommand extends DatabaseInspectionCommand */ public function handle(ConnectionResolverInterface $connections) { - if (! $this->ensureDependenciesExist()) { - return 1; - } - $connection = $connections->connection($this->input->getOption('database')); + $schema = $connection->getSchemaBuilder(); + $tables = $schema->getTables(); - $schema = $connection->getDoctrineSchemaManager(); - - $this->registerTypeMappings($connection->getDoctrineConnection()->getDatabasePlatform()); - - $table = $this->argument('table') ?: select( + $tableName = $this->argument('table') ?: select( 'Which table would you like to inspect?', - collect($schema->listTables())->flatMap(fn (Table $table) => [$table->getName()])->toArray() + array_column($tables, 'name') ); - if (! $schema->tablesExist([$table])) { - return $this->components->warn("Table [{$table}] doesn't exist."); + $table = Arr::first($tables, fn ($table) => $table['name'] === $tableName); + + if (! $table) { + $this->components->warn("Table [{$table}] doesn't exist."); + + return 1; } - $table = $schema->introspectTable($table); + $tableName = $this->withoutTablePrefix($connection, $table['name']); - $columns = $this->columns($table); - $indexes = $this->indexes($table); - $foreignKeys = $this->foreignKeys($table); + $columns = $this->columns($schema, $tableName); + $indexes = $this->indexes($schema, $tableName); + $foreignKeys = $this->foreignKeys($schema, $tableName); $data = [ 'table' => [ - 'name' => $table->getName(), - 'columns' => $columns->count(), - 'size' => $this->getTableSize($connection, $table->getName()), + 'name' => $table['name'], + 'columns' => count($columns), + 'size' => $table['size'], ], 'columns' => $columns, 'indexes' => $indexes, @@ -83,46 +79,48 @@ public function handle(ConnectionResolverInterface $connections) /** * Get the information regarding the table's columns. * - * @param \Doctrine\DBAL\Schema\Table $table + * @param \Illuminate\Database\Schema\Builder $schema + * @param string $table * @return \Illuminate\Support\Collection */ - protected function columns(Table $table) + protected function columns(Builder $schema, string $table) { - return collect($table->getColumns())->map(fn (Column $column) => [ - 'column' => $column->getName(), + return collect($schema->getColumns($table))->map(fn ($column) => [ + 'column' => $column['name'], 'attributes' => $this->getAttributesForColumn($column), - 'default' => $column->getDefault(), - 'type' => $column->getType()->getName(), + 'default' => $column['default'], + 'type' => $column['type'], ]); } /** * Get the attributes for a table column. * - * @param \Doctrine\DBAL\Schema\Column $column + * @param array $column * @return \Illuminate\Support\Collection */ - protected function getAttributesForColumn(Column $column) + protected function getAttributesForColumn($column) { return collect([ - $column->getAutoincrement() ? 'autoincrement' : null, - 'type' => $column->getType()->getName(), - $column->getUnsigned() ? 'unsigned' : null, - ! $column->getNotNull() ? 'nullable' : null, + $column['type_name'], + $column['auto_increment'] ? 'autoincrement' : null, + $column['nullable'] ? 'nullable' : null, + $column['collation'], ])->filter(); } /** * Get the information regarding the table's indexes. * - * @param \Doctrine\DBAL\Schema\Table $table + * @param \Illuminate\Database\Schema\Builder $schema + * @param string $table * @return \Illuminate\Support\Collection */ - protected function indexes(Table $table) + protected function indexes(Builder $schema, string $table) { - return collect($table->getIndexes())->map(fn (Index $index) => [ - 'name' => $index->getName(), - 'columns' => collect($index->getColumns()), + return collect($schema->getIndexes($table))->map(fn ($index) => [ + 'name' => $index['name'], + 'columns' => collect($index['columns']), 'attributes' => $this->getAttributesForIndex($index), ]); } @@ -130,34 +128,36 @@ protected function indexes(Table $table) /** * Get the attributes for a table index. * - * @param \Doctrine\DBAL\Schema\Index $index + * @param array $index * @return \Illuminate\Support\Collection */ - protected function getAttributesForIndex(Index $index) + protected function getAttributesForIndex($index) { return collect([ - 'compound' => count($index->getColumns()) > 1, - 'unique' => $index->isUnique(), - 'primary' => $index->isPrimary(), - ])->filter()->keys()->map(fn ($attribute) => Str::lower($attribute)); + $index['type'], + count($index['columns']) > 1 ? 'compound' : null, + $index['unique'] && ! $index['primary'] ? 'unique' : null, + $index['primary'] ? 'primary' : null, + ])->filter(); } /** * Get the information regarding the table's foreign keys. * - * @param \Doctrine\DBAL\Schema\Table $table + * @param \Illuminate\Database\Schema\Builder $schema + * @param string $table * @return \Illuminate\Support\Collection */ - protected function foreignKeys(Table $table) + protected function foreignKeys(Builder $schema, string $table) { - return collect($table->getForeignKeys())->map(fn (ForeignKeyConstraint $foreignKey) => [ - 'name' => $foreignKey->getName(), - 'local_table' => $table->getName(), - 'local_columns' => collect($foreignKey->getLocalColumns()), - 'foreign_table' => $foreignKey->getForeignTableName(), - 'foreign_columns' => collect($foreignKey->getForeignColumns()), - 'on_update' => Str::lower(rescue(fn () => $foreignKey->getOption('onUpdate'), 'N/A')), - 'on_delete' => Str::lower(rescue(fn () => $foreignKey->getOption('onDelete'), 'N/A')), + return collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => [ + 'name' => $foreignKey['name'], + 'columns' => collect($foreignKey['columns']), + 'foreign_schema' => $foreignKey['foreign_schema'], + 'foreign_table' => $foreignKey['foreign_table'], + 'foreign_columns' => collect($foreignKey['foreign_columns']), + 'on_update' => $foreignKey['on_update'], + 'on_delete' => $foreignKey['on_delete'], ]); } @@ -201,7 +201,7 @@ protected function displayForCli(array $data) $this->components->twoColumnDetail('Columns', $table['columns']); if ($size = $table['size']) { - $this->components->twoColumnDetail('Size', number_format($size / 1024 / 1024, 2).'MiB'); + $this->components->twoColumnDetail('Size', Number::fileSize($size, 2)); } $this->newLine(); @@ -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'] ); }); @@ -237,7 +237,7 @@ protected function displayForCli(array $data) $foreignKeys->each(function ($foreignKey) { $this->components->twoColumnDetail( - $foreignKey['name'].' '.$foreignKey['local_columns']->implode(', ').' references '.$foreignKey['foreign_columns']->implode(', ').' on '.$foreignKey['foreign_table'].'', + $foreignKey['name'].' '.$foreignKey['columns']->implode(', ').' references '.$foreignKey['foreign_columns']->implode(', ').' on '.$foreignKey['foreign_table'].'', $foreignKey['on_update'].' / '.$foreignKey['on_delete'], ); }); diff --git a/src/Illuminate/Database/DBAL/TimestampType.php b/src/Illuminate/Database/DBAL/TimestampType.php deleted file mode 100644 index aee4a2a0130b..000000000000 --- a/src/Illuminate/Database/DBAL/TimestampType.php +++ /dev/null @@ -1,94 +0,0 @@ - $this->getMySqlPlatformSQLDeclaration($column), - PostgreSQLPlatform::class => $this->getPostgresPlatformSQLDeclaration($column), - SQLServerPlatform::class => $this->getSqlServerPlatformSQLDeclaration($column), - SQLitePlatform::class => 'DATETIME', - default => throw NotSupported::new('TIMESTAMP'), - }; - } - - /** - * Get the SQL declaration for MySQL. - * - * @param array $column - * @return string - */ - protected function getMySqlPlatformSQLDeclaration(array $column): string - { - $columnType = 'TIMESTAMP'; - - if ($column['precision']) { - $columnType = 'TIMESTAMP('.min((int) $column['precision'], 6).')'; - } - - $notNull = $column['notnull'] ?? false; - - if (! $notNull) { - return $columnType.' NULL'; - } - - return $columnType; - } - - /** - * Get the SQL declaration for PostgreSQL. - * - * @param array $column - * @return string - */ - protected function getPostgresPlatformSQLDeclaration(array $column): string - { - return 'TIMESTAMP('.min((int) $column['precision'], 6).')'; - } - - /** - * Get the SQL declaration for SQL Server. - * - * @param array $column - * @return string - */ - protected function getSqlServerPlatformSQLDeclaration(array $column): string - { - return $column['precision'] ?? false - ? 'DATETIME2('.min((int) $column['precision'], 7).')' - : 'DATETIME'; - } - - /** - * {@inheritdoc} - * - * @return string - */ - public function getName() - { - return 'timestamp'; - } -} diff --git a/src/Illuminate/Database/DatabaseManager.php b/src/Illuminate/Database/DatabaseManager.php index 84602917ea1a..9239fca06f90 100755 --- a/src/Illuminate/Database/DatabaseManager.php +++ b/src/Illuminate/Database/DatabaseManager.php @@ -2,7 +2,6 @@ namespace Illuminate\Database; -use Doctrine\DBAL\Types\Type; use Illuminate\Database\Connectors\ConnectionFactory; use Illuminate\Database\Events\ConnectionEstablished; use Illuminate\Support\Arr; @@ -57,13 +56,6 @@ class DatabaseManager implements ConnectionResolverInterface */ protected $reconnector; - /** - * The custom Doctrine column types. - * - * @var array - */ - protected $doctrineTypes = []; - /** * Create a new database manager instance. * @@ -101,16 +93,39 @@ public function connection($name = null) $this->makeConnection($database), $type ); - if ($this->app->bound('events')) { - $this->app['events']->dispatch( - new ConnectionEstablished($this->connections[$name]) - ); - } + $this->dispatchConnectionEstablishedEvent($this->connections[$name]); } return $this->connections[$name]; } + /** + * Get a database connection instance from the given configuration. + * + * @param string $name + * @param array $config + * @param bool $force + * @return \Illuminate\Database\ConnectionInterface + */ + public function connectUsing(string $name, array $config, bool $force = false) + { + if ($force) { + $this->purge($name); + } + + if (isset($this->connections[$name])) { + throw new RuntimeException("Cannot establish connection [$name] because another connection with that name already exists."); + } + + $connection = $this->configure( + $this->factory->make($config, $name), null + ); + + $this->dispatchConnectionEstablishedEvent($connection); + + return tap($connection, fn ($connection) => $this->connections[$name] = $connection); + } + /** * Parse the connection into an array of the name and read / write type. * @@ -204,70 +219,42 @@ protected function configure(Connection $connection, $type) // the connection, which will allow us to reconnect from the connections. $connection->setReconnector($this->reconnector); - $this->registerConfiguredDoctrineTypes($connection); - - return $connection; - } - - /** - * Prepare the read / write mode for database connection instance. - * - * @param \Illuminate\Database\Connection $connection - * @param string|null $type - * @return \Illuminate\Database\Connection - */ - protected function setPdoForType(Connection $connection, $type = null) - { - if ($type === 'read') { - $connection->setPdo($connection->getReadPdo()); - } elseif ($type === 'write') { - $connection->setReadPdo($connection->getPdo()); - } - return $connection; } /** - * Register custom Doctrine types with the connection. + * Dispatch the ConnectionEstablished event if the event dispatcher is available. * * @param \Illuminate\Database\Connection $connection * @return void */ - protected function registerConfiguredDoctrineTypes(Connection $connection): void + protected function dispatchConnectionEstablishedEvent(Connection $connection) { - foreach ($this->app['config']->get('database.dbal.types', []) as $name => $class) { - $this->registerDoctrineType($class, $name, $name); + if (! $this->app->bound('events')) { + return; } - foreach ($this->doctrineTypes as $name => [$type, $class]) { - $connection->registerDoctrineType($class, $name, $type); - } + $this->app['events']->dispatch( + new ConnectionEstablished($connection) + ); } /** - * Register a custom Doctrine type. - * - * @param string $class - * @param string $name - * @param string $type - * @return void + * Prepare the read / write mode for database connection instance. * - * @throws \Doctrine\DBAL\Exception - * @throws \RuntimeException + * @param \Illuminate\Database\Connection $connection + * @param string|null $type + * @return \Illuminate\Database\Connection */ - public function registerDoctrineType(string $class, string $name, string $type): void + protected function setPdoForType(Connection $connection, $type = null) { - if (! class_exists('Doctrine\DBAL\Connection')) { - throw new RuntimeException( - 'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).' - ); - } - - if (! Type::hasType($name)) { - Type::addType($name, $class); + if ($type === 'read') { + $connection->setPdo($connection->getReadPdo()); + } elseif ($type === 'write') { + $connection->setReadPdo($connection->getPdo()); } - $this->doctrineTypes[$name] = [$type, $class]; + return $connection; } /** @@ -374,13 +361,13 @@ public function setDefaultConnection($name) } /** - * Get all of the support drivers. + * Get all of the supported drivers. * * @return string[] */ public function supportedDrivers() { - return ['mysql', 'pgsql', 'sqlite', 'sqlsrv']; + return ['mysql', 'mariadb', 'pgsql', 'sqlite', 'sqlsrv']; } /** diff --git a/src/Illuminate/Database/DatabaseTransactionsManager.php b/src/Illuminate/Database/DatabaseTransactionsManager.php index c730dc503ac2..ee2889a2d18a 100755 --- a/src/Illuminate/Database/DatabaseTransactionsManager.php +++ b/src/Illuminate/Database/DatabaseTransactionsManager.php @@ -83,7 +83,8 @@ public function commit($connection, $levelBeingCommitted, $newTransactionLevel) // shouldn't be any pending transactions, but going to clear them here anyways just // in case. This method could be refactored to receive a level in the future too. $this->pendingTransactions = $this->pendingTransactions->reject( - fn ($transaction) => $transaction->connection === $connection + fn ($transaction) => $transaction->connection === $connection && + $transaction->level >= $levelBeingCommitted )->values(); [$forThisConnection, $forOtherConnections] = $this->committedTransactions->partition( diff --git a/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php b/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php new file mode 100644 index 000000000000..600174146f94 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php @@ -0,0 +1,19 @@ +contains('or')) { + if ($whereBooleans->contains(fn ($logicalOperator) => str_contains($logicalOperator, 'or'))) { $query->wheres[] = $this->createNestedWhere( - $whereSlice, $whereBooleans->first() + $whereSlice, str_replace(' not', '', $whereBooleans->first()) ); } else { $query->wheres = array_merge($query->wheres, $whereSlice); diff --git a/src/Illuminate/Database/Eloquent/Casts/AsArrayObject.php b/src/Illuminate/Database/Eloquent/Casts/AsArrayObject.php index 2ee6b56e0901..5ee80d0bb4f0 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsArrayObject.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsArrayObject.php @@ -25,7 +25,7 @@ public function get($model, $key, $value, $attributes) $data = Json::decode($attributes[$key]); - return is_array($data) ? new ArrayObject($data) : null; + return is_array($data) ? new ArrayObject($data, ArrayObject::ARRAY_AS_PROPS) : null; } public function set($model, $key, $value, $attributes) diff --git a/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php index b7e0d7dea8c0..f7d4c9ff538d 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php @@ -7,7 +7,7 @@ trait GuardsAttributes /** * The attributes that are mass assignable. * - * @var array + * @var array */ protected $fillable = []; diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index a95fb62fb73a..10187f50d701 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1328,7 +1328,7 @@ public function fromJson($value, $asObject = false) */ public function fromEncryptedString($value) { - return (static::$encrypter ?? Crypt::getFacadeRoot())->decrypt($value, false); + return static::currentEncrypter()->decrypt($value, false); } /** @@ -1340,7 +1340,7 @@ public function fromEncryptedString($value) */ protected function castAttributeAsEncryptedString($key, $value) { - return (static::$encrypter ?? Crypt::getFacadeRoot())->encrypt($value, false); + return static::currentEncrypter()->encrypt($value, false); } /** @@ -1354,6 +1354,16 @@ public static function encryptUsing($encrypter) static::$encrypter = $encrypter; } + /** + * Get the current encrypter being used by the model. + * + * @return \Illuminate\Contracts\Encryption\Encrypter + */ + protected static function currentEncrypter() + { + return static::$encrypter ?? Crypt::getFacadeRoot(); + } + /** * Cast the given attribute to a hashed string. * @@ -2145,6 +2155,8 @@ public function originalIsEquivalent($key) } return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4; + } elseif ($this->isEncryptedCastable($key) && ! empty(static::currentEncrypter()->getPreviousKeys())) { + return false; } elseif ($this->hasCast($key, static::$primitiveCastTypes)) { return $this->castAttribute($key, $attribute) === $this->castAttribute($key, $original); @@ -2153,7 +2165,11 @@ public function originalIsEquivalent($key) } elseif ($this->isClassCastable($key) && Str::startsWith($this->getCasts()[$key], [AsEnumArrayObject::class, AsEnumCollection::class])) { return $this->fromJson($attribute) === $this->fromJson($original); } elseif ($this->isClassCastable($key) && $original !== null && Str::startsWith($this->getCasts()[$key], [AsEncryptedArrayObject::class, AsEncryptedCollection::class])) { - return $this->fromEncryptedString($attribute) === $this->fromEncryptedString($original); + if (empty(static::currentEncrypter()->getPreviousKeys())) { + return $this->fromEncryptedString($attribute) === $this->fromEncryptedString($original); + } + + return false; } return is_numeric($attribute) && is_numeric($original) @@ -2182,6 +2198,13 @@ protected function transformModelValue($key, $value) // an appropriate native PHP type dependent upon the associated value // given with the key in the pair. Dayle made this comment line up. if ($this->hasCast($key)) { + if (static::preventsAccessingMissingAttributes() && + ! array_key_exists($key, $this->attributes) && + ($this->isEnumCastable($key) || + in_array($this->getCastType($key), static::$primitiveCastTypes))) { + $this->throwMissingAttributeExceptionIfApplicable($key); + } + return $this->castAttribute($key, $value); } diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php b/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php index 37bc063aaa85..0730dcb10971 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php @@ -3,9 +3,11 @@ namespace Illuminate\Database\Eloquent\Concerns; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Events\NullDispatcher; use Illuminate\Support\Arr; use InvalidArgumentException; +use ReflectionClass; trait HasEvents { @@ -27,6 +29,31 @@ trait HasEvents */ protected $observables = []; + /** + * Boot the has event trait for a model. + * + * @return void + */ + public static function bootHasEvents() + { + static::observe(static::resolveObserveAttributes()); + } + + /** + * Resolve the observe class names from the attributes. + * + * @return array + */ + public static function resolveObserveAttributes() + { + $reflectionClass = new ReflectionClass(static::class); + + return collect($reflectionClass->getAttributes(ObservedBy::class)) + ->map(fn ($attribute) => $attribute->getArguments()) + ->flatten() + ->all(); + } + /** * Register observers with the model. * diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php b/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php index 5d7047953115..0913d94b372a 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php @@ -3,12 +3,39 @@ namespace Illuminate\Database\Eloquent\Concerns; use Closure; +use Illuminate\Database\Eloquent\Attributes\ScopedBy; use Illuminate\Database\Eloquent\Scope; use Illuminate\Support\Arr; use InvalidArgumentException; +use ReflectionClass; trait HasGlobalScopes { + /** + * Boot the has global scopes trait for a model. + * + * @return void + */ + public static function bootHasGlobalScopes() + { + static::addGlobalScopes(static::resolveGlobalScopeAttributes()); + } + + /** + * Resolve the global scope class names from the attributes. + * + * @return array + */ + public static function resolveGlobalScopeAttributes() + { + $reflectionClass = new ReflectionClass(static::class); + + return collect($reflectionClass->getAttributes(ScopedBy::class)) + ->map(fn ($attribute) => $attribute->getArguments()) + ->flatten() + ->all(); + } + /** * Register a new global scope on the model. * @@ -26,9 +53,28 @@ public static function addGlobalScope($scope, $implementation = null) return static::$globalScopes[static::class][spl_object_hash($scope)] = $scope; } elseif ($scope instanceof Scope) { return static::$globalScopes[static::class][get_class($scope)] = $scope; + } elseif (is_string($scope) && class_exists($scope) && is_subclass_of($scope, Scope::class)) { + return static::$globalScopes[static::class][$scope] = new $scope; } - throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope.'); + throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope or be a class name of a class extending '.Scope::class); + } + + /** + * Register multiple global scopes on the model. + * + * @param array $scopes + * @return void + */ + public static function addGlobalScopes(array $scopes) + { + foreach ($scopes as $key => $scope) { + if (is_string($key)) { + static::addGlobalScope($key, $scope); + } else { + static::addGlobalScope($scope); + } + } } /** diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index 154717fa4d81..de43f9839824 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -599,7 +599,7 @@ public function orWhereBelongsTo($related, $relationshipName = null) * Add subselect queries to include an aggregate value for a relationship. * * @param mixed $relations - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $function * @return $this */ @@ -630,15 +630,19 @@ public function withAggregate($relations, $column, $function = null) $relation = $this->getRelationWithoutConstraints($name); if ($function) { - $hashedColumn = $this->getRelationHashedColumn($column, $relation); + if ($this->getQuery()->getGrammar()->isExpression($column)) { + $aggregateColumn = $this->getQuery()->getGrammar()->getValue($column); + } else { + $hashedColumn = $this->getRelationHashedColumn($column, $relation); - $wrappedColumn = $this->getQuery()->getGrammar()->wrap( - $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn) - ); + $aggregateColumn = $this->getQuery()->getGrammar()->wrap( + $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn) + ); + } - $expression = $function === 'exists' ? $wrappedColumn : sprintf('%s(%s)', $function, $wrappedColumn); + $expression = $function === 'exists' ? $aggregateColumn : sprintf('%s(%s)', $function, $aggregateColumn); } else { - $expression = $column; + $expression = $this->getQuery()->getGrammar()->getValue($column); } // Here, we will grab the relationship sub-query and prepare to add it to the main query @@ -667,7 +671,7 @@ public function withAggregate($relations, $column, $function = null) // the query builder. Then, we will return the builder instance back to the developer // for further constraint chaining that needs to take place on the query as needed. $alias ??= Str::snake( - preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function $column") + preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function {$this->getQuery()->getGrammar()->getValue($column)}") ); if ($function === 'exists') { @@ -719,7 +723,7 @@ public function withCount($relations) * Add subselect queries to include the max of the relation's column. * * @param string|array $relation - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function withMax($relation, $column) @@ -731,7 +735,7 @@ public function withMax($relation, $column) * Add subselect queries to include the min of the relation's column. * * @param string|array $relation - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function withMin($relation, $column) @@ -743,7 +747,7 @@ public function withMin($relation, $column) * Add subselect queries to include the sum of the relation's column. * * @param string|array $relation - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function withSum($relation, $column) @@ -755,7 +759,7 @@ public function withSum($relation, $column) * Add subselect queries to include the average of the relation's column. * * @param string|array $relation - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function withAvg($relation, $column) diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 37c698f3d80f..9fe0d301918b 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot; use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary; use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable; +use Illuminate\Database\Query\Grammars\MySqlGrammar; use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Support\Str; use InvalidArgumentException; @@ -1179,6 +1180,10 @@ protected function guessInverseRelation() */ public function touch() { + if ($this->related->isIgnoringTouch()) { + return; + } + $columns = [ $this->related->getUpdatedAtColumn() => $this->related->freshTimestampString(), ]; @@ -1347,6 +1352,42 @@ public function getRelationExistenceQueryForSelfJoin(Builder $query, Builder $pa return parent::getRelationExistenceQuery($query, $parentQuery, $columns); } + /** + * Alias to set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function take($value) + { + return $this->limit($value); + } + + /** + * Set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function limit($value) + { + if ($this->parent->exists) { + $this->query->limit($value); + } else { + $column = $this->getExistenceCompareKey(); + + $grammar = $this->query->getQuery()->getGrammar(); + + if ($grammar instanceof MySqlGrammar && $grammar->useLegacyGroupLimit($this->query->getQuery())) { + $column = 'pivot_'.last(explode('.', $column)); + } + + $this->query->groupLimit($value, $column); + } + + return $this; + } + /** * Get the key for comparing against the parent key in "has" query. * diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index b0b4b1fdebe1..55f9aacd1e2c 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Database\Query\Grammars\MySqlGrammar; use Illuminate\Database\UniqueConstraintViolationException; class HasManyThrough extends Relation @@ -762,6 +763,42 @@ public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, ); } + /** + * Alias to set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function take($value) + { + return $this->limit($value); + } + + /** + * Set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function limit($value) + { + if ($this->farParent->exists) { + $this->query->limit($value); + } else { + $column = $this->getQualifiedFirstKeyName(); + + $grammar = $this->query->getQuery()->getGrammar(); + + if ($grammar instanceof MySqlGrammar && $grammar->useLegacyGroupLimit($this->query->getQuery())) { + $column = 'laravel_through_key'; + } + + $this->query->groupLimit($value, $column); + } + + return $this; + } + /** * Get the qualified foreign key on the related model. * diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index af263baf854f..e1d295d86be4 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -453,6 +453,34 @@ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder ); } + /** + * Alias to set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function take($value) + { + return $this->limit($value); + } + + /** + * Set the "limit" value of the query. + * + * @param int $value + * @return $this + */ + public function limit($value) + { + if ($this->parent->exists) { + $this->query->limit($value); + } else { + $this->query->groupLimit($value, $this->getExistenceCompareKey()); + } + + return $this; + } + /** * Get the key for comparing against the parent key in "has" query. * diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php index 5ca8b48bed02..39c7852f2888 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php @@ -68,6 +68,8 @@ public function delete() $query->where($this->morphType, $this->morphClass); return tap($query->delete(), function () { + $this->exists = false; + $this->fireModelEvent('deleted', false); }); } diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php index 87b8e7816f9f..8cf113bd0f34 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php @@ -189,6 +189,16 @@ public function getMorphType() return $this->morphType; } + /** + * Get the fully qualified morph type for the relation. + * + * @return string + */ + public function getQualifiedMorphTypeName() + { + return $this->qualifyPivotColumn($this->morphType); + } + /** * Get the class name of the parent model. * diff --git a/src/Illuminate/Database/Eloquent/Relations/Pivot.php b/src/Illuminate/Database/Eloquent/Relations/Pivot.php index a65ecdea6633..6e1d3f27897e 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Pivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/Pivot.php @@ -19,7 +19,7 @@ class Pivot extends Model /** * The attributes that aren't mass assignable. * - * @var array + * @var array|bool */ protected $guarded = []; } diff --git a/src/Illuminate/Database/Grammar.php b/src/Illuminate/Database/Grammar.php index 9ce0ec352595..79da1724ccb4 100755 --- a/src/Illuminate/Database/Grammar.php +++ b/src/Illuminate/Database/Grammar.php @@ -43,21 +43,38 @@ public function wrapArray(array $values) */ public function wrapTable($table) { - if (! $this->isExpression($table)) { - return $this->wrap($this->tablePrefix.$table, true); + if ($this->isExpression($table)) { + return $this->getValue($table); } - return $this->getValue($table); + // If the table being wrapped has an alias we'll need to separate the pieces + // so we can prefix the table and then wrap each of the segments on their + // own and then join these both back together using the "as" connector. + if (stripos($table, ' as ') !== false) { + return $this->wrapAliasedTable($table); + } + + // If the table being wrapped has a custom schema name specified, we need to + // prefix the last segment as the table name then wrap each segment alone + // and eventually join them both back together using the dot connector. + if (str_contains($table, '.')) { + $table = substr_replace($table, '.'.$this->tablePrefix, strrpos($table, '.'), 1); + + return collect(explode('.', $table)) + ->map($this->wrapValue(...)) + ->implode('.'); + } + + return $this->wrapValue($this->tablePrefix.$table); } /** * Wrap a value in keyword identifiers. * * @param \Illuminate\Contracts\Database\Query\Expression|string $value - * @param bool $prefixAlias * @return string */ - public function wrap($value, $prefixAlias = false) + public function wrap($value) { if ($this->isExpression($value)) { return $this->getValue($value); @@ -67,7 +84,7 @@ public function wrap($value, $prefixAlias = false) // the pieces so we can wrap each of the segments of the expression on its // own, and then join these both back together using the "as" connector. if (stripos($value, ' as ') !== false) { - return $this->wrapAliasedValue($value, $prefixAlias); + return $this->wrapAliasedValue($value); } // If the given value is a JSON selector we will wrap it differently than a @@ -84,23 +101,28 @@ public function wrap($value, $prefixAlias = false) * Wrap a value that has an alias. * * @param string $value - * @param bool $prefixAlias * @return string */ - protected function wrapAliasedValue($value, $prefixAlias = false) + protected function wrapAliasedValue($value) { $segments = preg_split('/\s+as\s+/i', $value); - // If we are wrapping a table we need to prefix the alias with the table prefix - // as well in order to generate proper syntax. If this is a column of course - // no prefix is necessary. The condition will be true when from wrapTable. - if ($prefixAlias) { - $segments[1] = $this->tablePrefix.$segments[1]; - } - return $this->wrap($segments[0]).' as '.$this->wrapValue($segments[1]); } + /** + * Wrap a table that has an alias. + * + * @param string $value + * @return string + */ + protected function wrapAliasedTable($value) + { + $segments = preg_split('/\s+as\s+/i', $value); + + return $this->wrapTable($segments[0]).' as '.$this->wrapValue($this->tablePrefix.$segments[1]); + } + /** * Wrap the given value segments. * diff --git a/src/Illuminate/Database/MariaDbConnection.php b/src/Illuminate/Database/MariaDbConnection.php new file mode 100755 index 000000000000..58e124d50c10 --- /dev/null +++ b/src/Illuminate/Database/MariaDbConnection.php @@ -0,0 +1,94 @@ +setConnection($this); + + return $this->withTablePrefix($grammar); + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\MariaDbBuilder + */ + public function getSchemaBuilder() + { + if (is_null($this->schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new MariaDbBuilder($this); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\MariaDbGrammar + */ + protected function getDefaultSchemaGrammar() + { + ($grammar = new SchemaGrammar)->setConnection($this); + + return $this->withTablePrefix($grammar); + } + + /** + * Get the schema state for the connection. + * + * @param \Illuminate\Filesystem\Filesystem|null $files + * @param callable|null $processFactory + * @return \Illuminate\Database\Schema\MariaDbSchemaState + */ + public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + { + return new MariaDbSchemaState($this, $files, $processFactory); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\MariaDbProcessor + */ + protected function getDefaultPostProcessor() + { + return new MariaDbProcessor; + } +} diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php index 4f6ec5247a04..e650959419f9 100755 --- a/src/Illuminate/Database/Migrations/Migrator.php +++ b/src/Illuminate/Database/Migrations/Migrator.php @@ -2,9 +2,7 @@ namespace Illuminate\Database\Migrations; -use Doctrine\DBAL\Schema\SchemaException; use Illuminate\Console\View\Components\BulletList; -use Illuminate\Console\View\Components\Error; use Illuminate\Console\View\Components\Info; use Illuminate\Console\View\Components\Task; use Illuminate\Console\View\Components\TwoColumnDetail; @@ -428,28 +426,19 @@ protected function runMigration($migration, $method) */ protected function pretendToRun($migration, $method) { - try { - $name = get_class($migration); - - $reflectionClass = new ReflectionClass($migration); + $name = get_class($migration); - if ($reflectionClass->isAnonymous()) { - $name = $this->getMigrationName($reflectionClass->getFileName()); - } + $reflectionClass = new ReflectionClass($migration); - $this->write(TwoColumnDetail::class, $name); + if ($reflectionClass->isAnonymous()) { + $name = $this->getMigrationName($reflectionClass->getFileName()); + } - $this->write(BulletList::class, collect($this->getQueries($migration, $method))->map(function ($query) { - return $query['query']; - })); - } catch (SchemaException) { - $name = get_class($migration); + $this->write(TwoColumnDetail::class, $name); - $this->write(Error::class, sprintf( - '[%s] failed to dump queries. This may be due to changing database columns using Doctrine, which is not supported while pretending to run migrations.', - $name, - )); - } + $this->write(BulletList::class, collect($this->getQueries($migration, $method))->map(function ($query) { + return $query['query']; + })); } /** diff --git a/src/Illuminate/Database/MySqlConnection.php b/src/Illuminate/Database/MySqlConnection.php index 460a4fd375c1..00d212e9481d 100755 --- a/src/Illuminate/Database/MySqlConnection.php +++ b/src/Illuminate/Database/MySqlConnection.php @@ -3,13 +3,13 @@ namespace Illuminate\Database; use Exception; -use Illuminate\Database\PDO\MySqlDriver; use Illuminate\Database\Query\Grammars\MySqlGrammar as QueryGrammar; use Illuminate\Database\Query\Processors\MySqlProcessor; use Illuminate\Database\Schema\Grammars\MySqlGrammar as SchemaGrammar; use Illuminate\Database\Schema\MySqlBuilder; use Illuminate\Database\Schema\MySqlSchemaState; use Illuminate\Filesystem\Filesystem; +use Illuminate\Support\Str; use PDO; class MySqlConnection extends Connection @@ -48,6 +48,18 @@ public function isMaria() return str_contains($this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), 'MariaDB'); } + /** + * Get the server version for the connection. + * + * @return string + */ + public function getServerVersion(): string + { + return str_contains($version = parent::getServerVersion(), 'MariaDB') + ? Str::between($version, '5.5.5-', '-MariaDB') + : $version; + } + /** * Get the default query grammar instance. * @@ -107,14 +119,4 @@ protected function getDefaultPostProcessor() { return new MySqlProcessor; } - - /** - * Get the Doctrine DBAL driver. - * - * @return \Illuminate\Database\PDO\MySqlDriver - */ - protected function getDoctrineDriver() - { - return new MySqlDriver; - } } diff --git a/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php b/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php deleted file mode 100644 index a2354182bc2c..000000000000 --- a/src/Illuminate/Database/PDO/Concerns/ConnectsToDatabase.php +++ /dev/null @@ -1,28 +0,0 @@ -connection = $connection; - } - - /** - * Execute an SQL statement. - * - * @param string $statement - * @return int - */ - public function exec(string $statement): int - { - try { - $result = $this->connection->exec($statement); - - \assert($result !== false); - - return $result; - } catch (PDOException $exception) { - throw Exception::new($exception); - } - } - - /** - * Prepare a new SQL statement. - * - * @param string $sql - * @return \Doctrine\DBAL\Driver\Statement - * - * @throws \Doctrine\DBAL\Driver\PDO\Exception - */ - public function prepare(string $sql): StatementInterface - { - try { - return $this->createStatement( - $this->connection->prepare($sql) - ); - } catch (PDOException $exception) { - throw Exception::new($exception); - } - } - - /** - * Execute a new query against the connection. - * - * @param string $sql - * @return \Doctrine\DBAL\Driver\Result - */ - public function query(string $sql): ResultInterface - { - try { - $stmt = $this->connection->query($sql); - - \assert($stmt instanceof PDOStatement); - - return new Result($stmt); - } catch (PDOException $exception) { - throw Exception::new($exception); - } - } - - /** - * Get the last insert ID. - * - * @param string|null $name - * @return string|int - * - * @throws \Doctrine\DBAL\Driver\PDO\Exception - */ - public function lastInsertId($name = null): string|int - { - try { - if ($name === null) { - return $this->connection->lastInsertId(); - } - - return $this->connection->lastInsertId($name); - } catch (PDOException $exception) { - throw Exception::new($exception); - } - } - - /** - * Create a new statement instance. - * - * @param \PDOStatement $stmt - * @return \Doctrine\DBAL\Driver\PDO\Statement - */ - protected function createStatement(PDOStatement $stmt): Statement - { - return new Statement($stmt); - } - - /** - * Begin a new database transaction. - * - * @return void - */ - public function beginTransaction(): void - { - $this->connection->beginTransaction(); - } - - /** - * Commit a database transaction. - * - * @return void - */ - public function commit(): void - { - $this->connection->commit(); - } - - /** - * Rollback a database transaction. - * - * @return void - */ - public function rollBack(): void - { - $this->connection->rollBack(); - } - - /** - * Wrap quotes around the given input. - * - * @param string $input - * @param string $type - * @return string - */ - public function quote($input, $type = ParameterType::STRING): string - { - return $this->connection->quote($input, $type); - } - - /** - * Get the server version for the connection. - * - * @return string - */ - public function getServerVersion(): string - { - return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); - } - - /** - * Get the native PDO connection. - * - * @return \PDO - */ - public function getNativeConnection(): PDO - { - return $this->connection; - } -} diff --git a/src/Illuminate/Database/PDO/MySqlDriver.php b/src/Illuminate/Database/PDO/MySqlDriver.php deleted file mode 100644 index 54ac37536189..000000000000 --- a/src/Illuminate/Database/PDO/MySqlDriver.php +++ /dev/null @@ -1,19 +0,0 @@ -connection = $connection; - } - - /** - * Prepare a new SQL statement. - * - * @param string $sql - * @return \Doctrine\DBAL\Driver\Statement - */ - public function prepare(string $sql): StatementInterface - { - return new Statement( - $this->connection->prepare($sql) - ); - } - - /** - * Execute a new query against the connection. - * - * @param string $sql - * @return \Doctrine\DBAL\Driver\Result - */ - public function query(string $sql): Result - { - return $this->connection->query($sql); - } - - /** - * Execute an SQL statement. - * - * @param string $statement - * @return int - */ - public function exec(string $statement): int - { - return $this->connection->exec($statement); - } - - /** - * Get the last insert ID. - * - * @param string|null $name - * @return string|int - */ - public function lastInsertId($name = null): string|int - { - if ($name === null) { - return $this->connection->lastInsertId($name); - } - - return $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?') - ->execute([$name]) - ->fetchOne(); - } - - /** - * Begin a new database transaction. - * - * @return void - */ - public function beginTransaction(): void - { - $this->connection->beginTransaction(); - } - - /** - * Commit a database transaction. - * - * @return void - */ - public function commit(): void - { - $this->connection->commit(); - } - - /** - * Rollback a database transaction. - * - * @return void - */ - public function rollBack(): void - { - $this->connection->rollBack(); - } - - /** - * Wrap quotes around the given input. - * - * @param string $value - * @param int $type - * @return string - */ - public function quote($value, $type = ParameterType::STRING): string - { - $val = $this->connection->quote($value, $type); - - // Fix for a driver version terminating all values with null byte... - if (\is_string($val) && str_contains($val, "\0")) { - $val = \substr($val, 0, -1); - } - - return $val; - } - - /** - * Get the server version for the connection. - * - * @return string - */ - public function getServerVersion(): string - { - return $this->connection->getServerVersion(); - } - - /** - * Get the native PDO connection. - * - * @return \PDO - */ - public function getNativeConnection(): PDO - { - return $this->connection->getWrappedConnection(); - } -} diff --git a/src/Illuminate/Database/PDO/SqlServerDriver.php b/src/Illuminate/Database/PDO/SqlServerDriver.php deleted file mode 100644 index ac7b8a1aedef..000000000000 --- a/src/Illuminate/Database/PDO/SqlServerDriver.php +++ /dev/null @@ -1,30 +0,0 @@ -join(new Expression($expression), $first, $operator, $second, $type, $where); } + /** + * Add a lateral join clause to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param string $as + * @param string $type + * @return $this + */ + public function joinLateral($query, string $as, string $type = 'inner') + { + [$query, $bindings] = $this->createSub($query); + + $expression = '('.$query.') as '.$this->grammar->wrapTable($as); + + $this->addBinding($bindings, 'join'); + + $this->joins[] = $this->newJoinLateralClause($this, $type, new Expression($expression)); + + return $this; + } + + /** + * Add a lateral left join to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param string $as + * @return $this + */ + public function leftJoinLateral($query, string $as) + { + return $this->joinLateral($query, $as, 'left'); + } + /** * Add a left join to the query. * @@ -729,6 +769,19 @@ protected function newJoinClause(self $parentQuery, $type, $table) return new JoinClause($parentQuery, $type, $table); } + /** + * Get a new join lateral clause. + * + * @param \Illuminate\Database\Query\Builder $parentQuery + * @param string $type + * @param string $table + * @return \Illuminate\Database\Query\JoinLateralClause + */ + protected function newJoinLateralClause(self $parentQuery, $type, $table) + { + return new JoinLateralClause($parentQuery, $type, $table); + } + /** * Merge an array of where clauses and bindings. * @@ -1383,7 +1436,7 @@ public function orWhereNotNull($column) * Add a "where date" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|null $operator * @param \DateTimeInterface|string|null $value * @param string $boolean * @return $this @@ -1407,7 +1460,7 @@ public function whereDate($column, $operator, $value = null, $boolean = 'and') * Add an "or where date" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|null $operator * @param \DateTimeInterface|string|null $value * @return $this */ @@ -1424,7 +1477,7 @@ public function orWhereDate($column, $operator, $value = null) * Add a "where time" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|null $operator * @param \DateTimeInterface|string|null $value * @param string $boolean * @return $this @@ -1448,7 +1501,7 @@ public function whereTime($column, $operator, $value = null, $boolean = 'and') * Add an "or where time" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|null $operator * @param \DateTimeInterface|string|null $value * @return $this */ @@ -1465,7 +1518,7 @@ public function orWhereTime($column, $operator, $value = null) * Add a "where day" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|int|null $operator * @param \DateTimeInterface|string|int|null $value * @param string $boolean * @return $this @@ -1493,7 +1546,7 @@ public function whereDay($column, $operator, $value = null, $boolean = 'and') * Add an "or where day" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|int|null $operator * @param \DateTimeInterface|string|int|null $value * @return $this */ @@ -1510,7 +1563,7 @@ public function orWhereDay($column, $operator, $value = null) * Add a "where month" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|int|null $operator * @param \DateTimeInterface|string|int|null $value * @param string $boolean * @return $this @@ -1538,7 +1591,7 @@ public function whereMonth($column, $operator, $value = null, $boolean = 'and') * Add an "or where month" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|int|null $operator * @param \DateTimeInterface|string|int|null $value * @return $this */ @@ -1555,7 +1608,7 @@ public function orWhereMonth($column, $operator, $value = null) * Add a "where year" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|int|null $operator * @param \DateTimeInterface|string|int|null $value * @param string $boolean * @return $this @@ -1579,7 +1632,7 @@ public function whereYear($column, $operator, $value = null, $boolean = 'and') * Add an "or where year" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $operator + * @param \DateTimeInterface|string|int|null $operator * @param \DateTimeInterface|string|int|null $value * @return $this */ @@ -2447,6 +2500,22 @@ public function limit($value) return $this; } + /** + * Add a "group limit" clause to the query. + * + * @param int $value + * @param string $column + * @return $this + */ + public function groupLimit($value, $column) + { + if ($value >= 0) { + $this->groupLimit = compact('value', 'column'); + } + + return $this; + } + /** * Set the limit and offset for a given page. * @@ -2740,10 +2809,14 @@ public function soleValue($column) */ public function get($columns = ['*']) { - return collect((clone $this) + $items = collect((clone $this) ->select(is_null($this->columns) ? Arr::wrap($columns) : $this->columns) ->processor->processSelect($this, $this->runSelect()) ); + + return isset($this->groupLimit) + ? $this->withoutGroupLimitKeys($items) + : $items; } /** @@ -2758,6 +2831,32 @@ protected function runSelect() ); } + /** + * Remove the group limit keys from the results in the collection. + * + * @param \Illuminate\Support\Collection $items + * @return \Illuminate\Support\Collection + */ + protected function withoutGroupLimitKeys($items) + { + $keysToRemove = ['laravel_row']; + + if (is_string($this->groupLimit['column'])) { + $column = last(explode('.', $this->groupLimit['column'])); + + $keysToRemove[] = '@laravel_group := '.$this->grammar->wrap($column); + $keysToRemove[] = '@laravel_group := '.$this->grammar->wrap('pivot_'.$column); + } + + $items->each(function ($item) use ($keysToRemove) { + foreach ($keysToRemove as $key) { + unset($item->$key); + } + }); + + return $items; + } + /** * Paginate the given query into a simple paginator. * @@ -3295,6 +3394,25 @@ public function insertUsing(array $columns, $query) ); } + /** + * Insert new records into the table using a subquery while ignoring errors. + * + * @param array $columns + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @return int + */ + public function insertOrIgnoreUsing(array $columns, $query) + { + $this->applyBeforeQueryCallbacks(); + + [$sql, $bindings] = $this->createSub($query); + + return $this->connection->affectingStatement( + $this->grammar->compileInsertOrIgnoreUsing($this, $columns, $sql), + $this->cleanBindings($bindings) + ); + } + /** * Update records in the database. * @@ -3305,10 +3423,20 @@ public function update(array $values) { $this->applyBeforeQueryCallbacks(); - $sql = $this->grammar->compileUpdate($this, $values); + $values = collect($values)->map(function ($value) { + if (! $value instanceof Builder) { + return ['value' => $value, 'bindings' => $value]; + } + + [$query, $bindings] = $this->parseSub($value); + + return ['value' => new Expression("({$query})"), 'bindings' => fn () => $bindings]; + }); + + $sql = $this->grammar->compileUpdate($this, $values->map(fn ($value) => $value['value'])->all()); return $this->connection->update($sql, $this->cleanBindings( - $this->grammar->prepareBindingsForUpdate($this->bindings, $values) + $this->grammar->prepareBindingsForUpdate($this->bindings, $values->map(fn ($value) => $value['bindings'])->all()) )); } diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index 3b4f117693f6..f2ce92c28ff3 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -7,6 +7,7 @@ use Illuminate\Database\Grammar as BaseGrammar; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\JoinClause; +use Illuminate\Database\Query\JoinLateralClause; use Illuminate\Support\Arr; use RuntimeException; @@ -60,6 +61,17 @@ public function compileSelect(Builder $query) return $this->compileUnionAggregate($query); } + // If a "group limit" is in place, we will need to compile the SQL to use a + // different syntax. This primarily supports limits on eager loads using + // Eloquent. We'll also set the columns if they have not been defined. + if (isset($query->groupLimit)) { + if (is_null($query->columns)) { + $query->columns = ['*']; + } + + return $this->compileGroupLimit($query); + } + // If the query does not have any columns set, we'll set the columns to the // * character to just get all of the columns from the database. Then we // can build the query and concatenate all the pieces together as one. @@ -182,10 +194,28 @@ protected function compileJoins(Builder $query, $joins) $tableAndNestedJoins = is_null($join->joins) ? $table : '('.$table.$nestedJoins.')'; + if ($join instanceof JoinLateralClause) { + return $this->compileJoinLateral($join, $tableAndNestedJoins); + } + return trim("{$join->type} join {$tableAndNestedJoins} {$this->compileWheres($join)}"); })->implode(' '); } + /** + * Compile a "lateral join" clause. + * + * @param \Illuminate\Database\Query\JoinLateralClause $join + * @param string $expression + * @return string + * + * @throws \RuntimeException + */ + public function compileJoinLateral(JoinLateralClause $join, string $expression): string + { + throw new RuntimeException('This database engine does not support lateral joins.'); + } + /** * Compile the "where" portions of the query. * @@ -917,6 +947,66 @@ protected function compileLimit(Builder $query, $limit) return 'limit '.(int) $limit; } + /** + * Compile a group limit clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileGroupLimit(Builder $query) + { + $selectBindings = array_merge($query->getRawBindings()['select'], $query->getRawBindings()['order']); + + $query->setBindings($selectBindings, 'select'); + $query->setBindings([], 'order'); + + $limit = (int) $query->groupLimit['value']; + $offset = $query->offset; + + if (isset($offset)) { + $offset = (int) $offset; + $limit += $offset; + + $query->offset = null; + } + + $components = $this->compileComponents($query); + + $components['columns'] .= $this->compileRowNumber( + $query->groupLimit['column'], + $components['orders'] ?? '' + ); + + unset($components['orders']); + + $table = $this->wrap('laravel_table'); + $row = $this->wrap('laravel_row'); + + $sql = $this->concatenate($components); + + $sql = 'select * from ('.$sql.') as '.$table.' where '.$row.' <= '.$limit; + + if (isset($offset)) { + $sql .= ' and '.$row.' > '.$offset; + } + + return $sql.' order by '.$row; + } + + /** + * Compile a row number clause. + * + * @param string $partition + * @param string $orders + * @return string + */ + protected function compileRowNumber($partition, $orders) + { + $over = trim('partition by '.$this->wrap($partition).' '.$orders); + + return ', row_number() over ('.$over.') as '.$this->wrap('laravel_row'); + } + /** * Compile the "offset" portions of the query. * @@ -1090,6 +1180,21 @@ public function compileInsertUsing(Builder $query, array $columns, string $sql) return "insert into {$table} ({$this->columnize($columns)}) $sql"; } + /** + * Compile an insert ignore statement using a subquery into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @param string $sql + * @return string + * + * @throws \RuntimeException + */ + public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql) + { + throw new RuntimeException('This database engine does not support inserting while ignoring errors.'); + } + /** * Compile an update statement into SQL. * @@ -1183,6 +1288,8 @@ public function prepareBindingsForUpdate(array $bindings, array $values) { $cleanBindings = Arr::except($bindings, ['select', 'join']); + $values = Arr::flatten(array_map(fn ($value) => value($value), $values)); + return array_values( array_merge($bindings['join'], $values, Arr::flatten($cleanBindings)) ); diff --git a/src/Illuminate/Database/Query/Grammars/MariaDbGrammar.php b/src/Illuminate/Database/Query/Grammars/MariaDbGrammar.php new file mode 100755 index 000000000000..37d2cc015431 --- /dev/null +++ b/src/Illuminate/Database/Query/Grammars/MariaDbGrammar.php @@ -0,0 +1,19 @@ +useLegacyGroupLimit($query) + ? $this->compileLegacyGroupLimit($query) + : parent::compileGroupLimit($query); + } + + /** + * Determine whether to use a legacy group limit clause for MySQL < 8.0. + * + * @param \Illuminate\Database\Query\Builder $query + * @return bool + */ + public function useLegacyGroupLimit(Builder $query) + { + $version = $query->getConnection()->getServerVersion(); + + return ! $query->getConnection()->isMaria() && version_compare($version, '8.0.11') < 0; + } + + /** + * Compile a group limit clause for MySQL < 8.0. + * + * Derived from https://softonsofa.com/tweaking-eloquent-relations-how-to-get-n-related-models-per-parent/. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileLegacyGroupLimit(Builder $query) + { + $limit = (int) $query->groupLimit['value']; + $offset = $query->offset; + + if (isset($offset)) { + $offset = (int) $offset; + $limit += $offset; + + $query->offset = null; + } + + $column = last(explode('.', $query->groupLimit['column'])); + $column = $this->wrap($column); + + $partition = ', @laravel_row := if(@laravel_group = '.$column.', @laravel_row + 1, 1) as `laravel_row`'; + $partition .= ', @laravel_group := '.$column; + + $orders = (array) $query->orders; + + array_unshift($orders, [ + 'column' => $query->groupLimit['column'], + 'direction' => 'asc', + ]); + + $query->orders = $orders; + + $components = $this->compileComponents($query); + + $sql = $this->concatenate($components); + + $from = '(select @laravel_row := 0, @laravel_group := 0) as `laravel_vars`, ('.$sql.') as `laravel_table`'; + + $sql = 'select `laravel_table`.*'.$partition.' from '.$from.' having `laravel_row` <= '.$limit; + + if (isset($offset)) { + $sql .= ' and `laravel_row` > '.$offset; + } + + return $sql.' order by `laravel_row`'; + } + /** * Compile an insert ignore statement into SQL. * @@ -106,6 +183,19 @@ public function compileInsertOrIgnore(Builder $query, array $values) return Str::replaceFirst('insert', 'insert ignore', $this->compileInsert($query, $values)); } + /** + * Compile an insert ignore statement using a subquery into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @param string $sql + * @return string + */ + public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql) + { + return Str::replaceFirst('insert', 'insert ignore', $this->compileInsertUsing($query, $columns, $sql)); + } + /** * Compile a "JSON contains" statement into SQL. * @@ -254,6 +344,18 @@ public function compileUpsert(Builder $query, array $values, array $uniqueBy, ar return $sql.$columns; } + /** + * Compile a "lateral join" clause. + * + * @param \Illuminate\Database\Query\JoinLateralClause $join + * @param string $expression + * @return string + */ + public function compileJoinLateral(JoinLateralClause $join, string $expression): string + { + return trim("{$join->type} join lateral {$expression} on true"); + } + /** * Prepare a JSON column being updated using the JSON_SET function. * diff --git a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php index b1786e5111cc..c22720a05c7c 100755 --- a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Query\Grammars; use Illuminate\Database\Query\Builder; +use Illuminate\Database\Query\JoinLateralClause; use Illuminate\Support\Arr; use Illuminate\Support\Str; @@ -323,6 +324,19 @@ public function compileInsertOrIgnore(Builder $query, array $values) return $this->compileInsert($query, $values).' on conflict do nothing'; } + /** + * Compile an insert ignore statement using a subquery into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @param string $sql + * @return string + */ + public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql) + { + return $this->compileInsertUsing($query, $columns, $sql).' on conflict do nothing'; + } + /** * Compile an insert and get ID statement into SQL. * @@ -396,6 +410,18 @@ public function compileUpsert(Builder $query, array $values, array $uniqueBy, ar return $sql.$columns; } + /** + * Compile a "lateral join" clause. + * + * @param \Illuminate\Database\Query\JoinLateralClause $join + * @param string $expression + * @return string + */ + public function compileJoinLateral(JoinLateralClause $join, string $expression): string + { + return trim("{$join->type} join lateral {$expression} on true"); + } + /** * Prepares a JSON column being updated using the JSONB_SET function. * diff --git a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php index 8bf7d39f6935..0c43e4db8bfd 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. * @@ -159,6 +184,25 @@ protected function compileJsonContainsKey($column) return 'json_type('.$field.$path.') is not null'; } + /** + * Compile a group limit clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileGroupLimit(Builder $query) + { + $version = $query->getConnection()->getServerVersion(); + + if (version_compare($version, '3.25.0') >= 0) { + return parent::compileGroupLimit($query); + } + + $query->groupLimit = null; + + return $this->compileSelect($query); + } + /** * Compile an update statement into SQL. * @@ -187,6 +231,19 @@ public function compileInsertOrIgnore(Builder $query, array $values) return Str::replaceFirst('insert', 'insert or ignore', $this->compileInsert($query, $values)); } + /** + * Compile an insert ignore statement using a subquery into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @param string $sql + * @return string + */ + public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql) + { + return Str::replaceFirst('insert', 'insert or ignore', $this->compileInsertUsing($query, $columns, $sql)); + } + /** * Compile the columns for an update statement. * diff --git a/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php index f68722a64bce..c084308b74ba 100755 --- a/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Query\Grammars; use Illuminate\Database\Query\Builder; +use Illuminate\Database\Query\JoinLateralClause; use Illuminate\Support\Arr; use Illuminate\Support\Str; @@ -312,6 +313,22 @@ protected function compileLimit(Builder $query, $limit) return ''; } + /** + * Compile a row number clause. + * + * @param string $partition + * @param string $orders + * @return string + */ + protected function compileRowNumber($partition, $orders) + { + if (empty($orders)) { + $orders = 'order by (select 0)'; + } + + return parent::compileRowNumber($partition, $orders); + } + /** * Compile the "offset" portions of the query. * @@ -444,6 +461,20 @@ public function prepareBindingsForUpdate(array $bindings, array $values) ); } + /** + * Compile a "lateral join" clause. + * + * @param \Illuminate\Database\Query\JoinLateralClause $join + * @param string $expression + * @return string + */ + public function compileJoinLateral(JoinLateralClause $join, string $expression): string + { + $type = $join->type == 'left' ? 'outer' : 'cross'; + + return trim("{$type} apply {$expression}"); + } + /** * Compile the SQL statement to define a savepoint. * diff --git a/src/Illuminate/Database/Query/JoinLateralClause.php b/src/Illuminate/Database/Query/JoinLateralClause.php new file mode 100644 index 000000000000..1be31d29626a --- /dev/null +++ b/src/Illuminate/Database/Query/JoinLateralClause.php @@ -0,0 +1,8 @@ +column_name; - }, $results); - } - /** * Process the results of a columns query. * @@ -38,7 +23,7 @@ public function processColumns($results) 'nullable' => $result->nullable === 'YES', 'default' => $result->default, 'auto_increment' => $result->extra === 'auto_increment', - 'comment' => $result->comment, + 'comment' => $result->comment ?: null, ]; }, $results); } diff --git a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index d35ee2dc2c6c..71c3e862ca37 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -30,21 +30,6 @@ public function processInsertGetId(Builder $query, $sql, $values, $sequence = nu return is_numeric($id) ? (int) $id : $id; } - /** - * Process the results of a column listing query. - * - * @deprecated Will be removed in a future Laravel version. - * - * @param array $results - * @return array - */ - public function processColumnListing($results) - { - return array_map(function ($result) { - return ((object) $result)->column_name; - }, $results); - } - /** * Process the results of a types query. * @@ -107,7 +92,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/Processor.php b/src/Illuminate/Database/Query/Processors/Processor.php index 97a994ebc221..936e6245b170 100755 --- a/src/Illuminate/Database/Query/Processors/Processor.php +++ b/src/Illuminate/Database/Query/Processors/Processor.php @@ -120,17 +120,4 @@ public function processForeignKeys($results) { return $results; } - - /** - * Process the results of a column listing query. - * - * @deprecated Will be removed in a future Laravel version. - * - * @param array $results - * @return array - */ - public function processColumnListing($results) - { - return $results; - } } diff --git a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php index 8f5fb98206e0..63cc84c067da 100644 --- a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php @@ -4,41 +4,33 @@ class SQLiteProcessor extends Processor { - /** - * Process the results of a column listing query. - * - * @deprecated Will be removed in a future Laravel version. - * - * @param array $results - * @return array - */ - public function processColumnListing($results) - { - return array_map(function ($result) { - return ((object) $result)->name; - }, $results); - } - /** * Process the results of a columns query. * * @param array $results + * @param string $sql * @return array */ - public function processColumns($results) + public function processColumns($results, $sql = '') { $hasPrimaryKey = array_sum(array_column($results, 'primary')) === 1; - return array_map(function ($result) use ($hasPrimaryKey) { + return array_map(function ($result) use ($hasPrimaryKey, $sql) { $result = (object) $result; $type = strtolower($result->type); + $collation = preg_match( + '/\b'.preg_quote($result->name).'\b[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:default|check|as)\s*(?:\(.*?\))?[^,]*)*collate\s+["\'`]?(\w+)/i', + $sql, + $matches + ) === 1 ? strtolower($matches[1]) : null; + return [ 'name' => $result->name, - 'type_name' => strtok($type, '('), + 'type_name' => strtok($type, '(') ?: '', 'type' => $type, - 'collation' => null, + 'collation' => $collation, 'nullable' => (bool) $result->nullable, 'default' => $result->default, 'auto_increment' => $hasPrimaryKey && $result->primary && $type === 'integer', diff --git a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php index c089593ed86a..8c632060b025 100755 --- a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php @@ -55,21 +55,6 @@ protected function processInsertGetIdForOdbc(Connection $connection) return is_object($row) ? $row->insertid : $row['insertid']; } - /** - * Process the results of a column listing query. - * - * @deprecated Will be removed in a future Laravel version. - * - * @param array $results - * @return array - */ - public function processColumnListing($results) - { - return array_map(function ($result) { - return ((object) $result)->name; - }, $results); - } - /** * Process the results of a columns query. * diff --git a/src/Illuminate/Database/SQLiteConnection.php b/src/Illuminate/Database/SQLiteConnection.php index ad7c1486d2d2..536fca3164b9 100755 --- a/src/Illuminate/Database/SQLiteConnection.php +++ b/src/Illuminate/Database/SQLiteConnection.php @@ -3,7 +3,6 @@ namespace Illuminate\Database; use Exception; -use Illuminate\Database\PDO\SQLiteDriver; use Illuminate\Database\Query\Grammars\SQLiteGrammar as QueryGrammar; use Illuminate\Database\Query\Processors\SQLiteProcessor; use Illuminate\Database\Schema\Grammars\SQLiteGrammar as SchemaGrammar; @@ -32,9 +31,17 @@ public function __construct($pdo, $database = '', $tablePrefix = '', array $conf return; } - $enableForeignKeyConstraints - ? $this->getSchemaBuilder()->enableForeignKeyConstraints() - : $this->getSchemaBuilder()->disableForeignKeyConstraints(); + $schemaBuilder = $this->getSchemaBuilder(); + + try { + $enableForeignKeyConstraints + ? $schemaBuilder->enableForeignKeyConstraints() + : $schemaBuilder->disableForeignKeyConstraints(); + } catch (QueryException $e) { + if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { + throw $e; + } + } } /** @@ -122,16 +129,6 @@ protected function getDefaultPostProcessor() return new SQLiteProcessor; } - /** - * Get the Doctrine DBAL driver. - * - * @return \Illuminate\Database\PDO\SQLiteDriver - */ - protected function getDoctrineDriver() - { - return new SQLiteDriver; - } - /** * Get the database connection foreign key constraints configuration option. * diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index 640cf0177c37..cd254b71b3f2 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -2,13 +2,12 @@ namespace Illuminate\Database\Schema; -use BadMethodCallException; use Closure; use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Concerns\HasUlids; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Grammars\Grammar; -use Illuminate\Database\SQLiteConnection; +use Illuminate\Database\Schema\Grammars\MySqlGrammar; use Illuminate\Support\Fluent; use Illuminate\Support\Traits\Macroable; @@ -130,6 +129,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)) { @@ -152,20 +155,7 @@ public function toSql(Connection $connection, Grammar $grammar) */ protected function ensureCommandsAreValid(Connection $connection) { - if ($connection instanceof SQLiteConnection) { - if ($this->commandsNamed(['dropColumn', 'renameColumn'])->count() > 1 - && ! $connection->usingNativeSchemaOperations()) { - throw new BadMethodCallException( - "SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification." - ); - } - - if ($this->commandsNamed(['dropForeign'])->count() > 0) { - throw new BadMethodCallException( - "SQLite doesn't support dropping foreign keys (you would need to re-create the table)." - ); - } - } + // } /** @@ -198,7 +188,7 @@ protected function addImpliedCommands(Connection $connection, Grammar $grammar) array_unshift($this->commands, $this->createCommand('change')); } - $this->addFluentIndexes(); + $this->addFluentIndexes($connection, $grammar); $this->addFluentCommands($connection, $grammar); } @@ -206,12 +196,21 @@ protected function addImpliedCommands(Connection $connection, Grammar $grammar) /** * Add the index commands fluently specified on columns. * + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return void */ - protected function addFluentIndexes() + protected function addFluentIndexes(Connection $connection, Grammar $grammar) { foreach ($this->columns as $column) { foreach (['primary', 'unique', 'index', 'fulltext', 'fullText', 'spatialIndex'] as $index) { + // If the column is supposed to be changed to an auto increment column and + // the specified index is primary, there is no need to add a command on + // MySQL, as it will be handled during the column definition instead. + if ($index === 'primary' && $column->autoIncrement && $column->change && $grammar instanceof MySqlGrammar) { + continue 2; + } + // If the index has been specified on the given column, but is simply equal // to "true" (boolean), no name has been specified for this index so the // index method can be called without a name and it will generate one. @@ -255,10 +254,6 @@ protected function addFluentIndexes() public function addFluentCommands(Connection $connection, Grammar $grammar) { foreach ($this->columns as $column) { - if ($column->change && ! $connection->usingNativeSchemaOperations()) { - continue; - } - foreach ($grammar->getFluentCommands() as $commandName) { $this->addCommand($commandName, compact('column')); } @@ -309,6 +304,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. * @@ -1341,100 +1358,26 @@ public function macAddress($column = 'mac_address') * Create a new geometry column on the table. * * @param string $column + * @param string|null $subtype + * @param int $srid * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function geometry($column) - { - return $this->addColumn('geometry', $column); - } - - /** - * Create a new point column on the table. - * - * @param string $column - * @param int|null $srid - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function point($column, $srid = null) - { - return $this->addColumn('point', $column, compact('srid')); - } - - /** - * Create a new linestring column on the table. - * - * @param string $column - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function lineString($column) - { - return $this->addColumn('linestring', $column); - } - - /** - * Create a new polygon column on the table. - * - * @param string $column - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function polygon($column) - { - return $this->addColumn('polygon', $column); - } - - /** - * Create a new geometrycollection column on the table. - * - * @param string $column - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function geometryCollection($column) + public function geometry($column, $subtype = null, $srid = 0) { - return $this->addColumn('geometrycollection', $column); + return $this->addColumn('geometry', $column, compact('subtype', 'srid')); } /** - * Create a new multipoint column on the table. + * Create a new geography column on the table. * * @param string $column + * @param string|null $subtype + * @param int $srid * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function multiPoint($column) + public function geography($column, $subtype = null, $srid = 4326) { - return $this->addColumn('multipoint', $column); - } - - /** - * Create a new multilinestring column on the table. - * - * @param string $column - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function multiLineString($column) - { - return $this->addColumn('multilinestring', $column); - } - - /** - * Create a new multipolygon column on the table. - * - * @param string $column - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function multiPolygon($column) - { - return $this->addColumn('multipolygon', $column); - } - - /** - * Create a new multipolygon column on the table. - * - * @param string $column - * @return \Illuminate\Database\Schema\ColumnDefinition - */ - public function multiPolygonZ($column) - { - return $this->addColumn('multipolygonz', $column); + return $this->addColumn('geography', $column, compact('subtype', 'srid')); } /** @@ -1656,7 +1599,11 @@ protected function dropIndexCommand($command, $type, $index) */ protected function createIndexName($type, array $columns) { - $index = strtolower($this->prefix.$this->table.'_'.implode('_', $columns).'_'.$type); + $table = str_contains($this->table, '.') + ? substr_replace($this->table, '.'.$this->prefix, strrpos($this->table, '.'), 1) + : $this->prefix.$this->table; + + $index = strtolower($table.'_'.implode('_', $columns).'_'.$type); return str_replace(['-', '.'], '_', $index); } diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 94d655c0f1ca..808b791b66af 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -5,11 +5,14 @@ use Closure; use Illuminate\Container\Container; use Illuminate\Database\Connection; +use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; use LogicException; class Builder { + use Macroable; + /** * The database connection instance. * @@ -45,13 +48,6 @@ class Builder */ public static $defaultMorphKeyType = 'int'; - /** - * Indicates whether Doctrine DBAL usage will be prevented if possible when dropping, renaming, and modifying columns. - * - * @var bool - */ - public static $alwaysUsesNativeSchemaOperationsIfPossible = false; - /** * Create a new database Schema manager. * @@ -112,17 +108,6 @@ public static function morphUsingUlids() return static::defaultMorphKeyType('ulid'); } - /** - * Attempt to use native schema operations for dropping, renaming, and modifying columns, even if Doctrine DBAL is installed. - * - * @param bool $value - * @return void - */ - public static function useNativeSchemaOperationsIfPossible(bool $value = true) - { - static::$alwaysUsesNativeSchemaOperationsIfPossible = $value; - } - /** * Create a database in the schema. * @@ -199,6 +184,16 @@ public function getTables() ); } + /** + * Get the names of the tables that belong to the database. + * + * @return array + */ + public function getTableListing() + { + return array_column($this->getTables(), 'name'); + } + /** * Get the views that belong to the database. * @@ -221,20 +216,6 @@ public function getTypes() throw new LogicException('This database driver does not support user-defined types.'); } - /** - * Get all of the table names for the database. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return array - * - * @throws \LogicException - */ - public function getAllTables() - { - throw new LogicException('This database driver does not support getting all tables.'); - } - /** * Determine if the given table has a given column. * @@ -309,14 +290,6 @@ public function whenTableDoesntHaveColumn(string $table, string $column, Closure */ public function getColumnType($table, $column, $fullDefinition = false) { - if (! $this->connection->usingNativeSchemaOperations()) { - $table = $this->connection->getTablePrefix().$table; - - $type = $this->connection->getDoctrineColumn($table, $column)->getType(); - - return $type::lookupName($type); - } - $columns = $this->getColumns($table); foreach ($columns as $value) { @@ -369,6 +342,43 @@ public function getIndexes($table) ); } + /** + * Get the names of the indexes for a given table. + * + * @param string $table + * @return array + */ + public function getIndexListing($table) + { + return array_column($this->getIndexes($table), 'name'); + } + + /** + * Determine if the given table has a given index. + * + * @param string $table + * @param string|array $index + * @param string|null $type + * @return bool + */ + public function hasIndex($table, $index, $type = null) + { + $type = is_null($type) ? $type : strtolower($type); + + foreach ($this->getIndexes($table) as $value) { + $typeMatches = is_null($type) + || ($type === 'primary' && $value['primary']) + || ($type === 'unique' && $value['unique']) + || $type === $value['type']; + + if (($value['name'] === $index || $value['columns'] === $index) && $typeMatches) { + return true; + } + } + + return false; + } + /** * Get the foreign keys for a given table. * diff --git a/src/Illuminate/Database/Schema/ColumnDefinition.php b/src/Illuminate/Database/Schema/ColumnDefinition.php index 51265ac4213e..0093ee1f25c6 100644 --- a/src/Illuminate/Database/Schema/ColumnDefinition.php +++ b/src/Illuminate/Database/Schema/ColumnDefinition.php @@ -10,23 +10,23 @@ * @method $this autoIncrement() Set INTEGER columns as auto-increment (primary key) * @method $this change() Change the column * @method $this charset(string $charset) Specify a character set for the column (MySQL) - * @method $this collation(string $collation) Specify a collation for the column (MySQL/PostgreSQL/SQL Server) + * @method $this collation(string $collation) Specify a collation for the column * @method $this comment(string $comment) Add a comment to the column (MySQL/PostgreSQL) * @method $this default(mixed $value) Specify a "default" value for the column * @method $this first() Place the column "first" in the table (MySQL) * @method $this from(int $startingValue) Set the starting value of an auto-incrementing field (MySQL / PostgreSQL) - * @method $this generatedAs(string|Expression $expression = null) Create a SQL compliant identity column (PostgreSQL) - * @method $this index(string $indexName = null) Add an index + * @method $this generatedAs(string|\Illuminate\Database\Query\Expression $expression = null) Create a SQL compliant identity column (PostgreSQL) + * @method $this index(bool|string $indexName = null) Add an index * @method $this invisible() Specify that the column should be invisible to "SELECT *" (MySQL) * @method $this nullable(bool $value = true) Allow NULL values to be inserted into the column * @method $this persisted() Mark the computed generated column as persistent (SQL Server) - * @method $this primary() Add a primary index - * @method $this fulltext(string $indexName = null) Add a fulltext index - * @method $this spatialIndex(string $indexName = null) Add a spatial index + * @method $this primary(bool $value = true) Add a primary index + * @method $this fulltext(bool|string $indexName = null) Add a fulltext index + * @method $this spatialIndex(bool|string $indexName = null) Add a spatial index * @method $this startingValue(int $startingValue) Set the starting value of an auto-incrementing field (MySQL/PostgreSQL) * @method $this storedAs(string $expression) Create a stored generated column (MySQL/PostgreSQL/SQLite) * @method $this type(string $type) Specify a type for the column - * @method $this unique(string $indexName = null) Add a unique index + * @method $this unique(bool|string $indexName = null) Add a unique index * @method $this unsigned() Set the INTEGER column as UNSIGNED (MySQL) * @method $this useCurrent() Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value * @method $this useCurrentOnUpdate() Set the TIMESTAMP column to use CURRENT_TIMESTAMP when updating (MySQL) diff --git a/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php b/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php deleted file mode 100644 index 7f429c0eccc5..000000000000 --- a/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php +++ /dev/null @@ -1,238 +0,0 @@ -isDoctrineAvailable()) { - throw new RuntimeException(sprintf( - 'Changing columns for table "%s" requires Doctrine DBAL. Please install the doctrine/dbal package.', - $blueprint->getTable() - )); - } - - $schema = $connection->getDoctrineSchemaManager(); - $databasePlatform = $connection->getDoctrineConnection()->getDatabasePlatform(); - $databasePlatform->registerDoctrineTypeMapping('enum', 'string'); - - $tableDiff = static::getChangedDiff( - $grammar, $blueprint, $schema - ); - - if (! $tableDiff->isEmpty()) { - return (array) $databasePlatform->getAlterTableSQL($tableDiff); - } - - return []; - } - - /** - * Get the Doctrine table difference for the given changes. - * - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema - * @return \Doctrine\DBAL\Schema\TableDiff - */ - protected static function getChangedDiff($grammar, Blueprint $blueprint, SchemaManager $schema) - { - $current = $schema->introspectTable($grammar->getTablePrefix().$blueprint->getTable()); - - return $schema->createComparator()->compareTables( - $current, static::getTableWithColumnChanges($blueprint, $current) - ); - } - - /** - * Get a copy of the given Doctrine table after making the column changes. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Doctrine\DBAL\Schema\Table $table - * @return \Doctrine\DBAL\Schema\Table - */ - protected static function getTableWithColumnChanges(Blueprint $blueprint, Table $table) - { - $table = clone $table; - - foreach ($blueprint->getChangedColumns() as $fluent) { - $column = static::getDoctrineColumn($table, $fluent); - - // Here we will spin through each fluent column definition and map it to the proper - // Doctrine column definitions - which is necessary because Laravel and Doctrine - // use some different terminology for various column attributes on the tables. - foreach ($fluent->getAttributes() as $key => $value) { - if (! is_null($option = static::mapFluentOptionToDoctrine($key))) { - if (method_exists($column, $method = 'set'.ucfirst($option))) { - $column->{$method}(static::mapFluentValueToDoctrine($option, $value)); - continue; - } - - $column->setPlatformOption($option, static::mapFluentValueToDoctrine($option, $value)); - } - } - } - - return $table; - } - - /** - * Get the Doctrine column instance for a column change. - * - * @param \Doctrine\DBAL\Schema\Table $table - * @param \Illuminate\Support\Fluent $fluent - * @return \Doctrine\DBAL\Schema\Column - */ - protected static function getDoctrineColumn(Table $table, Fluent $fluent) - { - return $table->modifyColumn( - $fluent['name'], static::getDoctrineColumnChangeOptions($fluent) - )->getColumn($fluent['name']); - } - - /** - * Get the Doctrine column change options. - * - * @param \Illuminate\Support\Fluent $fluent - * @return array - */ - protected static function getDoctrineColumnChangeOptions(Fluent $fluent) - { - $options = ['Type' => static::getDoctrineColumnType($fluent['type'])]; - - if (! in_array($fluent['type'], ['smallint', 'integer', 'bigint'])) { - $options['Autoincrement'] = false; - } - - if (in_array($fluent['type'], ['tinyText', 'text', 'mediumText', 'longText'])) { - $options['Length'] = static::calculateDoctrineTextLength($fluent['type']); - } - - if ($fluent['type'] === 'char') { - $options['Fixed'] = true; - } - - if (static::doesntNeedCharacterOptions($fluent['type'])) { - $options['PlatformOptions'] = [ - 'collation' => '', - 'charset' => '', - ]; - } - - return $options; - } - - /** - * Get the doctrine column type. - * - * @param string $type - * @return \Doctrine\DBAL\Types\Type - */ - protected static function getDoctrineColumnType($type) - { - $type = strtolower($type); - - return Type::getType(match ($type) { - 'biginteger' => 'bigint', - 'smallinteger' => 'smallint', - 'tinytext', 'mediumtext', 'longtext' => 'text', - 'binary' => 'blob', - 'uuid' => 'guid', - 'char' => 'string', - 'double' => 'float', - default => $type, - }); - } - - /** - * Calculate the proper column length to force the Doctrine text type. - * - * @param string $type - * @return int - */ - protected static function calculateDoctrineTextLength($type) - { - return match ($type) { - 'tinyText' => 1, - 'mediumText' => 65535 + 1, - 'longText' => 16777215 + 1, - default => 255 + 1, - }; - } - - /** - * Determine if the given type does not need character / collation options. - * - * @param string $type - * @return bool - */ - protected static function doesntNeedCharacterOptions($type) - { - return in_array($type, [ - 'bigInteger', - 'binary', - 'boolean', - 'date', - 'dateTime', - 'decimal', - 'double', - 'float', - 'integer', - 'json', - 'mediumInteger', - 'smallInteger', - 'time', - 'timestamp', - 'tinyInteger', - ]); - } - - /** - * Get the matching Doctrine option for a given Fluent attribute name. - * - * @param string $attribute - * @return string|null - */ - protected static function mapFluentOptionToDoctrine($attribute) - { - return match ($attribute) { - 'type', 'name' => null, - 'nullable' => 'notnull', - 'total' => 'precision', - 'places' => 'scale', - default => $attribute, - }; - } - - /** - * Get the matching Doctrine value for a given Fluent attribute. - * - * @param string $option - * @param mixed $value - * @return mixed - */ - protected static function mapFluentValueToDoctrine($option, $value) - { - return $option === 'notnull' ? ! $value : $value; - } -} diff --git a/src/Illuminate/Database/Schema/Grammars/Grammar.php b/src/Illuminate/Database/Schema/Grammars/Grammar.php index 9933a38be0fc..86f4290b39d8 100755 --- a/src/Illuminate/Database/Schema/Grammars/Grammar.php +++ b/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -3,8 +3,6 @@ namespace Illuminate\Database\Schema\Grammars; use BackedEnum; -use Doctrine\DBAL\Schema\AbstractSchemaManager as SchemaManager; -use Doctrine\DBAL\Schema\TableDiff; use Illuminate\Contracts\Database\Query\Expression; use Illuminate\Database\Concerns\CompilesJsonPaths; use Illuminate\Database\Connection; @@ -76,7 +74,11 @@ public function compileDropDatabaseIfExists($name) */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { - return RenameColumn::compile($this, $blueprint, $command, $connection); + return sprintf('alter table %s rename column %s to %s', + $this->wrapTable($blueprint), + $this->wrap($command->from), + $this->wrap($command->to) + ); } /** @@ -91,7 +93,7 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - return ChangeColumn::compile($this, $blueprint, $command, $connection); + throw new LogicException('This database driver does not support modifying columns.'); } /** @@ -162,6 +164,18 @@ public function compileForeign(Blueprint $blueprint, Fluent $command) return $sql; } + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + throw new RuntimeException('This database driver does not support dropping foreign keys.'); + } + /** * Compile the blueprint's added column definitions. * @@ -228,7 +242,7 @@ protected function addModifiers($sql, Blueprint $blueprint, Fluent $column) } /** - * Get the primary key command if it exists on the blueprint. + * Get the command with a given name if it exists on the blueprint. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param string $name @@ -257,6 +271,24 @@ protected function getCommandsByName(Blueprint $blueprint, $name) }); } + /* + * Determine if a command with a given name exists on the blueprint. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param string $name + * @return bool + */ + protected function hasCommand(Blueprint $blueprint, $name) + { + foreach ($blueprint->getCommands() as $command) { + if ($command->name === $name) { + return true; + } + } + + return false; + } + /** * Add a prefix to an array of values. * @@ -319,22 +351,6 @@ protected function getDefaultValue($value) : "'".(string) $value."'"; } - /** - * Create an empty Doctrine DBAL TableDiff from the Blueprint. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema - * @return \Doctrine\DBAL\Schema\TableDiff - */ - public function getDoctrineTableDiff(Blueprint $blueprint, SchemaManager $schema) - { - $tableName = $this->getTablePrefix().$blueprint->getTable(); - - $table = $schema->introspectTable($tableName); - - return $schema->createComparator()->compareTables(oldTable: $table, newTable: $table); - } - /** * Get the fluent commands for the grammar. * diff --git a/src/Illuminate/Database/Schema/Grammars/MariaDbGrammar.php b/src/Illuminate/Database/Schema/Grammars/MariaDbGrammar.php new file mode 100755 index 000000000000..39ae68619127 --- /dev/null +++ b/src/Illuminate/Database/Schema/Grammars/MariaDbGrammar.php @@ -0,0 +1,87 @@ +getServerVersion(), '10.5.2', '<')) { + $column = collect($connection->getSchemaBuilder()->getColumns($blueprint->getTable())) + ->firstWhere('name', $command->from); + + $modifiers = $this->addModifiers($column['type'], $blueprint, new ColumnDefinition([ + 'change' => true, + 'type' => match ($column['type_name']) { + 'bigint' => 'bigInteger', + 'int' => 'integer', + 'mediumint' => 'mediumInteger', + 'smallint' => 'smallInteger', + 'tinyint' => 'tinyInteger', + default => $column['type_name'], + }, + 'nullable' => $column['nullable'], + 'default' => $column['default'] && str_starts_with(strtolower($column['default']), 'current_timestamp') + ? new Expression($column['default']) + : $column['default'], + 'autoIncrement' => $column['auto_increment'], + 'collation' => $column['collation'], + 'comment' => $column['comment'], + ])); + + return sprintf('alter table %s change %s %s %s', + $this->wrapTable($blueprint), + $this->wrap($command->from), + $this->wrap($command->to), + $modifiers + ); + } + + return parent::compileRenameColumn($blueprint, $command, $connection); + } + + /** + * Create the column definition for a uuid type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeUuid(Fluent $column) + { + return 'uuid'; + } + + /** + * Create the column definition for a spatial Geometry type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeGeometry(Fluent $column) + { + $subtype = $column->subtype ? strtolower($column->subtype) : null; + + if (! in_array($subtype, ['point', 'linestring', 'polygon', 'geometrycollection', 'multipoint', 'multilinestring', 'multipolygon'])) { + $subtype = null; + } + + return sprintf('%s%s', + $subtype ?? 'geometry', + $column->srid ? ' ref_system_id='.$column->srid : '' + ); + } +} diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 07847fa23f8e..aec73db6eef4 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -5,6 +5,7 @@ use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\ColumnDefinition; use Illuminate\Support\Fluent; use RuntimeException; @@ -17,7 +18,7 @@ class MySqlGrammar extends Grammar */ protected $modifiers = [ 'Unsigned', 'Charset', 'Collate', 'VirtualAs', 'StoredAs', 'Nullable', - 'Srid', 'Default', 'OnUpdate', 'Invisible', 'Increment', 'Comment', 'After', 'First', + 'Default', 'OnUpdate', 'Invisible', 'Increment', 'Comment', 'After', 'First', ]; /** @@ -43,11 +44,21 @@ class MySqlGrammar extends Grammar */ public function compileCreateDatabase($name, $connection) { + $charset = $connection->getConfig('charset'); + $collation = $connection->getConfig('collation'); + + if (! $charset || ! $collation) { + return sprintf( + 'create database %s', + $this->wrapValue($name), + ); + } + return sprintf( 'create database %s default character set %s default collate %s', $this->wrapValue($name), - $this->wrapValue($connection->getConfig('charset')), - $this->wrapValue($connection->getConfig('collation')), + $this->wrapValue($charset), + $this->wrapValue($collation), ); } @@ -65,18 +76,6 @@ public function compileDropDatabaseIfExists($name) ); } - /** - * Compile the query to determine the list of tables. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileTableExists() - { - return "select * from information_schema.tables where table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"; - } - /** * Compile the query to determine the tables. * @@ -88,7 +87,7 @@ public function compileTables($database) return sprintf( 'select table_name as `name`, (data_length + index_length) as `size`, ' .'table_comment as `comment`, engine as `engine`, table_collation as `collation` ' - ."from information_schema.tables where table_schema = %s and table_type = 'BASE TABLE' " + ."from information_schema.tables where table_schema = %s and table_type in ('BASE TABLE', 'SYSTEM VERSIONED') " .'order by table_name', $this->quoteString($database) ); @@ -110,42 +109,6 @@ public function compileViews($database) ); } - /** - * Compile the SQL needed to retrieve all table names. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileGetAllTables() - { - return 'SHOW FULL TABLES WHERE table_type = \'BASE TABLE\''; - } - - /** - * Compile the SQL needed to retrieve all view names. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileGetAllViews() - { - return 'SHOW FULL TABLES WHERE table_type = \'VIEW\''; - } - - /** - * Compile the query to determine the list of columns. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileColumnListing() - { - return 'select column_name as `column_name` from information_schema.columns where table_schema = ? and table_name = ?'; - } - /** * Compile the query to determine the columns. * @@ -158,7 +121,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 +211,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) ); } @@ -344,13 +319,41 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { - return $connection->usingNativeSchemaOperations() - ? sprintf('alter table %s rename column %s to %s', + $version = $connection->getServerVersion(); + + if (($connection->isMaria() && version_compare($version, '10.5.2', '<')) || + (! $connection->isMaria() && version_compare($version, '8.0.3', '<'))) { + $column = collect($connection->getSchemaBuilder()->getColumns($blueprint->getTable())) + ->firstWhere('name', $command->from); + + $modifiers = $this->addModifiers($column['type'], $blueprint, new ColumnDefinition([ + 'change' => true, + 'type' => match ($column['type_name']) { + 'bigint' => 'bigInteger', + 'int' => 'integer', + 'mediumint' => 'mediumInteger', + 'smallint' => 'smallInteger', + 'tinyint' => 'tinyInteger', + default => $column['type_name'], + }, + 'nullable' => $column['nullable'], + 'default' => $column['default'] && str_starts_with(strtolower($column['default']), 'current_timestamp') + ? new Expression($column['default']) + : $column['default'], + 'autoIncrement' => $column['auto_increment'], + 'collation' => $column['collation'], + 'comment' => $column['comment'], + ])); + + return sprintf('alter table %s change %s %s %s', $this->wrapTable($blueprint), $this->wrap($command->from), - $this->wrap($command->to) - ) - : parent::compileRenameColumn($blueprint, $command, $connection); + $this->wrap($command->to), + $modifiers + ); + } + + return parent::compileRenameColumn($blueprint, $command, $connection); } /** @@ -365,10 +368,6 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - if (! $connection->usingNativeSchemaOperations()) { - return parent::compileChange($blueprint, $command, $connection); - } - $columns = []; foreach ($blueprint->getChangedColumns() as $column) { @@ -1042,86 +1041,33 @@ protected function typeMacAddress(Fluent $column) * @param \Illuminate\Support\Fluent $column * @return string */ - public function typeGeometry(Fluent $column) + protected function typeGeometry(Fluent $column) { - return 'geometry'; - } + $subtype = $column->subtype ? strtolower($column->subtype) : null; - /** - * Create the column definition for a spatial Point type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typePoint(Fluent $column) - { - return 'point'; - } - - /** - * Create the column definition for a spatial LineString type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeLineString(Fluent $column) - { - return 'linestring'; - } - - /** - * Create the column definition for a spatial Polygon type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typePolygon(Fluent $column) - { - return 'polygon'; - } - - /** - * Create the column definition for a spatial GeometryCollection type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeGeometryCollection(Fluent $column) - { - return 'geometrycollection'; - } - - /** - * Create the column definition for a spatial MultiPoint type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeMultiPoint(Fluent $column) - { - return 'multipoint'; - } + if (! in_array($subtype, ['point', 'linestring', 'polygon', 'geometrycollection', 'multipoint', 'multilinestring', 'multipolygon'])) { + $subtype = null; + } - /** - * Create the column definition for a spatial MultiLineString type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeMultiLineString(Fluent $column) - { - return 'multilinestring'; + return sprintf('%s%s', + $subtype ?? 'geometry', + match (true) { + $column->srid && $this->connection?->isMaria() => ' ref_system_id='.$column->srid, + (bool) $column->srid => ' srid '.$column->srid, + default => '', + } + ); } /** - * Create the column definition for a spatial MultiPolygon type. + * Create the column definition for a spatial Geography type. * * @param \Illuminate\Support\Fluent $column * @return string */ - public function typeMultiPolygon(Fluent $column) + protected function typeGeography(Fluent $column) { - return 'multipolygon'; + return $this->typeGeometry($column); } /** @@ -1296,7 +1242,9 @@ protected function modifyOnUpdate(Blueprint $blueprint, Fluent $column) protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { if (in_array($column->type, $this->serials) && $column->autoIncrement) { - return ' auto_increment primary key'; + return $this->hasCommand($blueprint, 'primary') || ($column->change && ! $column->primary) + ? ' auto_increment' + : ' auto_increment primary key'; } } @@ -1342,20 +1290,6 @@ protected function modifyComment(Blueprint $blueprint, Fluent $column) } } - /** - * Get the SQL for a SRID column modifier. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $column - * @return string|null - */ - protected function modifySrid(Blueprint $blueprint, Fluent $column) - { - if (is_int($column->srid) && $column->srid > 0) { - return ' srid '.$column->srid; - } - } - /** * Wrap a single string in keyword identifiers. * diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index a0b67f01ca60..aad87542d552 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -68,18 +68,6 @@ public function compileDropDatabaseIfExists($name) ); } - /** - * Compile the query to determine if a table exists. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileTableExists() - { - return "select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"; - } - /** * Compile the query to determine the tables. * @@ -89,7 +77,7 @@ public function compileTables() { return 'select c.relname as name, n.nspname as schema, pg_total_relation_size(c.oid) as size, ' ."obj_description(c.oid, 'pg_class') as comment from pg_class c, pg_namespace n " - ."where c.relkind in ('r', 'p') and n.oid = c.relnamespace and n.nspname not in ('pg_catalog', 'information_schema')" + ."where c.relkind in ('r', 'p') and n.oid = c.relnamespace and n.nspname not in ('pg_catalog', 'information_schema') " .'order by c.relname'; } @@ -121,44 +109,6 @@ public function compileTypes() ."and n.nspname not in ('pg_catalog', 'information_schema')"; } - /** - * Compile the SQL needed to retrieve all table names. - * - * @deprecated Will be removed in a future Laravel version. - * - * @param string|array $searchPath - * @return string - */ - public function compileGetAllTables($searchPath) - { - return "select tablename, concat('\"', schemaname, '\".\"', tablename, '\"') as qualifiedname from pg_catalog.pg_tables where schemaname in ('".implode("','", (array) $searchPath)."')"; - } - - /** - * Compile the SQL needed to retrieve all view names. - * - * @deprecated Will be removed in a future Laravel version. - * - * @param string|array $searchPath - * @return string - */ - public function compileGetAllViews($searchPath) - { - return "select viewname, concat('\"', schemaname, '\".\"', viewname, '\"') as qualifiedname from pg_catalog.pg_views where schemaname in ('".implode("','", (array) $searchPath)."')"; - } - - /** - * Compile the query to determine the list of columns. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileColumnListing() - { - return 'select column_name from information_schema.columns where table_catalog = ? and table_schema = ? and table_name = ?'; - } - /** * Compile the query to determine the columns. * @@ -169,11 +119,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', @@ -280,27 +230,10 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent { if ($command->column->autoIncrement && $value = $command->column->get('startingValue', $command->column->get('from'))) { - return 'alter sequence '.$blueprint->getTable().'_'.$command->column->name.'_seq restart with '.$value; - } - } + $table = last(explode('.', $blueprint->getTable())); - /** - * Compile a rename column command. - * - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Illuminate\Database\Connection $connection - * @return array|string - */ - public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) - { - return $connection->usingNativeSchemaOperations() - ? sprintf('alter table %s rename column %s to %s', - $this->wrapTable($blueprint), - $this->wrap($command->from), - $this->wrap($command->to) - ) - : parent::compileRenameColumn($blueprint, $command, $connection); + return 'alter sequence '.$blueprint->getPrefix().$table.'_'.$command->column->name.'_seq restart with '.$value; + } } /** @@ -315,10 +248,6 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - if (! $connection->usingNativeSchemaOperations()) { - return parent::compileChange($blueprint, $command, $connection); - } - $columns = []; foreach ($blueprint->getChangedColumns() as $column) { @@ -533,18 +462,6 @@ public function compileDropAllDomains($domains) return 'drop domain '.implode(',', $this->escapeNames($domains)).' cascade'; } - /** - * Compile the SQL needed to retrieve all type names. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileGetAllTypes() - { - return 'select distinct pg_type.typname from pg_type inner join pg_enum on pg_enum.enumtypid = pg_type.oid'; - } - /** * Compile a drop column command. * @@ -568,7 +485,8 @@ public function compileDropColumn(Blueprint $blueprint, Fluent $command) */ public function compileDropPrimary(Blueprint $blueprint, Fluent $command) { - $index = $this->wrap("{$blueprint->getPrefix()}{$blueprint->getTable()}_pkey"); + $table = last(explode('.', $blueprint->getTable())); + $index = $this->wrap("{$blueprint->getPrefix()}{$table}_pkey"); return 'alter table '.$this->wrapTable($blueprint)." drop constraint {$index}"; } @@ -816,7 +734,7 @@ protected function typeLongText(Fluent $column) */ protected function typeInteger(Fluent $column) { - return $column->autoIncrement && is_null($column->generatedAs) ? 'serial' : 'integer'; + return $column->autoIncrement && is_null($column->generatedAs) && ! $column->change ? 'serial' : 'integer'; } /** @@ -827,7 +745,7 @@ protected function typeInteger(Fluent $column) */ protected function typeBigInteger(Fluent $column) { - return $column->autoIncrement && is_null($column->generatedAs) ? 'bigserial' : 'bigint'; + return $column->autoIncrement && is_null($column->generatedAs) && ! $column->change ? 'bigserial' : 'bigint'; } /** @@ -860,7 +778,7 @@ protected function typeTinyInteger(Fluent $column) */ protected function typeSmallInteger(Fluent $column) { - return $column->autoIncrement && is_null($column->generatedAs) ? 'smallserial' : 'smallint'; + return $column->autoIncrement && is_null($column->generatedAs) && ! $column->change ? 'smallserial' : 'smallint'; } /** @@ -1107,115 +1025,32 @@ protected function typeMacAddress(Fluent $column) */ protected function typeGeometry(Fluent $column) { - return $this->formatPostGisType('geometry', $column); - } - - /** - * Create the column definition for a spatial Point type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - protected function typePoint(Fluent $column) - { - return $this->formatPostGisType('point', $column); - } - - /** - * Create the column definition for a spatial LineString type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - protected function typeLineString(Fluent $column) - { - return $this->formatPostGisType('linestring', $column); - } - - /** - * Create the column definition for a spatial Polygon type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - protected function typePolygon(Fluent $column) - { - return $this->formatPostGisType('polygon', $column); - } - - /** - * Create the column definition for a spatial GeometryCollection type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - protected function typeGeometryCollection(Fluent $column) - { - return $this->formatPostGisType('geometrycollection', $column); - } - - /** - * Create the column definition for a spatial MultiPoint type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - protected function typeMultiPoint(Fluent $column) - { - return $this->formatPostGisType('multipoint', $column); - } - - /** - * Create the column definition for a spatial MultiLineString type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeMultiLineString(Fluent $column) - { - return $this->formatPostGisType('multilinestring', $column); - } - - /** - * Create the column definition for a spatial MultiPolygon type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - protected function typeMultiPolygon(Fluent $column) - { - return $this->formatPostGisType('multipolygon', $column); - } + if ($column->subtype) { + return sprintf('geometry(%s%s)', + strtolower($column->subtype), + $column->srid ? ','.$column->srid : '' + ); + } - /** - * Create the column definition for a spatial MultiPolygonZ type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - protected function typeMultiPolygonZ(Fluent $column) - { - return $this->formatPostGisType('multipolygonz', $column); + return 'geometry'; } /** - * Format the column definition for a PostGIS spatial type. + * Create the column definition for a spatial Geography type. * - * @param string $type * @param \Illuminate\Support\Fluent $column * @return string */ - private function formatPostGisType($type, Fluent $column) + protected function typeGeography(Fluent $column) { - if ($column->isGeometry === null) { - return sprintf('geography(%s, %s)', $type, $column->projection ?? '4326'); - } - - if ($column->projection !== null) { - return sprintf('geometry(%s, %s)', $type, $column->projection); + if ($column->subtype) { + return sprintf('geography(%s%s)', + strtolower($column->subtype), + $column->srid ? ','.$column->srid : '' + ); } - return "geometry({$type})"; + return 'geography'; } /** @@ -1258,7 +1093,11 @@ protected function modifyNullable(Blueprint $blueprint, Fluent $column) protected function modifyDefault(Blueprint $blueprint, Fluent $column) { if ($column->change) { - return is_null($column->default) ? 'drop default' : 'set default '.$this->getDefaultValue($column->default); + if (! $column->autoIncrement || ! is_null($column->generatedAs)) { + return is_null($column->default) ? 'drop default' : 'set default '.$this->getDefaultValue($column->default); + } + + return null; } if (! is_null($column->default)) { @@ -1276,6 +1115,7 @@ protected function modifyDefault(Blueprint $blueprint, Fluent $column) protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { if (! $column->change + && ! $this->hasCommand($blueprint, 'primary') && (in_array($column->type, $this->serials) || ($column->generatedAs !== null)) && $column->autoIncrement) { return ' primary key'; @@ -1350,7 +1190,7 @@ protected function modifyGeneratedAs(Blueprint $blueprint, Fluent $column) } if ($column->change) { - $changes = ['drop identity if exists']; + $changes = $column->autoIncrement && is_null($sql) ? [] : ['drop identity if exists']; if (! is_null($sql)) { $changes[] = 'add '.$sql; diff --git a/src/Illuminate/Database/Schema/Grammars/RenameColumn.php b/src/Illuminate/Database/Schema/Grammars/RenameColumn.php deleted file mode 100644 index ff611c93160a..000000000000 --- a/src/Illuminate/Database/Schema/Grammars/RenameColumn.php +++ /dev/null @@ -1,93 +0,0 @@ -getDoctrineSchemaManager(); - $databasePlatform = $connection->getDoctrineConnection()->getDatabasePlatform(); - $databasePlatform->registerDoctrineTypeMapping('enum', 'string'); - - $column = $connection->getDoctrineColumn( - $grammar->getTablePrefix().$blueprint->getTable(), $command->from - ); - - return (array) $databasePlatform->getAlterTableSQL(static::getRenamedDiff( - $grammar, $blueprint, $command, $column, $schema - )); - } - - /** - * Get a new column instance with the new column name. - * - * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar - * @param \Illuminate\Database\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command - * @param \Doctrine\DBAL\Schema\Column $column - * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema - * @return \Doctrine\DBAL\Schema\TableDiff - */ - protected static function getRenamedDiff(Grammar $grammar, Blueprint $blueprint, Fluent $command, Column $column, SchemaManager $schema) - { - return static::setRenamedColumns( - $grammar->getDoctrineTableDiff($blueprint, $schema), $command, $column - ); - } - - /** - * Set the renamed columns on the table diff. - * - * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff - * @param \Illuminate\Support\Fluent $command - * @param \Doctrine\DBAL\Schema\Column $column - * @return \Doctrine\DBAL\Schema\TableDiff - */ - protected static function setRenamedColumns(TableDiff $tableDiff, Fluent $command, Column $column) - { - return new TableDiff( - $tableDiff->getOldTable(), - $tableDiff->getAddedColumns(), - $tableDiff->getModifiedColumns(), - $tableDiff->getDroppedColumns(), - [$command->from => new Column($command->to, $column->getType(), self::getWritableColumnOptions($column))], - $tableDiff->getAddedIndexes(), - $tableDiff->getModifiedIndexes(), - $tableDiff->getDroppedIndexes(), - $tableDiff->getRenamedIndexes(), - $tableDiff->getAddedForeignKeys(), - $tableDiff->getModifiedColumns(), - $tableDiff->getDroppedForeignKeys(), - ); - } - - /** - * Get the writable column options. - * - * @param \Doctrine\DBAL\Schema\Column $column - * @return array - */ - private static function getWritableColumnOptions(Column $column) - { - return array_filter($column->toArray(), function (string $name) use ($column) { - return method_exists($column, 'set'.$name); - }, ARRAY_FILTER_USE_KEY); - } -} diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php old mode 100755 new mode 100644 index 744a9d691534..5d969c3eaf93 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -2,11 +2,12 @@ namespace Illuminate\Database\Schema\Grammars; -use Doctrine\DBAL\Schema\Index; -use Doctrine\DBAL\Schema\TableDiff; use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\ColumnDefinition; +use Illuminate\Database\Schema\ForeignKeyDefinition; +use Illuminate\Database\Schema\IndexDefinition; use Illuminate\Support\Arr; use Illuminate\Support\Fluent; use RuntimeException; @@ -18,7 +19,7 @@ class SQLiteGrammar extends Grammar * * @var string[] */ - protected $modifiers = ['Increment', 'Nullable', 'Default', 'VirtualAs', 'StoredAs']; + protected $modifiers = ['Increment', 'Nullable', 'Default', 'Collate', 'VirtualAs', 'StoredAs']; /** * The columns available as serials. @@ -28,15 +29,18 @@ class SQLiteGrammar extends Grammar protected $serials = ['bigInteger', 'integer', 'mediumInteger', 'smallInteger', 'tinyInteger']; /** - * Compile the query to determine if a table exists. - * - * @deprecated Will be removed in a future Laravel version. + * Compile the query to determine the SQL text that describes the given object. * + * @param string $name + * @param string $type * @return string */ - public function compileTableExists() + public function compileSqlCreateStatement($name, $type = 'table') { - return "select * from sqlite_master where type = 'table' and name = ?"; + return sprintf('select "sql" from sqlite_master where type = %s and name = %s', + $this->wrap($type), + $this->wrap(str_replace('.', '__', $name)) + ); } /** @@ -76,43 +80,6 @@ public function compileViews() return "select name, sql as definition from sqlite_master where type = 'view' order by name"; } - /** - * Compile the SQL needed to retrieve all table names. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileGetAllTables() - { - return 'select type, name from sqlite_master where type = \'table\' and name not like \'sqlite_%\''; - } - - /** - * Compile the SQL needed to retrieve all view names. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileGetAllViews() - { - return 'select type, name from sqlite_master where type = \'view\''; - } - - /** - * Compile the query to determine the list of columns. - * - * @deprecated Will be removed in a future Laravel version. - * - * @param string $table - * @return string - */ - public function compileColumnListing($table) - { - return 'pragma table_info('.$this->wrap(str_replace('.', '__', $table)).')'; - } - /** * Compile the query to determine the columns. * @@ -122,8 +89,8 @@ public function compileColumnListing($table) public function compileColumns($table) { return sprintf( - "select name, type, not 'notnull' as 'nullable', dflt_value as 'default', pk as 'primary' " - .'from pragma_table_info(%s) order by cid asc', + 'select name, type, not "notnull" as "nullable", dflt_value as "default", pk as "primary" ' + .'from pragma_table_xinfo(%s) order by cid asc', $this->wrap(str_replace('.', '__', $table)) ); } @@ -177,39 +144,24 @@ public function compileCreate(Blueprint $blueprint, Fluent $command) $blueprint->temporary ? 'create temporary' : 'create', $this->wrapTable($blueprint), implode(', ', $this->getColumns($blueprint)), - (string) $this->addForeignKeys($blueprint), - (string) $this->addPrimaryKeys($blueprint) + $this->addForeignKeys($this->getCommandsByName($blueprint, 'foreign')), + $this->addPrimaryKeys($this->getCommandByName($blueprint, 'primary')) ); } /** * Get the foreign key syntax for a table creation statement. * - * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Database\Schema\ForeignKeyDefinition[] $foreignKeys * @return string|null */ - protected function addForeignKeys(Blueprint $blueprint) + protected function addForeignKeys($foreignKeys) { - $foreigns = $this->getCommandsByName($blueprint, 'foreign'); - - return collect($foreigns)->reduce(function ($sql, $foreign) { + return collect($foreignKeys)->reduce(function ($sql, $foreign) { // Once we have all the foreign key commands for the table creation statement // we'll loop through each of them and add them to the create table SQL we // are building, since SQLite needs foreign keys on the tables creation. - $sql .= $this->getForeignKey($foreign); - - if (! is_null($foreign->onDelete)) { - $sql .= " on delete {$foreign->onDelete}"; - } - - // If this foreign key specifies the action to be taken on update we will add - // that to the statement here. We'll append it to this SQL and then return - // the SQL so we can keep adding any other foreign constraints onto this. - if (! is_null($foreign->onUpdate)) { - $sql .= " on update {$foreign->onUpdate}"; - } - - return $sql; + return $sql.$this->getForeignKey($foreign); }, ''); } @@ -224,22 +176,35 @@ protected function getForeignKey($foreign) // We need to columnize the columns that the foreign key is being defined for // so that it is a properly formatted list. Once we have done this, we can // return the foreign key SQL declaration to the calling method for use. - return sprintf(', foreign key(%s) references %s(%s)', + $sql = sprintf(', foreign key(%s) references %s(%s)', $this->columnize($foreign->columns), $this->wrapTable($foreign->on), $this->columnize((array) $foreign->references) ); + + if (! is_null($foreign->onDelete)) { + $sql .= " on delete {$foreign->onDelete}"; + } + + // If this foreign key specifies the action to be taken on update we will add + // that to the statement here. We'll append it to this SQL and then return + // this SQL so we can keep adding any other foreign constraints to this. + if (! is_null($foreign->onUpdate)) { + $sql .= " on update {$foreign->onUpdate}"; + } + + return $sql; } /** * Get the primary key syntax for a table creation statement. * - * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent|null $primary * @return string|null */ - protected function addPrimaryKeys(Blueprint $blueprint) + protected function addPrimaryKeys($primary) { - if (! is_null($primary = $this->getCommandByName($blueprint, 'primary'))) { + if (! is_null($primary)) { return ", primary key ({$this->columnize($primary->columns)})"; } } @@ -255,30 +220,99 @@ public function compileAdd(Blueprint $blueprint, Fluent $command) { $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); - return collect($columns)->reject(function ($column) { - return preg_match('/as \(.*\) stored/', $column) > 0; - })->map(function ($column) use ($blueprint) { + return collect($columns)->map(function ($column) use ($blueprint) { return 'alter table '.$this->wrapTable($blueprint).' '.$column; })->all(); } /** - * Compile a rename column command. + * Compile a change column command into a series of SQL statements. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string + * + * @throws \RuntimeException */ - public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) - { - return $connection->usingNativeSchemaOperations() - ? sprintf('alter table %s rename column %s to %s', - $this->wrapTable($blueprint), - $this->wrap($command->from), - $this->wrap($command->to) - ) - : parent::compileRenameColumn($blueprint, $command, $connection); + public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + { + $schema = $connection->getSchemaBuilder(); + $table = $blueprint->getTable(); + + $changedColumns = collect($blueprint->getChangedColumns()); + $columnNames = []; + $autoIncrementColumn = null; + + $columns = collect($schema->getColumns($table)) + ->map(function ($column) use ($blueprint, $changedColumns, &$columnNames, &$autoIncrementColumn) { + $column = $changedColumns->first(fn ($col) => $col->name === $column['name'], $column); + + if ($column instanceof Fluent) { + $name = $this->wrap($column); + $columnNames[] = $name; + $autoIncrementColumn = $column->autoIncrement ? $column->name : $autoIncrementColumn; + + return $this->addModifiers($name.' '.$this->getType($column), $blueprint, $column); + } else { + $name = $this->wrap($column['name']); + $columnNames[] = $name; + $autoIncrementColumn = $column['auto_increment'] ? $column['name'] : $autoIncrementColumn; + + return $this->addModifiers($name.' '.$column['type'], $blueprint, + new ColumnDefinition([ + 'change' => true, + 'type' => $column['type_name'], + 'nullable' => $column['nullable'], + 'default' => $column['default'] ? new Expression($column['default']) : null, + 'autoIncrement' => $column['auto_increment'], + 'collation' => $column['collation'], + 'comment' => $column['comment'], + ]) + ); + } + })->all(); + + $foreignKeys = collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => new ForeignKeyDefinition([ + 'columns' => $foreignKey['columns'], + 'on' => $foreignKey['foreign_table'], + 'references' => $foreignKey['foreign_columns'], + 'onUpdate' => $foreignKey['on_update'], + 'onDelete' => $foreignKey['on_delete'], + ]))->all(); + + [$primary, $indexes] = collect($schema->getIndexes($table))->map(fn ($index) => new IndexDefinition([ + 'name' => match (true) { + $index['primary'] => 'primary', + $index['unique'] => 'unique', + default => 'index', + }, + 'index' => $index['name'], + 'columns' => $index['columns'], + ]))->partition(fn ($index) => $index->name === 'primary'); + + $indexes = collect($indexes)->reject(fn ($index) => str_starts_with('sqlite_', $index->index))->map( + fn ($index) => $this->{'compile'.ucfirst($index->name)}($blueprint, $index) + )->all(); + + $tempTable = $this->wrap('__temp__'.$blueprint->getPrefix().$table); + $table = $this->wrapTable($blueprint); + $columnNames = implode(', ', $columnNames); + + $foreignKeyConstraintsEnabled = $connection->scalar('pragma foreign_keys'); + + return array_filter(array_merge([ + $foreignKeyConstraintsEnabled ? $this->compileDisableForeignKeyConstraints() : null, + sprintf('create table %s (%s%s%s)', + $tempTable, + implode(', ', $columns), + $this->addForeignKeys($foreignKeys), + $autoIncrementColumn ? '' : $this->addPrimaryKeys($primary->first()) + ), + sprintf('insert into %s (%s) select %s from %s', $tempTable, $columnNames, $columnNames, $table), + sprintf('drop table %s', $table), + sprintf('alter table %s rename to %s', $tempTable, $table), + ], $indexes, [$foreignKeyConstraintsEnabled ? $this->compileEnableForeignKeyConstraints() : null])); } /** @@ -403,45 +437,11 @@ public function compileRebuild() */ public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { - if ($connection->usingNativeSchemaOperations()) { - $table = $this->wrapTable($blueprint); - - $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); + $table = $this->wrapTable($blueprint); - return collect($columns)->map(fn ($column) => 'alter table '.$table.' '.$column - )->all(); - } else { - $tableDiff = $this->getDoctrineTableDiff( - $blueprint, $schema = $connection->getDoctrineSchemaManager() - ); + $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); - $droppedColumns = []; - - foreach ($command->columns as $name) { - $droppedColumns[$name] = $connection->getDoctrineColumn( - $this->getTablePrefix().$blueprint->getTable(), $name - ); - } - - $platform = $connection->getDoctrineConnection()->getDatabasePlatform(); - - return (array) $platform->getAlterTableSQL( - new TableDiff( - $tableDiff->getOldTable(), - $tableDiff->getAddedColumns(), - $tableDiff->getModifiedColumns(), - $droppedColumns, - $tableDiff->getRenamedColumns(), - $tableDiff->getAddedIndexes(), - $tableDiff->getModifiedIndexes(), - $tableDiff->getDroppedIndexes(), - $tableDiff->getRenamedIndexes(), - $tableDiff->getAddedForeignKeys(), - $tableDiff->getModifiedColumns(), - $tableDiff->getDroppedForeignKeys(), - ) - ); - } + return collect($columns)->map(fn ($column) => 'alter table '.$table.' '.$column)->all(); } /** @@ -512,26 +512,32 @@ public function compileRename(Blueprint $blueprint, Fluent $command) */ public function compileRenameIndex(Blueprint $blueprint, Fluent $command, Connection $connection) { - $schemaManager = $connection->getDoctrineSchemaManager(); - - $indexes = $schemaManager->listTableIndexes($this->getTablePrefix().$blueprint->getTable()); + $indexes = $connection->getSchemaBuilder()->getIndexes($blueprint->getTable()); - $index = Arr::get($indexes, $command->from); + $index = Arr::first($indexes, fn ($index) => $index['name'] === $command->from); if (! $index) { throw new RuntimeException("Index [{$command->from}] does not exist."); } - $newIndex = new Index( - $command->to, $index->getColumns(), $index->isUnique(), - $index->isPrimary(), $index->getFlags(), $index->getOptions() - ); + if ($index['primary']) { + throw new RuntimeException('SQLite does not support altering primary keys.'); + } - $platform = $connection->getDoctrineConnection()->getDatabasePlatform(); + if ($index['unique']) { + return [ + $this->compileDropUnique($blueprint, new IndexDefinition(['index' => $index['name']])), + $this->compileUnique($blueprint, + new IndexDefinition(['index' => $command->to, 'columns' => $index['columns']]) + ), + ]; + } return [ - $platform->getDropIndexSQL($command->from, $this->getTablePrefix().$blueprint->getTable()), - $platform->getCreateIndexSQL($newIndex, $this->getTablePrefix().$blueprint->getTable()), + $this->compileDropIndex($blueprint, new IndexDefinition(['index' => $index['name']])), + $this->compileIndex($blueprint, + new IndexDefinition(['index' => $command->to, 'columns' => $index['columns']]) + ), ]; } @@ -923,86 +929,20 @@ protected function typeMacAddress(Fluent $column) * @param \Illuminate\Support\Fluent $column * @return string */ - public function typeGeometry(Fluent $column) + protected function typeGeometry(Fluent $column) { return 'geometry'; } /** - * Create the column definition for a spatial Point type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typePoint(Fluent $column) - { - return 'point'; - } - - /** - * Create the column definition for a spatial LineString type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeLineString(Fluent $column) - { - return 'linestring'; - } - - /** - * Create the column definition for a spatial Polygon type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typePolygon(Fluent $column) - { - return 'polygon'; - } - - /** - * Create the column definition for a spatial GeometryCollection type. + * Create the column definition for a spatial Geography type. * * @param \Illuminate\Support\Fluent $column * @return string */ - public function typeGeometryCollection(Fluent $column) + protected function typeGeography(Fluent $column) { - return 'geometrycollection'; - } - - /** - * Create the column definition for a spatial MultiPoint type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeMultiPoint(Fluent $column) - { - return 'multipoint'; - } - - /** - * Create the column definition for a spatial MultiLineString type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeMultiLineString(Fluent $column) - { - return 'multilinestring'; - } - - /** - * Create the column definition for a spatial MultiPolygon type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeMultiPolygon(Fluent $column) - { - return 'multipolygon'; + return $this->typeGeometry($column); } /** @@ -1111,6 +1051,20 @@ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) } } + /** + * Get the SQL for a collation column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyCollate(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->collation)) { + return " collate '{$column->collation}'"; + } + } + /** * Wrap the given JSON selector. * diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index dd56619c74d9..b719f127f3dc 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -66,18 +66,6 @@ public function compileDropDatabaseIfExists($name) ); } - /** - * Compile the query to determine if a table exists. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileTableExists() - { - return "select * from sys.sysobjects where id = object_id(?) and xtype in ('U', 'V')"; - } - /** * Compile the query to determine the tables. * @@ -85,7 +73,7 @@ public function compileTableExists() */ public function compileTables() { - return 'select t.name as name, SCHEMA_NAME(t.schema_id) as [schema], sum(u.total_pages) * 8 * 1024 as size ' + return 'select t.name as name, schema_name(t.schema_id) as [schema], sum(u.total_pages) * 8 * 1024 as size ' .'from sys.tables as t ' .'join sys.partitions as p on p.object_id = t.object_id ' .'join sys.allocation_units as u on u.container_id = p.hobt_id ' @@ -100,55 +88,19 @@ public function compileTables() */ public function compileViews() { - return 'select name, SCHEMA_NAME(v.schema_id) as [schema], definition from sys.views as v ' + return 'select name, schema_name(v.schema_id) as [schema], definition from sys.views as v ' .'inner join sys.sql_modules as m on v.object_id = m.object_id ' .'order by name'; } - /** - * Compile the SQL needed to retrieve all table names. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileGetAllTables() - { - return "select name, type from sys.tables where type = 'U'"; - } - - /** - * Compile the SQL needed to retrieve all view names. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return string - */ - public function compileGetAllViews() - { - return "select name, type from sys.objects where type = 'V'"; - } - - /** - * Compile the query to determine the list of columns. - * - * @deprecated Will be removed in a future Laravel version. - * - * @param string $table - * @return string - */ - public function compileColumnListing($table) - { - return "select name from sys.columns where object_id = object_id('$table')"; - } - /** * Compile the query to determine the columns. * + * @param string $schema * @param string $table * @return string */ - public function compileColumns($table) + public function compileColumns($schema, $table) { return sprintf( 'select col.name, type.name as type_name, ' @@ -162,18 +114,21 @@ 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 = %s " + .'order by col.column_id', $this->quoteString($table), + $schema ? $this->quoteString($schema) : 'schema_name()', ); } /** * Compile the query to determine the indexes. * + * @param string $schema * @param string $table * @return string */ - public function compileIndexes($table) + public function compileIndexes($schema, $table) { return sprintf( "select idx.name as name, string_agg(col.name, ',') within group (order by idxcol.key_ordinal) as columns, " @@ -183,19 +138,21 @@ public function compileIndexes($table) .'join sys.schemas as scm on tbl.schema_id = scm.schema_id ' .'join sys.index_columns as idxcol on idx.object_id = idxcol.object_id and idx.index_id = idxcol.index_id ' .'join sys.columns as col on idxcol.object_id = col.object_id and idxcol.column_id = col.column_id ' - .'where tbl.name = %s and scm.name = SCHEMA_NAME() ' + .'where tbl.name = %s and scm.name = %s ' .'group by idx.name, idx.type_desc, idx.is_unique, idx.is_primary_key', $this->quoteString($table), + $schema ? $this->quoteString($schema) : 'schema_name()', ); } /** * Compile the query to determine the foreign keys. * + * @param string $schema * @param string $table * @return string */ - public function compileForeignKeys($table) + public function compileForeignKeys($schema, $table) { return sprintf( 'select fk.name as name, ' @@ -212,9 +169,10 @@ public function compileForeignKeys($table) .'join sys.tables as ft on ft.object_id = fk.referenced_object_id ' .'join sys.schemas as fs on ft.schema_id = fs.schema_id ' .'join sys.columns as fc on fkc.referenced_object_id = fc.object_id and fkc.referenced_column_id = fc.column_id ' - .'where lt.name = %s and ls.name = SCHEMA_NAME() ' + .'where lt.name = %s and ls.name = %s ' .'group by fk.name, fs.name, ft.name, fk.update_referential_action_desc, fk.delete_referential_action_desc', - $this->quoteString($table) + $this->quoteString($table), + $schema ? $this->quoteString($schema) : 'schema_name()', ); } @@ -257,12 +215,10 @@ public function compileAdd(Blueprint $blueprint, Fluent $command) */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { - return $connection->usingNativeSchemaOperations() - ? sprintf("sp_rename '%s', %s, 'COLUMN'", - $this->wrap($blueprint->getTable().'.'.$command->from), - $this->wrap($command->to) - ) - : parent::compileRenameColumn($blueprint, $command, $connection); + return sprintf("sp_rename %s, %s, N'COLUMN'", + $this->quoteString($this->wrapTable($blueprint).'.'.$this->wrap($command->from)), + $this->wrap($command->to) + ); } /** @@ -277,10 +233,6 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - if (! $connection->usingNativeSchemaOperations()) { - return parent::compileChange($blueprint, $command, $connection); - } - $changes = [$this->compileDropDefaultConstraint($blueprint, $command)]; foreach ($blueprint->getChangedColumns() as $column) { @@ -405,8 +357,8 @@ public function compileDrop(Blueprint $blueprint, Fluent $command) */ public function compileDropIfExists(Blueprint $blueprint, Fluent $command) { - return sprintf('if exists (select * from sys.sysobjects where id = object_id(%s, \'U\')) drop table %s', - "'".str_replace("'", "''", $this->getTablePrefix().$blueprint->getTable())."'", + return sprintf('if object_id(%s, \'U\') is not null drop table %s', + $this->quoteString($this->wrapTable($blueprint)), $this->wrapTable($blueprint) ); } @@ -450,12 +402,13 @@ public function compileDropDefaultConstraint(Blueprint $blueprint, Fluent $comma ? "'".collect($blueprint->getChangedColumns())->pluck('name')->implode("','")."'" : "'".implode("','", $command->columns)."'"; - $tableName = $this->getTablePrefix().$blueprint->getTable(); + $table = $this->wrapTable($blueprint); + $tableName = $this->quoteString($this->wrapTable($blueprint)); $sql = "DECLARE @sql NVARCHAR(MAX) = '';"; - $sql .= "SELECT @sql += 'ALTER TABLE [dbo].[{$tableName}] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' "; + $sql .= "SELECT @sql += 'ALTER TABLE $table DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' "; $sql .= 'FROM sys.columns '; - $sql .= "WHERE [object_id] = OBJECT_ID('[dbo].[{$tableName}]') AND [name] in ({$columns}) AND [default_object_id] <> 0;"; + $sql .= "WHERE [object_id] = OBJECT_ID($tableName) AND [name] in ($columns) AND [default_object_id] <> 0;"; $sql .= 'EXEC(@sql)'; return $sql; @@ -538,9 +491,10 @@ public function compileDropForeign(Blueprint $blueprint, Fluent $command) */ public function compileRename(Blueprint $blueprint, Fluent $command) { - $from = $this->wrapTable($blueprint); - - return "sp_rename {$from}, ".$this->wrapTable($command->to); + return sprintf('sp_rename %s, %s', + $this->quoteString($this->wrapTable($blueprint)), + $this->wrapTable($command->to) + ); } /** @@ -552,8 +506,8 @@ public function compileRename(Blueprint $blueprint, Fluent $command) */ public function compileRenameIndex(Blueprint $blueprint, Fluent $command) { - return sprintf("sp_rename N'%s', %s, N'INDEX'", - $this->wrap($blueprint->getTable().'.'.$command->from), + return sprintf("sp_rename %s, %s, N'INDEX'", + $this->quoteString($this->wrapTable($blueprint).'.'.$this->wrap($command->from)), $this->wrap($command->to) ); } @@ -962,84 +916,18 @@ protected function typeMacAddress(Fluent $column) * @param \Illuminate\Support\Fluent $column * @return string */ - public function typeGeometry(Fluent $column) + protected function typeGeometry(Fluent $column) { - return 'geography'; - } - - /** - * Create the column definition for a spatial Point type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typePoint(Fluent $column) - { - return 'geography'; - } - - /** - * Create the column definition for a spatial LineString type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeLineString(Fluent $column) - { - return 'geography'; - } - - /** - * Create the column definition for a spatial Polygon type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typePolygon(Fluent $column) - { - return 'geography'; - } - - /** - * Create the column definition for a spatial GeometryCollection type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeGeometryCollection(Fluent $column) - { - return 'geography'; - } - - /** - * Create the column definition for a spatial MultiPoint type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeMultiPoint(Fluent $column) - { - return 'geography'; - } - - /** - * Create the column definition for a spatial MultiLineString type. - * - * @param \Illuminate\Support\Fluent $column - * @return string - */ - public function typeMultiLineString(Fluent $column) - { - return 'geography'; + return 'geometry'; } /** - * Create the column definition for a spatial MultiPolygon type. + * Create the column definition for a spatial Geography type. * * @param \Illuminate\Support\Fluent $column * @return string */ - public function typeMultiPolygon(Fluent $column) + protected function typeGeography(Fluent $column) { return 'geography'; } @@ -1107,7 +995,7 @@ protected function modifyDefault(Blueprint $blueprint, Fluent $column) protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { if (! $column->change && in_array($column->type, $this->serials) && $column->autoIncrement) { - return ' identity primary key'; + return $this->hasCommand($blueprint, 'primary') ? ' identity' : ' identity primary key'; } } diff --git a/src/Illuminate/Database/Schema/MariaDbBuilder.php b/src/Illuminate/Database/Schema/MariaDbBuilder.php new file mode 100755 index 000000000000..012befe3802e --- /dev/null +++ b/src/Illuminate/Database/Schema/MariaDbBuilder.php @@ -0,0 +1,8 @@ +connectionString().' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc --column-statistics=0'; + + return $command.' "${:LARAVEL_LOAD_DATABASE}"'; + } +} diff --git a/src/Illuminate/Database/Schema/MySqlBuilder.php b/src/Illuminate/Database/Schema/MySqlBuilder.php index 0c537ba980cd..943ae9f4fadf 100755 --- a/src/Illuminate/Database/Schema/MySqlBuilder.php +++ b/src/Illuminate/Database/Schema/MySqlBuilder.php @@ -58,34 +58,6 @@ public function getViews() ); } - /** - * Get all of the table names for the database. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return array - */ - public function getAllTables() - { - return $this->connection->select( - $this->grammar->compileGetAllTables() - ); - } - - /** - * Get all of the view names for the database. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return array - */ - public function getAllViews() - { - return $this->connection->select( - $this->grammar->compileGetAllViews() - ); - } - /** * Get the columns for a given table. * diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index 9cf829721ad2..be6512bbf2f7 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -60,50 +60,36 @@ public function hasTable($table) } /** - * Get the user-defined types that belong to the database. + * Determine if the given view exists. * - * @return array + * @param string $view + * @return bool */ - public function getTypes() + public function hasView($view) { - return $this->connection->getPostProcessor()->processTypes( - $this->connection->selectFromWriteConnection($this->grammar->compileTypes()) - ); - } + [$schema, $view] = $this->parseSchemaAndTable($view); - /** - * Get all of the table names for the database. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return array - */ - public function getAllTables() - { - return $this->connection->select( - $this->grammar->compileGetAllTables( - $this->parseSearchPath( - $this->connection->getConfig('search_path') ?: $this->connection->getConfig('schema') - ) - ) - ); + $view = $this->connection->getTablePrefix().$view; + + foreach ($this->getViews() as $value) { + if (strtolower($view) === strtolower($value['name']) + && strtolower($schema) === strtolower($value['schema'])) { + return true; + } + } + + return false; } /** - * Get all of the view names for the database. - * - * @deprecated Will be removed in a future Laravel version. + * Get the user-defined types that belong to the database. * * @return array */ - public function getAllViews() + public function getTypes() { - return $this->connection->select( - $this->grammar->compileGetAllViews( - $this->parseSearchPath( - $this->connection->getConfig('search_path') ?: $this->connection->getConfig('schema') - ) - ) + return $this->connection->getPostProcessor()->processTypes( + $this->connection->selectFromWriteConnection($this->grammar->compileTypes()) ); } @@ -166,20 +152,6 @@ public function dropAllViews() ); } - /** - * Get all of the type names for the database. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return array - */ - public function getAllTypes() - { - return $this->connection->select( - $this->grammar->compileGetAllTypes() - ); - } - /** * Drop all types from the database. * @@ -289,7 +261,7 @@ protected function parseSchemaAndTable($reference) if (count($parts) > 2) { $database = $parts[0]; - throw new InvalidArgumentException("Using 3-parts reference is not supported, you may use `Schema::connection('$database')` instead."); + throw new InvalidArgumentException("Using three-part reference is not supported, you may use `Schema::connection('$database')` instead."); } // We will use the default schema unless the schema has been specified in the diff --git a/src/Illuminate/Database/Schema/SQLiteBuilder.php b/src/Illuminate/Database/Schema/SQLiteBuilder.php index 8ae272d767b6..ef19684172fa 100644 --- a/src/Illuminate/Database/Schema/SQLiteBuilder.php +++ b/src/Illuminate/Database/Schema/SQLiteBuilder.php @@ -52,30 +52,18 @@ public function getTables() } /** - * Get all of the table names for the database. - * - * @deprecated Will be removed in a future Laravel version. + * Get the columns for a given table. * + * @param string $table * @return array */ - public function getAllTables() + public function getColumns($table) { - return $this->connection->select( - $this->grammar->compileGetAllTables() - ); - } + $table = $this->connection->getTablePrefix().$table; - /** - * Get all of the view names for the database. - * - * @deprecated Will be removed in a future Laravel version. - * - * @return array - */ - public function getAllViews() - { - return $this->connection->select( - $this->grammar->compileGetAllViews() + return $this->connection->getPostProcessor()->processColumns( + $this->connection->selectFromWriteConnection($this->grammar->compileColumns($table)), + $this->connection->scalar($this->grammar->compileSqlCreateStatement($table)) ); } diff --git a/src/Illuminate/Database/Schema/SqlServerBuilder.php b/src/Illuminate/Database/Schema/SqlServerBuilder.php index e7717534f803..5f3edb6a4e41 100644 --- a/src/Illuminate/Database/Schema/SqlServerBuilder.php +++ b/src/Illuminate/Database/Schema/SqlServerBuilder.php @@ -30,6 +30,50 @@ public function dropDatabaseIfExists($name) ); } + /** + * Determine if the given table exists. + * + * @param string $table + * @return bool + */ + public function hasTable($table) + { + [$schema, $table] = $this->parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix().$table; + + foreach ($this->getTables() as $value) { + if (strtolower($table) === strtolower($value['name']) + && strtolower($schema) === strtolower($value['schema'])) { + return true; + } + } + + return false; + } + + /** + * Determine if the given view exists. + * + * @param string $view + * @return bool + */ + public function hasView($view) + { + [$schema, $view] = $this->parseSchemaAndTable($view); + + $view = $this->connection->getTablePrefix().$view; + + foreach ($this->getViews() as $value) { + if (strtolower($view) === strtolower($value['name']) + && strtolower($schema) === strtolower($value['schema'])) { + return true; + } + } + + return false; + } + /** * Drop all tables from the database. * @@ -53,30 +97,74 @@ public function dropAllViews() } /** - * Drop all tables from the database. - * - * @deprecated Will be removed in a future Laravel version. + * Get the columns for a given table. * + * @param string $table * @return array */ - public function getAllTables() + public function getColumns($table) { - return $this->connection->select( - $this->grammar->compileGetAllTables() + [$schema, $table] = $this->parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix().$table; + + $results = $this->connection->selectFromWriteConnection( + $this->grammar->compileColumns($schema, $table) ); + + return $this->connection->getPostProcessor()->processColumns($results); } /** - * Get all of the view names for the database. + * Get the indexes for a given table. * - * @deprecated Will be removed in a future Laravel version. + * @param string $table + * @return array + */ + public function getIndexes($table) + { + [$schema, $table] = $this->parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processIndexes( + $this->connection->selectFromWriteConnection($this->grammar->compileIndexes($schema, $table)) + ); + } + + /** + * Get the foreign keys for a given table. * + * @param string $table * @return array */ - public function getAllViews() + public function getForeignKeys($table) { - return $this->connection->select( - $this->grammar->compileGetAllViews() + [$schema, $table] = $this->parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processForeignKeys( + $this->connection->selectFromWriteConnection($this->grammar->compileForeignKeys($schema, $table)) ); } + + /** + * Parse the database object reference and extract the schema and table. + * + * @param string $reference + * @return array + */ + protected function parseSchemaAndTable($reference) + { + $parts = array_pad(explode('.', $reference, 2), -2, 'dbo'); + + if (str_contains($parts[1], '.')) { + $database = $parts[0]; + + throw new InvalidArgumentException("Using three-part reference is not supported, you may use `Schema::connection('$database')` instead."); + } + + return $parts; + } } diff --git a/src/Illuminate/Database/SqlServerConnection.php b/src/Illuminate/Database/SqlServerConnection.php index e376e6fa6c38..f977df57cc68 100755 --- a/src/Illuminate/Database/SqlServerConnection.php +++ b/src/Illuminate/Database/SqlServerConnection.php @@ -4,7 +4,6 @@ use Closure; use Exception; -use Illuminate\Database\PDO\SqlServerDriver; use Illuminate\Database\Query\Grammars\SqlServerGrammar as QueryGrammar; use Illuminate\Database\Query\Processors\SqlServerProcessor; use Illuminate\Database\Schema\Grammars\SqlServerGrammar as SchemaGrammar; @@ -139,14 +138,4 @@ protected function getDefaultPostProcessor() { return new SqlServerProcessor; } - - /** - * Get the Doctrine DBAL driver. - * - * @return \Illuminate\Database\PDO\SqlServerDriver - */ - protected function getDoctrineDriver() - { - return new SqlServerDriver; - } } diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index c840493bd621..bcbb837c0818 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -17,7 +17,7 @@ "require": { "php": "^8.2", "ext-pdo": "*", - "brick/math": "^0.9.3|^0.10.2|^0.11", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "illuminate/collections": "^11.0", "illuminate/container": "^11.0", "illuminate/contracts": "^11.0", @@ -36,7 +36,6 @@ }, "suggest": { "ext-filter": "Required to use the Postgres database driver.", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^4.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.21).", "illuminate/console": "Required to use the database commands (^11.0).", "illuminate/events": "Required to use the observers with Eloquent (^11.0).", diff --git a/src/Illuminate/Encryption/Encrypter.php b/src/Illuminate/Encryption/Encrypter.php index 8a8c6d85b0fc..003bbfbffc87 100755 --- a/src/Illuminate/Encryption/Encrypter.php +++ b/src/Illuminate/Encryption/Encrypter.php @@ -17,6 +17,13 @@ class Encrypter implements EncrypterContract, StringEncrypter */ protected $key; + /** + * The previous / legacy encryption keys. + * + * @var array + */ + protected $previousKeys = []; + /** * The algorithm used for encryption. * @@ -113,7 +120,7 @@ public function encrypt($value, $serialize = true) $mac = self::$supportedCiphers[strtolower($this->cipher)]['aead'] ? '' // For AEAD-algorithms, the tag / MAC is returned by openssl_encrypt... - : $this->hash($iv, $value); + : $this->hash($iv, $value, $this->key); $json = json_encode(compact('iv', 'value', 'mac', 'tag'), JSON_UNESCAPED_SLASHES); @@ -159,9 +166,15 @@ public function decrypt($payload, $unserialize = true) // Here we will decrypt the value. If we are able to successfully decrypt it // we will then unserialize it and return it out to the caller. If we are // unable to decrypt this value we will throw out an exception message. - $decrypted = \openssl_decrypt( - $payload['value'], strtolower($this->cipher), $this->key, 0, $iv, $tag ?? '' - ); + foreach ($this->getAllKeys() as $key) { + $decrypted = \openssl_decrypt( + $payload['value'], strtolower($this->cipher), $key, 0, $iv, $tag ?? '' + ); + + if ($decrypted !== false) { + break; + } + } if ($decrypted === false) { throw new DecryptException('Could not decrypt the data.'); @@ -188,11 +201,12 @@ public function decryptString($payload) * * @param string $iv * @param mixed $value + * @param string $key * @return string */ - protected function hash($iv, $value) + protected function hash($iv, $value, $key) { - return hash_hmac('sha256', $iv.$value, $this->key); + return hash_hmac('sha256', $iv.$value, $key); } /** @@ -258,9 +272,17 @@ protected function validPayload($payload) */ protected function validMac(array $payload) { - return hash_equals( - $this->hash($payload['iv'], $payload['value']), $payload['mac'] - ); + foreach ($this->getAllKeys() as $key) { + $valid = hash_equals( + $this->hash($payload['iv'], $payload['value'], $key), $payload['mac'] + ); + + if ($valid === true) { + return true; + } + } + + return false; } /** @@ -289,4 +311,45 @@ public function getKey() { return $this->key; } + + /** + * Get the current encryption key and all previous encryption keys. + * + * @return array + */ + public function getAllKeys() + { + return [$this->key, ...$this->previousKeys]; + } + + /** + * Get the previous encryption keys. + * + * @return array + */ + public function getPreviousKeys() + { + return $this->previousKeys; + } + + /** + * Set the previous / legacy encryption keys that should be utilized if decryption fails. + * + * @param array $key + * @return $this + */ + public function previousKeys(array $keys) + { + foreach ($keys as $key) { + if (! static::supported($key, $this->cipher)) { + $ciphers = implode(', ', array_keys(self::$supportedCiphers)); + + throw new RuntimeException("Unsupported cipher or incorrect key length. Supported ciphers are: {$ciphers}."); + } + } + + $this->previousKeys = $keys; + + return $this; + } } diff --git a/src/Illuminate/Encryption/EncryptionServiceProvider.php b/src/Illuminate/Encryption/EncryptionServiceProvider.php index a4d27a3720b9..5307042792af 100755 --- a/src/Illuminate/Encryption/EncryptionServiceProvider.php +++ b/src/Illuminate/Encryption/EncryptionServiceProvider.php @@ -29,7 +29,11 @@ protected function registerEncrypter() $this->app->singleton('encrypter', function ($app) { $config = $app->make('config')->get('app'); - return new Encrypter($this->parseKey($config), $config['cipher']); + return (new Encrypter($this->parseKey($config), $config['cipher'])) + ->previousKeys(array_map( + fn ($key) => $this->parseKey(['key' => $key]), + $config['previous_keys'] ?? [] + )); }); } diff --git a/src/Illuminate/Filesystem/Filesystem.php b/src/Illuminate/Filesystem/Filesystem.php index 23fc17eeb03c..c9485337c299 100644 --- a/src/Illuminate/Filesystem/Filesystem.php +++ b/src/Illuminate/Filesystem/Filesystem.php @@ -546,7 +546,7 @@ public function hasSameHash($firstFile, $secondFile) { $hash = @md5_file($firstFile); - return $hash && $hash === @md5_file($secondFile); + return $hash && hash_equals($hash, (string) @md5_file($secondFile)); } /** diff --git a/src/Illuminate/Filesystem/FilesystemAdapter.php b/src/Illuminate/Filesystem/FilesystemAdapter.php index a8e260d246d7..088135358fcf 100644 --- a/src/Illuminate/Filesystem/FilesystemAdapter.php +++ b/src/Illuminate/Filesystem/FilesystemAdapter.php @@ -237,7 +237,7 @@ public function directoryMissing($path) } /** - * Get the full path for the file at the given "short" path. + * Get the full path to the file that exists at the given relative path. * * @param string $path * @return string diff --git a/src/Illuminate/Filesystem/LockableFile.php b/src/Illuminate/Filesystem/LockableFile.php index 8b2de765eaad..d354b884036a 100644 --- a/src/Illuminate/Filesystem/LockableFile.php +++ b/src/Illuminate/Filesystem/LockableFile.php @@ -2,7 +2,6 @@ namespace Illuminate\Filesystem; -use Exception; use Illuminate\Contracts\Filesystem\LockTimeoutException; class LockableFile @@ -67,11 +66,7 @@ protected function ensureDirectoryExists($path) */ protected function createResource($path, $mode) { - $this->handle = @fopen($path, $mode); - - if (! $this->handle) { - throw new Exception('Unable to create lockable file: '.$path.'. Please ensure you have permission to create files in this location.'); - } + $this->handle = fopen($path, $mode); } /** 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..af39716f6e4f --- /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..a6b471f39409 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -3,6 +3,7 @@ namespace Illuminate\Foundation; use Closure; +use Composer\Autoload\ClassLoader; use Illuminate\Container\Container; use Illuminate\Contracts\Console\Kernel as ConsoleKernelContract; use Illuminate\Contracts\Foundation\Application as ApplicationContract; @@ -32,6 +33,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; @@ -217,19 +220,34 @@ public function __construct($basePath = null) /** * Begin configuring a new Laravel application instance. * - * @param string|null $baseDirectory - * @return \Illuminate\Foundation\ApplicationBuilder + * @param string|null $basePath + * @return \Illuminate\Foundation\Configuration\ApplicationBuilder */ - public static function configure(string $baseDirectory = null) + public static function configure(string $basePath = null) { - $baseDirectory = $ENV['APP_BASE_PATH'] ?? ($baseDirectory ?: dirname(dirname( - debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'] - ))); + $basePath = match (true) { + is_string($basePath) => $basePath, + default => static::inferBasePath(), + }; - return (new Configuration\ApplicationBuilder(new static($baseDirectory))) + return (new Configuration\ApplicationBuilder(new static($basePath))) ->withKernels() ->withEvents() - ->withCommands(); + ->withCommands() + ->withProviders(); + } + + /** + * Infer the application's base directory from the environment. + * + * @return string + */ + public static function inferBasePath() + { + return match (true) { + isset($_ENV['APP_BASE_PATH']) => $_ENV['APP_BASE_PATH'], + default => dirname(array_keys(ClassLoader::getRegisteredLoaders())[0]), + }; } /** @@ -624,7 +642,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/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index ee890cff2705..90bc446dca4e 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -7,7 +7,9 @@ use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\LogManager; +use Illuminate\Support\Env; use Monolog\Handler\NullHandler; +use PHPUnit\Runner\ErrorHandler; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\ErrorHandler\Error\FatalError; use Throwable; @@ -36,7 +38,7 @@ class HandleExceptions */ public function bootstrap(Application $app) { - self::$reservedMemory = str_repeat('x', 32768); + static::$reservedMemory = str_repeat('x', 32768); static::$app = $app; @@ -118,7 +120,7 @@ protected function shouldIgnoreDeprecationErrors() { return ! class_exists(LogManager::class) || ! static::$app->hasBeenBootstrapped() - || static::$app->runningUnitTests(); + || (static::$app->runningUnitTests() && ! Env::get('LOG_DEPRECATIONS_WHILE_TESTING')); } /** @@ -176,7 +178,7 @@ protected function ensureNullLogDriverIsConfigured() */ public function handleException(Throwable $e) { - self::$reservedMemory = null; + static::$reservedMemory = null; try { $this->getExceptionHandler()->report($e); @@ -224,7 +226,7 @@ protected function renderHttpResponse(Throwable $e) */ public function handleShutdown() { - self::$reservedMemory = null; + static::$reservedMemory = null; if (! is_null($error = error_get_last()) && $this->isFatal($error['type'])) { $this->handleException($this->fatalErrorFromPhpError($error, 0)); @@ -291,9 +293,70 @@ protected function getExceptionHandler() * Clear the local application instance from memory. * * @return void + * + * @deprecated This method will be removed in a future Laravel version. */ public static function forgetApp() { static::$app = null; } + + /** + * Flush the bootstrapper's global state. + * + * @return void + */ + public static function flushState() + { + if (is_null(static::$app)) { + return; + } + + static::flushHandlersState(); + + static::$app = null; + + static::$reservedMemory = null; + } + + /** + * Flush the bootstrapper's global handlers state. + * + * @return void + */ + public static function flushHandlersState() + { + while (true) { + $previousHandler = set_exception_handler(static fn () => null); + + restore_exception_handler(); + + if ($previousHandler === null) { + break; + } + + restore_exception_handler(); + } + + while (true) { + $previousHandler = set_error_handler(static fn () => null); + + restore_error_handler(); + + if ($previousHandler === null) { + break; + } + + restore_error_handler(); + } + + if (class_exists(ErrorHandler::class)) { + $instance = ErrorHandler::instance(); + + if ((fn () => $this->enabled ?? false)->call($instance)) { + $instance->disable(); + $instance->enable(); + } + } + } } diff --git a/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php b/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php index ddad0ffabcc5..700651913caf 100644 --- a/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php +++ b/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php @@ -79,4 +79,16 @@ public static function merge(array $providers, ?string $bootstrapProviderPath = array_merge(static::$merge, $providers) ))); } + + /** + * Flush the bootstrapper's global state. + * + * @return void + */ + public static function flushState() + { + static::$bootstrapProviderPath = null; + + static::$merge = []; + } } diff --git a/src/Illuminate/Foundation/Bus/PendingChain.php b/src/Illuminate/Foundation/Bus/PendingChain.php index 8d3c6892615a..2fb14990c56a 100644 --- a/src/Illuminate/Foundation/Bus/PendingChain.php +++ b/src/Illuminate/Foundation/Bus/PendingChain.php @@ -132,7 +132,7 @@ public function catchCallbacks() } /** - * Dispatch the job with the given arguments. + * Dispatch the job chain. * * @return \Illuminate\Foundation\Bus\PendingDispatch */ @@ -165,4 +165,26 @@ public function dispatch() return app(Dispatcher::class)->dispatch($firstJob); } + + /** + * Dispatch the job chain if the given truth test passes. + * + * @param bool|\Closure $boolean + * @return \Illuminate\Foundation\Bus\PendingDispatch|null + */ + public function dispatchIf($boolean) + { + return value($boolean) ? $this->dispatch() : null; + } + + /** + * Dispatch the job chain unless the given truth test passes. + * + * @param bool|\Closure $boolean + * @return \Illuminate\Foundation\Bus\PendingDispatch|null + */ + public function dispatchUnless($boolean) + { + return ! value($boolean) ? $this->dispatch() : null; + } } diff --git a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php index bfefaeda3bbe..4af83acda162 100644 --- a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php +++ b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php @@ -7,14 +7,24 @@ use Illuminate\Contracts\Http\Kernel as HttpKernel; use Illuminate\Foundation\Application; use Illuminate\Foundation\Bootstrap\RegisterProviders; +use Illuminate\Foundation\Events\DiagnosingHealth; use Illuminate\Foundation\Support\Providers\EventServiceProvider as AppEventServiceProvider; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as AppRouteServiceProvider; use Illuminate\Support\Facades\Broadcast; +use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Route; +use Illuminate\Support\Facades\View; use Laravel\Folio\Folio; class ApplicationBuilder { + /** + * The service provider that are marked for registration. + * + * @var array + */ + protected array $pendingProviders = []; + /** * The Folio / page middleware that have been defined by the user. * @@ -71,13 +81,22 @@ public function withProviders(array $providers = [], bool $withBootstrapProvider /** * Register the core event service provider for the application. * + * @param array $discover * @return $this */ - public function withEvents() + public function withEvents(array $discover = []) { - $this->app->booting(function () { - $this->app->register(AppEventServiceProvider::class); - }); + if (count($discover) > 0) { + AppEventServiceProvider::setEventDiscoveryPaths($discover); + } + + if (! isset($this->pendingProviders[AppEventServiceProvider::class])) { + $this->app->booting(function () { + $this->app->register(AppEventServiceProvider::class); + }); + } + + $this->pendingProviders[AppEventServiceProvider::class] = true; return $this; } @@ -120,17 +139,18 @@ public function withRouting(?Closure $using = null, ?string $commands = null, ?string $channels = null, ?string $pages = null, + ?string $health = null, string $apiPrefix = 'api', ?callable $then = null) { - if (is_null($using) && (is_string($web) || is_string($api))) { - $using = $this->buildRoutingCallback($web, $api, $pages, $apiPrefix, $then); + if (is_null($using) && (is_string($web) || is_string($api) || is_string($pages) || is_string($health)) || is_callable($then)) { + $using = $this->buildRoutingCallback($web, $api, $pages, $health, $apiPrefix, $then); } AppRouteServiceProvider::loadRoutesUsing($using); $this->app->booting(function () { - $this->app->register(AppRouteServiceProvider::class); + $this->app->register(AppRouteServiceProvider::class, force: true); }); if (is_string($commands) && realpath($commands) !== false) { @@ -150,6 +170,7 @@ public function withRouting(?Closure $using = null, * @param string|null $web * @param string|null $api * @param string|null $pages + * @param string|null $health * @param string $apiPrefix * @param callable|null $then * @return \Closure @@ -157,14 +178,23 @@ public function withRouting(?Closure $using = null, protected function buildRoutingCallback(?string $web, ?string $api, ?string $pages, + ?string $health, string $apiPrefix, ?callable $then) { - return function () use ($web, $api, $pages, $apiPrefix, $then) { + return function () use ($web, $api, $pages, $health, $apiPrefix, $then) { if (is_string($api) && realpath($api) !== false) { Route::middleware('api')->prefix($apiPrefix)->group($api); } + if (is_string($health)) { + Route::middleware('web')->get($health, function () { + Event::dispatch(new DiagnosingHealth); + + return View::file(__DIR__.'/../resources/health-up.blade.php'); + }); + } + if (is_string($web) && realpath($web) !== false) { Route::middleware('web')->group($web); } @@ -176,7 +206,7 @@ class_exists(Folio::class)) { } if (is_callable($then)) { - $then(); + $then($this->app); } }; } @@ -184,21 +214,27 @@ class_exists(Folio::class)) { /** * Register the global middleware, middleware groups, and middleware aliases for the application. * - * @param callable $callback + * @param callable|null $callback * @return $this */ - public function withMiddleware(callable $callback) + public function withMiddleware(?callable $callback = null) { $this->app->afterResolving(HttpKernel::class, function ($kernel) use ($callback) { $middleware = (new Middleware) - ->auth(redirectTo: fn () => route('login')); + ->redirectGuestsTo(fn () => route('login')); - $callback($middleware); + if (! is_null($callback)) { + $callback($middleware); + } $this->pageMiddleware = $middleware->getPageMiddleware(); $kernel->setGlobalMiddleware($middleware->getGlobalMiddleware()); $kernel->setMiddlewareGroups($middleware->getMiddlewareGroups()); $kernel->setMiddlewareAliases($middleware->getMiddlewareAliases()); + + if ($priorities = $middleware->getMiddlewarePriority()) { + $kernel->setMiddlewarePriority($priorities); + } }); return $this; @@ -298,6 +334,19 @@ public function withSingletons(array $singletons) }); } + /** + * Register a callback to be invoked when the application's service providers are registered. + * + * @param callable $callback + * @return $this + */ + public function registered(callable $callback) + { + $this->app->registered($callback); + + return $this; + } + /** * Register a callback to be invoked when the application is "booting". * diff --git a/src/Illuminate/Foundation/Configuration/Exceptions.php b/src/Illuminate/Foundation/Configuration/Exceptions.php index b38bd2a20278..499d35539b71 100644 --- a/src/Illuminate/Foundation/Configuration/Exceptions.php +++ b/src/Illuminate/Foundation/Configuration/Exceptions.php @@ -4,6 +4,7 @@ use Closure; use Illuminate\Foundation\Exceptions\Handler; +use Illuminate\Support\Arr; class Exceptions { @@ -77,7 +78,7 @@ public function map($from, $to = null) * @param \Psr\Log\LogLevel::* $level * @return $this */ - public function level($type, $level) + public function level(string $type, string $level) { $this->handler->level($type, $level); @@ -100,12 +101,14 @@ public function context(Closure $contextCallback) /** * Indicate that the given exception type should not be reported. * - * @param string $class + * @param array|string $class * @return $this */ - public function dontReport(string $class) + public function dontReport(array|string $class) { - $this->handler->dontReport($class); + foreach (Arr::wrap($class) as $exceptionClass) { + $this->handler->dontReport($exceptionClass); + } return $this; } @@ -128,10 +131,23 @@ public function dontReportDuplicates() * @param array|string $attributes * @return $this */ - public function dontFlash($attributes) + public function dontFlash(array|string $attributes) { $this->handler->dontFlash($attributes); return $this; } + + /** + * Indicate that the given exception class should not be ignored. + * + * @param array>|class-string<\Throwable> $class + * @return $this + */ + public function stopIgnoring(array|string $class) + { + $this->handler->stopIgnoring($class); + + return $this; + } } diff --git a/src/Illuminate/Foundation/Configuration/Middleware.php b/src/Illuminate/Foundation/Configuration/Middleware.php index 949dfe4b5806..ff4874c9ec22 100644 --- a/src/Illuminate/Foundation/Configuration/Middleware.php +++ b/src/Illuminate/Foundation/Configuration/Middleware.php @@ -2,9 +2,18 @@ namespace Illuminate\Foundation\Configuration; +use Closure; use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Middleware\Authenticate; use Illuminate\Auth\Middleware\RedirectIfAuthenticated; +use Illuminate\Cookie\Middleware\EncryptCookies; +use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; +use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance; +use Illuminate\Foundation\Http\Middleware\TrimStrings; +use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken; +use Illuminate\Http\Middleware\TrustHosts; +use Illuminate\Http\Middleware\TrustProxies; +use Illuminate\Routing\Middleware\ValidateSignature; use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\Support\Arr; @@ -15,7 +24,7 @@ class Middleware * * @var array */ - protected $global; + protected $global = []; /** * The middleware that should be prepended to the global middleware stack. @@ -111,29 +120,16 @@ class Middleware /** * Indicates if Redis throttling should be applied. * - * @var array + * @var bool */ protected $throttleWithRedis = false; /** - * The default middleware aliases. + * Indicates if sessions should be authenticated for the "web" middleware group. * - * @var array + * @var bool */ - protected $aliases = [ - 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, - 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, - 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, - 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, - 'can' => \Illuminate\Auth\Middleware\Authorize::class, - 'guest' => \Illuminate\Auth\Middleware\RedirectIfAuthenticated::class, - 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, - 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, - 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, - 'subscribed' => \Spark\Http\Middleware\VerifyBillableIsSubscribed::class, - 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, - ]; + protected $authenticatedSessions = false; /** * The custom middleware aliases. @@ -142,6 +138,13 @@ class Middleware */ protected $customAliases = []; + /** + * The custom middleware priority definition. + * + * @var array + */ + protected $priority = []; + /** * Prepend middleware to the application's global middleware stack. * @@ -258,8 +261,8 @@ public function prependToGroup(string $group, array|string $middleware) public function appendToGroup(string $group, array|string $middleware) { $this->groupAppends[$group] = array_merge( - Arr::wrap($middleware), - $this->groupAppends[$group] ?? [] + $this->groupAppends[$group] ?? [], + Arr::wrap($middleware) ); return $this; @@ -386,6 +389,19 @@ public function alias(array $aliases) return $this; } + /** + * Define the middleware priority for the application. + * + * @param array $priority + * @return $this + */ + public function priority(array $priority) + { + $this->priority = $priority; + + return $this; + } + /** * Get the global middleware. * @@ -425,14 +441,15 @@ public function getGlobalMiddleware() public function getMiddlewareGroups() { $middleware = [ - 'web' => [ + 'web' => array_values(array_filter([ \Illuminate\Cookie\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, - ], + $this->authenticatedSessions ? 'auth.session' : null, + ])), 'api' => array_values(array_filter([ $this->statefulApi ? \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class : null, @@ -473,29 +490,106 @@ public function getMiddlewareGroups() } /** - * Configure the behavior of the authentication middleware. + * Configure where guests are redirected by the authentication middleware. + * + * @param callable|string $redirect + * @return $this + */ + public function redirectGuestsTo(callable|string $redirect) + { + return $this->redirectTo(guests: $redirect); + } + + /** + * Configure where users are redirected by the authentication and guest middleware. * - * @param callable $redirectTo + * @param callable|string $guests + * @param callable|string $users * @return $this */ - public function auth(callable $redirectTo) + public function redirectTo(callable|string $guests = null, callable|string $users = null) { - Authenticate::redirectUsing($redirectTo); - AuthenticateSession::redirectUsing($redirectTo); - AuthenticationException::redirectUsing($redirectTo); + $guests = is_string($guests) ? fn () => $guests : $guests; + $users = is_string($users) ? fn () => $users : $users; + + if ($guests) { + Authenticate::redirectUsing($guests); + AuthenticateSession::redirectUsing($guests); + AuthenticationException::redirectUsing($guests); + } + + if ($users) { + RedirectIfAuthenticated::redirectUsing($users); + } + + return $this; + } + + /** + * Configure the cookie encryption middleware. + * + * @param array $except + * @return $this + */ + public function encryptCookies(array $except = []) + { + EncryptCookies::except($except); return $this; } /** - * Configure the behavior of the "guest" middleware. + * Configure the CSRF token validation middleware. * - * @param callable $redirectTo + * @param array $except * @return $this */ - public function guest(callable $redirectTo) + public function validateCsrfTokens(array $except = []) { - RedirectIfAuthenticated::redirectUsing($redirectTo); + ValidateCsrfToken::except($except); + + return $this; + } + + /** + * Configure the URL signature validation middleware. + * + * @param array $except + * @return $this + */ + public function validateSignatures(array $except = []) + { + ValidateSignature::except($except); + + return $this; + } + + /** + * Configure the empty string conversion middleware. + * + * @param array $except + * @return $this + */ + public function convertEmptyStringsToNull(array $except = []) + { + collect($except)->each(fn (Closure $callback) => ConvertEmptyStringsToNull::skipWhen($callback)); + + return $this; + } + + /** + * Configure the string trimming middleware. + * + * @param array $except + * @return $this + */ + public function trimStrings(array $except = []) + { + [$skipWhen, $except] = collect($except)->partition(fn ($value) => $value instanceof Closure); + + $skipWhen->each(fn (Closure $callback) => TrimStrings::skipWhen($callback)); + + TrimStrings::except($except->all()); return $this; } @@ -503,12 +597,51 @@ public function guest(callable $redirectTo) /** * Indicate that the trusted host middleware should be enabled. * + * @param array|null $at + * @param bool $subdomains * @return $this */ - public function withTrustedHosts() + public function trustHosts(array $at = null, bool $subdomains = true) { $this->trustHosts = true; + if (is_array($at)) { + TrustHosts::at($at, $subdomains); + } + + return $this; + } + + /** + * Configure the trusted proxies for the application. + * + * @param array|string|null $at + * @param int|null $headers + * @return $this + */ + public function trustProxies(array|string $at = null, int $headers = null) + { + if (! is_null($at)) { + TrustProxies::at($at); + } + + if (! is_null($headers)) { + TrustProxies::withHeaders($headers); + } + + return $this; + } + + /** + * Configure the middleware that prevents requests during maintenance mode. + * + * @param array $except + * @return $this + */ + public function preventRequestsDuringMaintenance(array $except = []) + { + PreventRequestsDuringMaintenance::except($except); + return $this; } @@ -517,7 +650,7 @@ public function withTrustedHosts() * * @return $this */ - public function withStatefulApi() + public function statefulApi() { $this->statefulApi = true; @@ -531,7 +664,7 @@ public function withStatefulApi() * @param bool $redis * @return $this */ - public function withThrottledApi($limiter = 'api', $redis = false) + public function throttleApi($limiter = 'api', $redis = false) { $this->apiLimiter = $limiter; @@ -554,6 +687,18 @@ public function throttleWithRedis() return $this; } + /** + * Indicate that sessions should be authenticated for the "web" middleware group. + * + * @return $this + */ + public function authenticateSessions() + { + $this->authenticatedSessions = true; + + return $this; + } + /** * Get the Folio / page middleware for the application. * @@ -598,4 +743,14 @@ protected function defaultAliases() 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, ]; } + + /** + * Get the middleware priority for the application. + * + * @return array + */ + public function getMiddlewarePriority() + { + return $this->priority; + } } diff --git a/src/Illuminate/Foundation/Console/AboutCommand.php b/src/Illuminate/Foundation/Console/AboutCommand.php index 23d2b23b631a..20c24e8e7c11 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'; @@ -300,4 +302,16 @@ protected function toSearchKeyword(string $value) { return (string) Str::of($value)->lower()->snake(); } + + /** + * Flush the registered about data. + * + * @return void + */ + public static function flushState() + { + static::$data = []; + + static::$customDataResolvers = []; + } } diff --git a/src/Illuminate/Foundation/Console/ApiInstallCommand.php b/src/Illuminate/Foundation/Console/ApiInstallCommand.php index 2a7a072a0461..9bab2abda4c4 100644 --- a/src/Illuminate/Foundation/Console/ApiInstallCommand.php +++ b/src/Illuminate/Foundation/Console/ApiInstallCommand.php @@ -90,7 +90,7 @@ protected function uncommentApiRoutesFile() protected function installSanctum() { $this->requireComposerPackages($this->option('composer'), [ - 'laravel/sanctum:dev-master', + 'laravel/sanctum:^4.0', ]); $php = (new PhpExecutableFinder())->find(false) ?: 'php'; diff --git a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php index d0b606e4e74e..e62426b99f12 100644 --- a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php +++ b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php @@ -2,19 +2,27 @@ namespace Illuminate\Foundation\Console; +use Composer\InstalledVersions; use Illuminate\Console\Command; use Illuminate\Filesystem\Filesystem; +use Illuminate\Support\Facades\Process; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Process\PhpExecutableFinder; + +use function Laravel\Prompts\confirm; #[AsCommand(name: 'install:broadcasting')] class BroadcastingInstallCommand extends Command { + use InteractsWithComposerPackages; + /** * The name and signature of the console command. * * @var string */ protected $signature = 'install:broadcasting + {--composer=global : Absolute path to the Composer binary which should be used to install packages} {--force : Overwrite any existing broadcasting routes file}'; /** @@ -31,6 +39,8 @@ class BroadcastingInstallCommand extends Command */ public function handle() { + $this->call('config:publish', ['name' => 'broadcasting']); + // Install channel routes file... if (file_exists($broadcastingRoutesPath = $this->laravel->basePath('routes/channels.php')) && ! $this->option('force')) { @@ -56,10 +66,12 @@ public function handle() if (! str_contains($bootstrapScript, 'echo.js')) { file_put_contents( $bootstrapScriptPath, - $bootstrapScript.PHP_EOL.file_get_contents(__DIR__.'/stubs/echo-bootstrap-js.stub') + trim($bootstrapScript.PHP_EOL.file_get_contents(__DIR__.'/stubs/echo-bootstrap-js.stub')).PHP_EOL, ); } } + + $this->installReverb(); } /** @@ -91,4 +103,36 @@ protected function uncommentChannelsRoutesFile() return; } } + + /** + * Install Laravel Reverb into the application if desired. + * + * @return void + */ + protected function installReverb() + { + if (InstalledVersions::isInstalled('laravel/reverb')) { + return; + } + + $install = confirm('Would you like to install Laravel Reverb?', default: true); + + if (! $install) { + return; + } + + $this->requireComposerPackages($this->option('composer'), [ + 'laravel/reverb:@beta', + ]); + + $php = (new PhpExecutableFinder())->find(false) ?: 'php'; + + Process::run([ + $php, + defined('ARTISAN_BINARY') ? ARTISAN_BINARY : 'artisan', + 'reverb:install', + ]); + + $this->components->info('Reverb installed successfully.'); + } } diff --git a/src/Illuminate/Foundation/Console/ClassMakeCommand.php b/src/Illuminate/Foundation/Console/ClassMakeCommand.php new file mode 100644 index 000000000000..a5d32b84089d --- /dev/null +++ b/src/Illuminate/Foundation/Console/ClassMakeCommand.php @@ -0,0 +1,70 @@ +option('invokable')) { + return __DIR__.'/stubs/class.invokable.stub'; + } + + return __DIR__.'/stubs/class.stub'; + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace; + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getOptions() + { + return [ + ['invokable', 'i', InputOption::VALUE_NONE, 'Generate a single method, invokable class'], + ['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the class already exists'], + ]; + } +} diff --git a/src/Illuminate/Foundation/Console/CliDumper.php b/src/Illuminate/Foundation/Console/CliDumper.php index 304dfcb0c351..6f5fd9a49886 100644 --- a/src/Illuminate/Foundation/Console/CliDumper.php +++ b/src/Illuminate/Foundation/Console/CliDumper.php @@ -57,6 +57,8 @@ public function __construct($output, $basePath, $compiledViewPath) $this->basePath = $basePath; $this->output = $output; $this->compiledViewPath = $compiledViewPath; + + $this->setColors($this->supportsColors()); } /** diff --git a/src/Illuminate/Foundation/Console/ConfigPublishCommand.php b/src/Illuminate/Foundation/Console/ConfigPublishCommand.php index bc12f35cd4d8..54ebce46eb94 100644 --- a/src/Illuminate/Foundation/Console/ConfigPublishCommand.php +++ b/src/Illuminate/Foundation/Console/ConfigPublishCommand.php @@ -92,7 +92,9 @@ protected function getBaseConfigurationFiles() $config = []; foreach (Finder::create()->files()->name('*.php')->in(__DIR__.'/../../../../config') as $file) { - $config[basename($file->getRealPath(), '.php')] = $file->getRealPath(); + $name = basename($file->getRealPath(), '.php'); + + $config[$name] = file_exists($stubPath = (__DIR__.'/../../../../config-stubs/'.$name.'.php')) ? $stubPath : $file->getRealPath(); } return collect($config)->sortKeys()->all(); diff --git a/src/Illuminate/Foundation/Console/EnumMakeCommand.php b/src/Illuminate/Foundation/Console/EnumMakeCommand.php new file mode 100644 index 000000000000..eeb97d40f142 --- /dev/null +++ b/src/Illuminate/Foundation/Console/EnumMakeCommand.php @@ -0,0 +1,109 @@ +option('string') || $this->option('int')) { + return __DIR__.'/stubs/enum.backed.stub'; + } + + return __DIR__.'/stubs/enum.stub'; + } + + /** + * Build the class with the given name. + * + * @param string $name + * @return string + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + protected function buildClass($name) + { + if ($this->option('string') || $this->option('int')) { + return str_replace( + ['{{ type }}'], + $this->option('string') ? 'string' : 'int', + parent::buildClass($name) + ); + } + + return parent::buildClass($name); + } + + /** + * Interact further with the user if they were prompted for missing arguments. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return void + */ + protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output) + { + if ($this->didReceiveOptions($input)) { + return; + } + + $type = select('Which type of enum would you like?', [ + 'pure' => 'Pure enum', + 'string' => 'Backed enum (String)', + 'int' => 'Backed enum (Integer)', + ]); + + if ($type !== 'pure') { + $input->setOption($type, true); + } + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getOptions() + { + return [ + ['string', 's', InputOption::VALUE_NONE, 'Generate a string backed enum.'], + ['int', 'i', InputOption::VALUE_NONE, 'Generate an integer backed enum.'], + ['force', 'f', InputOption::VALUE_NONE, 'Create the enum even if the enum already exists'], + ]; + } +} diff --git a/src/Illuminate/Foundation/Console/EventGenerateCommand.php b/src/Illuminate/Foundation/Console/EventGenerateCommand.php index 5b0857a3a11a..ad99e9373357 100644 --- a/src/Illuminate/Foundation/Console/EventGenerateCommand.php +++ b/src/Illuminate/Foundation/Console/EventGenerateCommand.php @@ -23,6 +23,13 @@ class EventGenerateCommand extends Command */ protected $description = 'Generate the missing events and listeners based on registration'; + /** + * Indicates whether the command should be shown in the Artisan command list. + * + * @var bool + */ + protected $hidden = true; + /** * Execute the console command. * diff --git a/src/Illuminate/Foundation/Console/ExceptionMakeCommand.php b/src/Illuminate/Foundation/Console/ExceptionMakeCommand.php index 7cf7faaf43c0..bcd82ab0c57d 100644 --- a/src/Illuminate/Foundation/Console/ExceptionMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ExceptionMakeCommand.php @@ -4,7 +4,11 @@ use Illuminate\Console\GeneratorCommand; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +use function Laravel\Prompts\{confirm}; #[AsCommand(name: 'make:exception')] class ExceptionMakeCommand extends GeneratorCommand @@ -70,6 +74,23 @@ protected function getDefaultNamespace($rootNamespace) return $rootNamespace.'\Exceptions'; } + /** + * Interact further with the user if they were prompted for missing arguments. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return void + */ + protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output) + { + if ($this->didReceiveOptions($input)) { + return; + } + + $input->setOption('report', confirm('Should the exception have a report method?', default: false)); + $input->setOption('render', confirm('Should the exception have a render method?', default: false)); + } + /** * Get the console command options. * diff --git a/src/Illuminate/Foundation/Console/InterfaceMakeCommand.php b/src/Illuminate/Foundation/Console/InterfaceMakeCommand.php new file mode 100644 index 000000000000..e30103497bb0 --- /dev/null +++ b/src/Illuminate/Foundation/Console/InterfaceMakeCommand.php @@ -0,0 +1,65 @@ +app['config']->get('cache.schedule_store', Env::get('SCHEDULE_CACHE_DRIVER')); + return $this->app['config']->get('cache.schedule_store', Env::get('SCHEDULE_CACHE_DRIVER', function () { + return Env::get('SCHEDULE_CACHE_STORE'); + })); } /** @@ -361,7 +363,7 @@ protected function load($paths) $namespace = $this->app->getNamespace(); - foreach ((new Finder)->in($paths)->files() as $file) { + foreach (Finder::create()->in($paths)->files() as $file) { $command = $this->commandClassFromFile($file, $namespace); if (is_subclass_of($command, Command::class) && diff --git a/src/Illuminate/Foundation/Console/ServeCommand.php b/src/Illuminate/Foundation/Console/ServeCommand.php index 194c80fa5127..a73bf52ef5c1 100644 --- a/src/Illuminate/Foundation/Console/ServeCommand.php +++ b/src/Illuminate/Foundation/Console/ServeCommand.php @@ -57,6 +57,9 @@ class ServeCommand extends Command */ public static $passthroughVariables = [ 'APP_ENV', + 'HERD_PHP_81_INI_SCAN_DIR', + 'HERD_PHP_82_INI_SCAN_DIR', + 'HERD_PHP_83_INI_SCAN_DIR', 'IGNITION_LOCAL_SITES_PATH', 'LARAVEL_SAIL', 'PATH', @@ -140,6 +143,14 @@ protected function startProcess($hasEnvironment) return in_array($key, static::$passthroughVariables) ? [$key => $value] : [$key => false]; })->all()); + $this->trap(fn () => [SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2, SIGQUIT], function ($signal) use ($process) { + if ($process->isRunning()) { + $process->stop(10, $signal); + } + + exit; + }); + $process->start($this->handleProcessOutput()); return $process; diff --git a/src/Illuminate/Foundation/Console/StorageUnlinkCommand.php b/src/Illuminate/Foundation/Console/StorageUnlinkCommand.php new file mode 100644 index 000000000000..504bc80ab0ac --- /dev/null +++ b/src/Illuminate/Foundation/Console/StorageUnlinkCommand.php @@ -0,0 +1,53 @@ +links() as $link => $target) { + if (! file_exists($link) || ! is_link($link)) { + continue; + } + + $this->laravel->make('files')->delete($link); + + $this->components->info("The [$link] link has been deleted."); + } + } + + /** + * Get the symbolic links that are configured for the application. + * + * @return array + */ + protected function links() + { + return $this->laravel['config']['filesystems.links'] ?? + [public_path('storage') => storage_path('app/public')]; + } +} diff --git a/src/Illuminate/Foundation/Console/StubPublishCommand.php b/src/Illuminate/Foundation/Console/StubPublishCommand.php index e2a293d489db..aa0f423f79e1 100644 --- a/src/Illuminate/Foundation/Console/StubPublishCommand.php +++ b/src/Illuminate/Foundation/Console/StubPublishCommand.php @@ -40,7 +40,11 @@ public function handle() $stubs = [ __DIR__.'/stubs/cast.inbound.stub' => 'cast.inbound.stub', __DIR__.'/stubs/cast.stub' => 'cast.stub', + __DIR__.'/stubs/class.stub' => 'class.stub', + __DIR__.'/stubs/class.invokable.stub' => 'class.invokable.stub', __DIR__.'/stubs/console.stub' => 'console.stub', + __DIR__.'/stubs/enum.stub' => 'enum.stub', + __DIR__.'/stubs/enum.backed.stub' => 'enum.backed.stub', __DIR__.'/stubs/event.stub' => 'event.stub', __DIR__.'/stubs/job.queued.stub' => 'job.queued.stub', __DIR__.'/stubs/job.stub' => 'job.stub', @@ -62,6 +66,7 @@ public function handle() __DIR__.'/stubs/scope.stub' => 'scope.stub', __DIR__.'/stubs/test.stub' => 'test.stub', __DIR__.'/stubs/test.unit.stub' => 'test.unit.stub', + __DIR__.'/stubs/trait.stub' => 'trait.stub', __DIR__.'/stubs/view-component.stub' => 'view-component.stub', realpath(__DIR__.'/../../Database/Console/Factories/stubs/factory.stub') => 'factory.stub', realpath(__DIR__.'/../../Database/Console/Seeds/stubs/seeder.stub') => 'seeder.stub', diff --git a/src/Illuminate/Foundation/Console/TestMakeCommand.php b/src/Illuminate/Foundation/Console/TestMakeCommand.php index 7b65cf991366..85440589f52d 100644 --- a/src/Illuminate/Foundation/Console/TestMakeCommand.php +++ b/src/Illuminate/Foundation/Console/TestMakeCommand.php @@ -44,7 +44,7 @@ protected function getStub() { $suffix = $this->option('unit') ? '.unit.stub' : '.stub'; - return $this->option('pest') + return $this->usingPest() ? $this->resolveStubPath('/stubs/pest'.$suffix) : $this->resolveStubPath('/stubs/test'.$suffix); } @@ -108,9 +108,10 @@ protected function rootNamespace() protected function getOptions() { return [ - ['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the test already exists'], + ['force', 'f', InputOption::VALUE_NONE, 'Create the test even if the test already exists'], ['unit', 'u', InputOption::VALUE_NONE, 'Create a unit test'], - ['pest', 'p', InputOption::VALUE_NONE, 'Create a Pest test'], + ['pest', null, InputOption::VALUE_NONE, 'Create a Pest test'], + ['phpunit', null, InputOption::VALUE_NONE, 'Create a PHPUnit test'], ]; } @@ -128,17 +129,29 @@ protected function afterPromptingForMissingArguments(InputInterface $input, Outp } $type = select('Which type of test would you like?', [ - 'feature' => 'Feature (PHPUnit)', - 'unit' => 'Unit (PHPUnit)', - 'pest-feature' => 'Feature (Pest)', - 'pest-unit' => 'Unit (Pest)', + 'feature' => 'Feature', + 'unit' => 'Unit', ]); match ($type) { 'feature' => null, 'unit' => $input->setOption('unit', true), - 'pest-feature' => $input->setOption('pest', true), - 'pest-unit' => tap($input)->setOption('pest', true)->setOption('unit', true), }; } + + /** + * Determine if Pest is being used by the application. + * + * @return bool + */ + protected function usingPest() + { + if ($this->option('phpunit')) { + return false; + } + + return $this->option('pest') || + (function_exists('\Pest\\version') && + file_exists(base_path('tests').'/Pest.php')); + } } diff --git a/src/Illuminate/Foundation/Console/TraitMakeCommand.php b/src/Illuminate/Foundation/Console/TraitMakeCommand.php new file mode 100644 index 000000000000..6f1c3172e06e --- /dev/null +++ b/src/Illuminate/Foundation/Console/TraitMakeCommand.php @@ -0,0 +1,65 @@ +option('test') && ! $this->option('pest')) { + if (! $this->option('test') && ! $this->option('pest') && ! $this->option('phpunit')) { return false; } @@ -201,7 +201,7 @@ protected function testClassFullyQualifiedName() */ protected function getTestStub() { - $stubName = 'view.'.($this->option('pest') ? 'pest' : 'test').'.stub'; + $stubName = 'view.'.($this->usingPest() ? 'pest' : 'test').'.stub'; return file_exists($customPath = $this->laravel->basePath("stubs/$stubName")) ? $customPath @@ -221,6 +221,22 @@ protected function testViewName() ->value(); } + /** + * Determine if Pest is being used by the application. + * + * @return bool + */ + protected function usingPest() + { + if ($this->option('phpunit')) { + return false; + } + + return $this->option('pest') || + (function_exists('\Pest\\version') && + file_exists(base_path('tests').'/Pest.php')); + } + /** * Get the console command arguments. * diff --git a/src/Illuminate/Foundation/Console/stubs/api-routes.stub b/src/Illuminate/Foundation/Console/stubs/api-routes.stub index aee5f3636395..0e7503c70253 100644 --- a/src/Illuminate/Foundation/Console/stubs/api-routes.stub +++ b/src/Illuminate/Foundation/Console/stubs/api-routes.stub @@ -4,17 +4,6 @@ use Illuminate\Auth\Middleware\Authenticate; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; -/* -|-------------------------------------------------------------------------- -| API Routes -|-------------------------------------------------------------------------- -| -| Here is where you can register API routes for your application. These -| routes are loaded within the "api" middleware group which includes -| the middleware most often needed by APIs. Build something great! -| -*/ - Route::get('/user', function (Request $request) { return $request->user(); })->middleware(Authenticate::using('sanctum')); diff --git a/src/Illuminate/Foundation/Console/stubs/broadcasting-routes.stub b/src/Illuminate/Foundation/Console/stubs/broadcasting-routes.stub index 5d451e1fae88..df2ad287ec14 100644 --- a/src/Illuminate/Foundation/Console/stubs/broadcasting-routes.stub +++ b/src/Illuminate/Foundation/Console/stubs/broadcasting-routes.stub @@ -2,17 +2,6 @@ use Illuminate\Support\Facades\Broadcast; -/* -|-------------------------------------------------------------------------- -| Broadcast Channels -|-------------------------------------------------------------------------- -| -| Here you may register all of the event broadcasting channels that your -| application supports. The given channel authorization callbacks are -| used to check if an authenticated user can listen to the channel. -| -*/ - Broadcast::channel('App.Models.User.{id}', function ($user, $id) { return (int) $user->id === (int) $id; }); diff --git a/src/Illuminate/Foundation/Console/stubs/class.invokable.stub b/src/Illuminate/Foundation/Console/stubs/class.invokable.stub new file mode 100644 index 000000000000..c55610cfe4a6 --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/class.invokable.stub @@ -0,0 +1,22 @@ +setCompiledRoutes( {{routes}} ); diff --git a/src/Illuminate/Foundation/Console/stubs/trait.stub b/src/Illuminate/Foundation/Console/stubs/trait.stub new file mode 100644 index 000000000000..e40984771f42 --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/trait.stub @@ -0,0 +1,8 @@ +files()->in($listenerPath), $basePath + Finder::create()->files()->in($listenerPath), $basePath )); $discoveredEvents = []; diff --git a/src/Illuminate/Foundation/Exceptions/Handler.php b/src/Illuminate/Foundation/Exceptions/Handler.php index facb54fe54c1..ff3231ef9cb7 100644 --- a/src/Illuminate/Foundation/Exceptions/Handler.php +++ b/src/Illuminate/Foundation/Exceptions/Handler.php @@ -251,23 +251,25 @@ public function map($from, $to = null) * * Alias of "ignore". * - * @param string $class + * @param array|string $exceptions * @return $this */ - public function dontReport(string $class) + public function dontReport(array|string $exceptions) { - return $this->ignore($class); + return $this->ignore($exceptions); } /** * Indicate that the given exception type should not be reported. * - * @param string $class + * @param array|string $class * @return $this */ - public function ignore(string $class) + public function ignore(array|string $exceptions) { - $this->dontReport[] = $class; + $exceptions = Arr::wrap($exceptions); + + $this->dontReport = array_values(array_unique(array_merge($this->dontReport, $exceptions))); return $this; } @@ -278,7 +280,7 @@ public function ignore(string $class) * @param array|string $attributes * @return $this */ - public function dontFlash($attributes) + public function dontFlash(array|string $attributes) { $this->dontFlash = array_values(array_unique( array_merge($this->dontFlash, Arr::wrap($attributes)) @@ -450,16 +452,18 @@ public function throttleUsing(callable $throttleUsing) /** * Remove the given exception class from the list of exceptions that should be ignored. * - * @param string $exception + * @param array|string $exceptions * @return $this */ - public function stopIgnoring(string $exception) + public function stopIgnoring(array|string $exceptions) { + $exceptions = Arr::wrap($exceptions); + $this->dontReport = collect($this->dontReport) - ->reject(fn ($ignored) => $ignored === $exception)->values()->all(); + ->reject(fn ($ignored) => in_array($ignored, $exceptions))->values()->all(); $this->internalDontReport = collect($this->internalDontReport) - ->reject(fn ($ignored) => $ignored === $exception)->values()->all(); + ->reject(fn ($ignored) => in_array($ignored, $exceptions))->values()->all(); return $this; } @@ -933,7 +937,7 @@ public function renderForConsole($output, Throwable $e) $message .= '. Did you mean one of these?'; with(new Error($output))->render($message); - with(new BulletList($output))->render($e->getAlternatives()); + with(new BulletList($output))->render($alternatives); $output->writeln(''); } else { diff --git a/src/Illuminate/Foundation/Http/FormRequest.php b/src/Illuminate/Foundation/Http/FormRequest.php index 772a2635a3b4..4824c27c1799 100644 --- a/src/Illuminate/Foundation/Http/FormRequest.php +++ b/src/Illuminate/Foundation/Http/FormRequest.php @@ -115,11 +115,13 @@ protected function getValidatorInstance() */ protected function createDefaultValidator(ValidationFactory $factory) { - $rules = method_exists($this, 'rules') ? $this->container->call([$this, 'rules']) : []; + $rules = $this->validationRules(); $validator = $factory->make( - $this->validationData(), $rules, - $this->messages(), $this->attributes() + $this->validationData(), + $rules, + $this->messages(), + $this->attributes(), )->stopOnFirstFailure($this->stopOnFirstFailure); if ($this->isPrecognitive()) { @@ -141,6 +143,16 @@ public function validationData() return $this->all(); } + /** + * Get the validation rules for this form request. + * + * @return array + */ + protected function validationRules() + { + return method_exists($this, 'rules') ? $this->container->call([$this, 'rules']) : []; + } + /** * Handle a failed validation attempt. * diff --git a/src/Illuminate/Foundation/Http/Kernel.php b/src/Illuminate/Foundation/Http/Kernel.php index 2ddb1c58c8bf..79d5a6d5336f 100644 --- a/src/Illuminate/Foundation/Http/Kernel.php +++ b/src/Illuminate/Foundation/Http/Kernel.php @@ -596,6 +596,21 @@ public function setMiddlewareAliases(array $aliases) return $this; } + /** + * Set the application's middleware priority. + * + * @param array $priority + * @return $this + */ + public function setMiddlewarePriority(array $priority) + { + $this->middlewarePriority = $priority; + + $this->syncMiddlewareToRouter(); + + return $this; + } + /** * Get the Laravel application instance. * diff --git a/src/Illuminate/Foundation/Http/Middleware/Concerns/ExcludesPaths.php b/src/Illuminate/Foundation/Http/Middleware/Concerns/ExcludesPaths.php new file mode 100644 index 000000000000..622d9dd0b5da --- /dev/null +++ b/src/Illuminate/Foundation/Http/Middleware/Concerns/ExcludesPaths.php @@ -0,0 +1,44 @@ + + */ + protected $except = []; + + /** + * Determine if the request has a URI that should be excluded. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + protected function inExceptArray($request) + { + foreach ($this->getExcludedPaths() as $except) { + if ($except !== '/') { + $except = trim($except, '/'); + } + + if ($request->fullUrlIs($except) || $request->is($except)) { + return true; + } + } + + return false; + } + + /** + * Get the URIs that should be excluded. + * + * @return array + */ + public function getExcludedPaths() + { + return $this->except; + } +} diff --git a/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php b/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php index a25e4c4a4b91..0ecfa38598e5 100644 --- a/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php +++ b/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php @@ -6,10 +6,14 @@ use ErrorException; use Illuminate\Contracts\Foundation\Application; use Illuminate\Foundation\Http\MaintenanceModeBypassCookie; +use Illuminate\Foundation\Http\Middleware\Concerns\ExcludesPaths; +use Illuminate\Support\Arr; use Symfony\Component\HttpKernel\Exception\HttpException; class PreventRequestsDuringMaintenance { + use ExcludesPaths; + /** * The application implementation. * @@ -18,11 +22,11 @@ class PreventRequestsDuringMaintenance protected $app; /** - * The URIs that should be accessible while maintenance mode is enabled. + * The URIs that should be accessible during maintenance. * - * @var array + * @var array */ - protected $except = []; + protected static $neverPrevent = []; /** * Create a new middleware instance. @@ -116,27 +120,6 @@ protected function hasValidBypassCookie($request, array $data) ); } - /** - * Determine if the request has a URI that should be accessible in maintenance mode. - * - * @param \Illuminate\Http\Request $request - * @return bool - */ - protected function inExceptArray($request) - { - foreach ($this->getExcludedPaths() as $except) { - if ($except !== '/') { - $except = trim($except, '/'); - } - - if ($request->fullUrlIs($except) || $request->is($except)) { - return true; - } - } - - return false; - } - /** * Redirect the user back to the root of the application with a maintenance mode bypass cookie. * @@ -168,12 +151,35 @@ protected function getHeaders($data) } /** - * Get the URIs that should be accessible even when maintenance mode is enabled. + * Get the URIs that should be excluded. * * @return array */ public function getExcludedPaths() { - return $this->except; + return array_merge($this->except, static::$neverPrevent); + } + + /** + * Indicate that the given URIs should always be accessible. + * + * @param array|string $uris + * @return void + */ + public static function except($uris) + { + static::$neverPrevent = array_values(array_unique( + array_merge(static::$neverPrevent, Arr::wrap($uris)) + )); + } + + /** + * Flush the state of the middleware. + * + * @return void + */ + public static function flushState() + { + static::$neverPrevent = []; } } diff --git a/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php b/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php index 10d8a137d57e..f5ad195b0325 100644 --- a/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php +++ b/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php @@ -99,6 +99,8 @@ public static function skipWhen(Closure $callback) */ public static function flushState() { + static::$neverTrim = []; + static::$skipCallbacks = []; } } diff --git a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php index d39b09981a80..ba2f67e6af11 100644 --- a/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php +++ b/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\Support\Responsable; use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Cookie\Middleware\EncryptCookies; +use Illuminate\Foundation\Http\Middleware\Concerns\ExcludesPaths; use Illuminate\Session\TokenMismatchException; use Illuminate\Support\Arr; use Illuminate\Support\InteractsWithTime; @@ -16,7 +17,8 @@ class VerifyCsrfToken { - use InteractsWithTime; + use InteractsWithTime, + ExcludesPaths; /** * The application instance. @@ -32,13 +34,6 @@ class VerifyCsrfToken */ protected $encrypter; - /** - * The URIs that should be excluded from CSRF verification. - * - * @var array - */ - protected $except = []; - /** * The globally ignored URIs that should be excluded from CSRF verification. * @@ -115,24 +110,13 @@ protected function runningUnitTests() } /** - * Determine if the request has a URI that should pass through CSRF verification. + * Get the URIs that should be excluded. * - * @param \Illuminate\Http\Request $request - * @return bool + * @return array */ - protected function inExceptArray($request) + public function getExcludedPaths() { - foreach (array_merge($this->except, static::$neverVerify) as $except) { - if ($except !== '/') { - $except = trim($except, '/'); - } - - if ($request->fullUrlIs($except) || $request->is($except)) { - return true; - } - } - - return false; + return array_merge($this->except, static::$neverVerify); } /** @@ -227,13 +211,13 @@ protected function newCookie($request, $config) /** * Indicate that the given URIs should be excluded from CSRF verification. * - * @param array|string $paths + * @param array|string $uris * @return void */ - public static function except($paths) + public static function except($uris) { static::$neverVerify = array_values(array_unique( - array_merge(static::$neverVerify, Arr::wrap($paths)) + array_merge(static::$neverVerify, Arr::wrap($uris)) )); } @@ -246,4 +230,14 @@ public static function serialized() { return EncryptCookies::serialized('XSRF-TOKEN'); } + + /** + * Flush the state of the middleware. + * + * @return void + */ + public static function flushState() + { + static::$neverVerify = []; + } } diff --git a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php index eab3b0d97adb..4ec8289653d7 100755 --- a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -33,6 +33,7 @@ use Illuminate\Foundation\Console\CastMakeCommand; use Illuminate\Foundation\Console\ChannelListCommand; use Illuminate\Foundation\Console\ChannelMakeCommand; +use Illuminate\Foundation\Console\ClassMakeCommand; use Illuminate\Foundation\Console\ClearCompiledCommand; use Illuminate\Foundation\Console\ComponentMakeCommand; use Illuminate\Foundation\Console\ConfigCacheCommand; @@ -42,6 +43,7 @@ use Illuminate\Foundation\Console\ConsoleMakeCommand; use Illuminate\Foundation\Console\DocsCommand; use Illuminate\Foundation\Console\DownCommand; +use Illuminate\Foundation\Console\EnumMakeCommand; use Illuminate\Foundation\Console\EnvironmentCommand; use Illuminate\Foundation\Console\EnvironmentDecryptCommand; use Illuminate\Foundation\Console\EnvironmentEncryptCommand; @@ -51,6 +53,7 @@ use Illuminate\Foundation\Console\EventListCommand; use Illuminate\Foundation\Console\EventMakeCommand; use Illuminate\Foundation\Console\ExceptionMakeCommand; +use Illuminate\Foundation\Console\InterfaceMakeCommand; use Illuminate\Foundation\Console\JobMakeCommand; use Illuminate\Foundation\Console\KeyGenerateCommand; use Illuminate\Foundation\Console\LangPublishCommand; @@ -73,8 +76,10 @@ use Illuminate\Foundation\Console\ScopeMakeCommand; use Illuminate\Foundation\Console\ServeCommand; use Illuminate\Foundation\Console\StorageLinkCommand; +use Illuminate\Foundation\Console\StorageUnlinkCommand; use Illuminate\Foundation\Console\StubPublishCommand; use Illuminate\Foundation\Console\TestMakeCommand; +use Illuminate\Foundation\Console\TraitMakeCommand; use Illuminate\Foundation\Console\UpCommand; use Illuminate\Foundation\Console\VendorPublishCommand; use Illuminate\Foundation\Console\ViewCacheCommand; @@ -161,6 +166,7 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid 'ScheduleInterrupt' => ScheduleInterruptCommand::class, 'ShowModel' => ShowModelCommand::class, 'StorageLink' => StorageLinkCommand::class, + 'StorageUnlink' => StorageUnlinkCommand::class, 'Up' => UpCommand::class, 'ViewCache' => ViewCacheCommand::class, 'ViewClear' => ViewClearCommand::class, @@ -178,15 +184,18 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid 'CastMake' => CastMakeCommand::class, 'ChannelList' => ChannelListCommand::class, 'ChannelMake' => ChannelMakeCommand::class, + 'ClassMake' => ClassMakeCommand::class, 'ComponentMake' => ComponentMakeCommand::class, 'ConfigPublish' => ConfigPublishCommand::class, 'ConsoleMake' => ConsoleMakeCommand::class, 'ControllerMake' => ControllerMakeCommand::class, 'Docs' => DocsCommand::class, + 'EnumMake' => EnumMakeCommand::class, 'EventGenerate' => EventGenerateCommand::class, 'EventMake' => EventMakeCommand::class, 'ExceptionMake' => ExceptionMakeCommand::class, 'FactoryMake' => FactoryMakeCommand::class, + 'InterfaceMake' => InterfaceMakeCommand::class, 'JobMake' => JobMakeCommand::class, 'LangPublish' => LangPublishCommand::class, 'ListenerMake' => ListenerMakeCommand::class, @@ -210,6 +219,7 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid 'Serve' => ServeCommand::class, 'StubPublish' => StubPublishCommand::class, 'TestMake' => TestMakeCommand::class, + 'TraitMake' => TraitMakeCommand::class, 'VendorPublish' => VendorPublishCommand::class, 'ViewMake' => ViewMakeCommand::class, ]; @@ -326,6 +336,18 @@ protected function registerChannelMakeCommand() }); } + /** + * Register the command. + * + * @return void + */ + protected function registerClassMakeCommand() + { + $this->app->singleton(ClassMakeCommand::class, function ($app) { + return new ClassMakeCommand($app['files']); + }); + } + /** * Register the command. * @@ -398,6 +420,18 @@ protected function registerControllerMakeCommand() }); } + /** + * Register the command. + * + * @return void + */ + protected function registerEnumMakeCommand() + { + $this->app->singleton(EnumMakeCommand::class, function ($app) { + return new EnumMakeCommand($app['files']); + }); + } + /** * Register the command. * @@ -446,6 +480,18 @@ protected function registerEventClearCommand() }); } + /** + * Register the command. + * + * @return void + */ + protected function registerInterfaceMakeCommand() + { + $this->app->singleton(InterfaceMakeCommand::class, function ($app) { + return new InterfaceMakeCommand($app['files']); + }); + } + /** * Register the command. * @@ -816,6 +862,18 @@ protected function registerTestMakeCommand() }); } + /** + * Register the command. + * + * @return void + */ + protected function registerTraitMakeCommand() + { + $this->app->singleton(TraitMakeCommand::class, function ($app) { + return new TraitMakeCommand($app['files']); + }); + } + /** * Register the command. * 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/Support/Providers/EventServiceProvider.php b/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php index 6dd42b16ff9c..69f4e63f28f4 100644 --- a/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php +++ b/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php @@ -32,6 +32,13 @@ class EventServiceProvider extends ServiceProvider */ protected $observers = []; + /** + * The configured event discovery paths. + * + * @var array|null + */ + protected static $eventDiscoveryPaths; + /** * Register the application's event listeners. * @@ -149,11 +156,22 @@ public function discoverEvents() */ protected function discoverEventsWithin() { - return [ + return static::$eventDiscoveryPaths ?: [ $this->app->path('Listeners'), ]; } + /** + * Set the globally configured event discovery paths. + * + * @param array $paths + * @return void + */ + public static function setEventDiscoveryPaths(array $paths) + { + static::$eventDiscoveryPaths = $paths; + } + /** * Get the base path to be used during event discovery. * diff --git a/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php b/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php index 9c9ba4f90be6..323f0f32b42a 100644 --- a/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php +++ b/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php @@ -90,7 +90,7 @@ protected function routes(Closure $routesCallback) */ public static function loadRoutesUsing(Closure $routesCallback) { - static::$alwaysLoadRoutesUsing = $routesCallback; + self::$alwaysLoadRoutesUsing = $routesCallback; } /** @@ -134,9 +134,11 @@ protected function loadCachedRoutes() */ protected function loadRoutes() { - if (! is_null(static::$alwaysLoadRoutesUsing)) { - $this->app->call(static::$alwaysLoadRoutesUsing); - } elseif (! is_null($this->loadRoutesUsing)) { + if (! is_null(self::$alwaysLoadRoutesUsing)) { + $this->app->call(self::$alwaysLoadRoutesUsing); + } + + if (! is_null($this->loadRoutesUsing)) { $this->app->call($this->loadRoutesUsing); } elseif (method_exists($this, 'map')) { $this->app->call([$this, 'map']); diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php new file mode 100644 index 000000000000..3d210cd7888d --- /dev/null +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php @@ -0,0 +1,291 @@ +app) { + $this->refreshApplication(); + + ParallelTesting::callSetUpTestCaseCallbacks($this); + } + + $this->setUpTraits(); + + foreach ($this->afterApplicationCreatedCallbacks as $callback) { + $callback(); + } + + Model::setEventDispatcher($this->app['events']); + + $this->setUpHasRun = true; + } + + /** + * Clean up the testing environment before the next test. + * + * @internal + * + * @return void + */ + protected function tearDownTheTestEnvironment(): void + { + if ($this->app) { + $this->callBeforeApplicationDestroyedCallbacks(); + + ParallelTesting::callTearDownTestCaseCallbacks($this); + + $this->app->flush(); + + $this->app = null; + } + + $this->setUpHasRun = false; + + if (property_exists($this, 'serverVariables')) { + $this->serverVariables = []; + } + + if (property_exists($this, 'defaultHeaders')) { + $this->defaultHeaders = []; + } + + if (class_exists('Mockery')) { + if ($container = Mockery::getContainer()) { + $this->addToAssertionCount($container->mockery_getExpectationCount()); + } + + try { + Mockery::close(); + } catch (InvalidCountException $e) { + if (! Str::contains($e->getMethodName(), ['doWrite', 'askQuestion'])) { + throw $e; + } + } + } + + if (class_exists(Carbon::class)) { + Carbon::setTestNow(); + } + + if (class_exists(CarbonImmutable::class)) { + CarbonImmutable::setTestNow(); + } + + $this->afterApplicationCreatedCallbacks = []; + $this->beforeApplicationDestroyedCallbacks = []; + + if (property_exists($this, 'originalExceptionHandler')) { + $this->originalExceptionHandler = null; + } + + if (property_exists($this, 'originalDeprecationHandler')) { + $this->originalDeprecationHandler = null; + } + + AboutCommand::flushState(); + Artisan::forgetBootstrappers(); + Component::flushCache(); + Component::forgetComponentsResolver(); + Component::forgetFactory(); + ConvertEmptyStringsToNull::flushState(); + EncryptCookies::flushState(); + HandleExceptions::flushState(); + Once::flush(); + PreventRequestsDuringMaintenance::flushState(); + Queue::createPayloadUsing(null); + RegisterProviders::flushState(); + Sleep::fake(false); + TrimStrings::flushState(); + TrustProxies::flushState(); + TrustHosts::flushState(); + ValidateCsrfToken::flushState(); + + if ($this->callbackException) { + throw $this->callbackException; + } + } + + /** + * Boot the testing helper traits. + * + * @return array + */ + protected function setUpTraits() + { + $uses = array_flip(class_uses_recursive(static::class)); + + if (isset($uses[RefreshDatabase::class])) { + $this->refreshDatabase(); + } + + if (isset($uses[DatabaseMigrations::class])) { + $this->runDatabaseMigrations(); + } + + if (isset($uses[DatabaseTruncation::class])) { + $this->truncateDatabaseTables(); + } + + if (isset($uses[DatabaseTransactions::class])) { + $this->beginDatabaseTransaction(); + } + + if (isset($uses[WithoutMiddleware::class])) { + $this->disableMiddlewareForAllTests(); + } + + if (isset($uses[WithFaker::class])) { + $this->setUpFaker(); + } + + foreach ($uses as $trait) { + if (method_exists($this, $method = 'setUp'.class_basename($trait))) { + $this->{$method}(); + } + + if (method_exists($this, $method = 'tearDown'.class_basename($trait))) { + $this->beforeApplicationDestroyed(fn () => $this->{$method}()); + } + } + + return $uses; + } + + /** + * Clean up the testing environment before the next test case. + * + * @internal + * + * @return void + */ + public static function tearDownAfterClassUsingTestCase() + { + (function () { + $this->classDocBlocks = []; + $this->methodDocBlocks = []; + })->call(PHPUnitRegistry::getInstance()); + } + + /** + * Register a callback to be run after the application is created. + * + * @param callable $callback + * @return void + */ + public function afterApplicationCreated(callable $callback) + { + $this->afterApplicationCreatedCallbacks[] = $callback; + + if ($this->setUpHasRun) { + $callback(); + } + } + + /** + * Register a callback to be run before the application is destroyed. + * + * @param callable $callback + * @return void + */ + protected function beforeApplicationDestroyed(callable $callback) + { + $this->beforeApplicationDestroyedCallbacks[] = $callback; + } + + /** + * Execute the application's pre-destruction callbacks. + * + * @return void + */ + protected function callBeforeApplicationDestroyedCallbacks() + { + foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { + try { + $callback(); + } catch (Throwable $e) { + if (! $this->callbackException) { + $this->callbackException = $e; + } + } + } + } +} 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/Foundation/Testing/DatabaseTruncation.php b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php index 6eb86d9b47a7..3f43181eb6be 100644 --- a/src/Illuminate/Foundation/Testing/DatabaseTruncation.php +++ b/src/Illuminate/Foundation/Testing/DatabaseTruncation.php @@ -83,7 +83,7 @@ protected function truncateTablesForConnection(ConnectionInterface $connection, $connection->unsetEventDispatcher(); - collect(static::$allTables[$name] ??= $connection->getDoctrineSchemaManager()->listTableNames()) + collect(static::$allTables[$name] ??= $connection->getSchemaBuilder()->getTableListing()) ->when( property_exists($this, 'tablesToTruncate'), fn ($tables) => $tables->intersect($this->tablesToTruncate), diff --git a/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php b/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php index 98204cceab48..3de593d9406b 100644 --- a/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php +++ b/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php @@ -17,15 +17,24 @@ public function refreshDatabase() { $database = $this->app->make('db'); - $database->beforeExecuting(function () { + $callback = function () { if (RefreshDatabaseState::$lazilyRefreshed) { return; } RefreshDatabaseState::$lazilyRefreshed = true; + $shouldMockOutput = $this->mockConsoleOutput; + + $this->mockConsoleOutput = false; + $this->baseRefreshDatabase(); - }); + + $this->mockConsoleOutput = $shouldMockOutput; + }; + + $database->beforeStartingTransaction($callback); + $database->beforeExecuting($callback); $this->beforeApplicationDestroyed(function () { RefreshDatabaseState::$lazilyRefreshed = false; diff --git a/src/Illuminate/Foundation/Testing/TestCase.php b/src/Illuminate/Foundation/Testing/TestCase.php index ab3fe672d4f1..94ec4c71d5be 100644 --- a/src/Illuminate/Foundation/Testing/TestCase.php +++ b/src/Illuminate/Foundation/Testing/TestCase.php @@ -2,21 +2,8 @@ namespace Illuminate\Foundation\Testing; -use Carbon\CarbonImmutable; -use Illuminate\Console\Application as Artisan; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Bootstrap\HandleExceptions; -use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; -use Illuminate\Foundation\Http\Middleware\TrimStrings; -use Illuminate\Queue\Queue; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Facade; -use Illuminate\Support\Facades\ParallelTesting; -use Illuminate\Support\Sleep; -use Illuminate\Support\Str; -use Illuminate\View\Component; -use Mockery; -use Mockery\Exception\InvalidCountException; +use Illuminate\Contracts\Console\Kernel; +use Illuminate\Foundation\Application; use PHPUnit\Framework\TestCase as BaseTestCase; use Throwable; @@ -31,51 +18,22 @@ abstract class TestCase extends BaseTestCase Concerns\InteractsWithExceptionHandling, Concerns\InteractsWithSession, Concerns\InteractsWithTime, + Concerns\InteractsWithTestCaseLifecycle, Concerns\InteractsWithViews; /** - * The Illuminate application instance. - * - * @var \Illuminate\Foundation\Application - */ - protected $app; - - /** - * The callbacks that should be run after the application is created. - * - * @var array - */ - protected $afterApplicationCreatedCallbacks = []; - - /** - * The callbacks that should be run before the application is destroyed. - * - * @var array - */ - protected $beforeApplicationDestroyedCallbacks = []; - - /** - * The exception thrown while running an application destruction callback. + * Creates the application. * - * @var \Throwable + * @return \Illuminate\Foundation\Application */ - protected $callbackException; + public function createApplication() + { + $app = require Application::inferBasePath().'/bootstrap/app.php'; - /** - * Indicates if we have made it through the base setUp function. - * - * @var bool - */ - protected $setUpHasRun = false; + $app->make(Kernel::class)->bootstrap(); - /** - * Creates the application. - * - * Needs to be implemented by subclasses. - * - * @return \Symfony\Component\HttpKernel\HttpKernelInterface - */ - abstract public function createApplication(); + return $app; + } /** * Setup the test environment. @@ -86,23 +44,7 @@ protected function setUp(): void { static::$latestResponse = null; - Facade::clearResolvedInstances(); - - if (! $this->app) { - $this->refreshApplication(); - - ParallelTesting::callSetUpTestCaseCallbacks($this); - } - - $this->setUpTraits(); - - foreach ($this->afterApplicationCreatedCallbacks as $callback) { - $callback(); - } - - Model::setEventDispatcher($this->app['events']); - - $this->setUpHasRun = true; + $this->setUpTheTestEnvironment(); } /** @@ -115,52 +57,6 @@ protected function refreshApplication() $this->app = $this->createApplication(); } - /** - * Boot the testing helper traits. - * - * @return array - */ - protected function setUpTraits() - { - $uses = array_flip(class_uses_recursive(static::class)); - - if (isset($uses[RefreshDatabase::class])) { - $this->refreshDatabase(); - } - - if (isset($uses[DatabaseMigrations::class])) { - $this->runDatabaseMigrations(); - } - - if (isset($uses[DatabaseTruncation::class])) { - $this->truncateDatabaseTables(); - } - - if (isset($uses[DatabaseTransactions::class])) { - $this->beginDatabaseTransaction(); - } - - if (isset($uses[WithoutMiddleware::class])) { - $this->disableMiddlewareForAllTests(); - } - - if (isset($uses[WithFaker::class])) { - $this->setUpFaker(); - } - - foreach ($uses as $trait) { - if (method_exists($this, $method = 'setUp'.class_basename($trait))) { - $this->{$method}(); - } - - if (method_exists($this, $method = 'tearDown'.class_basename($trait))) { - $this->beforeApplicationDestroyed(fn () => $this->{$method}()); - } - } - - return $uses; - } - /** * {@inheritdoc} */ @@ -184,67 +80,7 @@ protected function transformException(Throwable $error): Throwable */ protected function tearDown(): void { - if ($this->app) { - $this->callBeforeApplicationDestroyedCallbacks(); - - ParallelTesting::callTearDownTestCaseCallbacks($this); - - $this->app->flush(); - - $this->app = null; - } - - $this->setUpHasRun = false; - - if (property_exists($this, 'serverVariables')) { - $this->serverVariables = []; - } - - if (property_exists($this, 'defaultHeaders')) { - $this->defaultHeaders = []; - } - - if (class_exists('Mockery')) { - if ($container = Mockery::getContainer()) { - $this->addToAssertionCount($container->mockery_getExpectationCount()); - } - - try { - Mockery::close(); - } catch (InvalidCountException $e) { - if (! Str::contains($e->getMethodName(), ['doWrite', 'askQuestion'])) { - throw $e; - } - } - } - - if (class_exists(Carbon::class)) { - Carbon::setTestNow(); - } - - if (class_exists(CarbonImmutable::class)) { - CarbonImmutable::setTestNow(); - } - - $this->afterApplicationCreatedCallbacks = []; - $this->beforeApplicationDestroyedCallbacks = []; - - $this->originalExceptionHandler = null; - $this->originalDeprecationHandler = null; - - Artisan::forgetBootstrappers(); - Component::flushCache(); - Component::forgetComponentsResolver(); - Component::forgetFactory(); - ConvertEmptyStringsToNull::flushState(); - HandleExceptions::forgetApp(); - Queue::createPayloadUsing(null); - Sleep::fake(false); - TrimStrings::flushState(); - - if ($this->callbackException) { - throw $this->callbackException; - } + $this->tearDownTheTestEnvironment(); } /** @@ -256,60 +92,6 @@ public static function tearDownAfterClass(): void { static::$latestResponse = null; - foreach ([ - \PHPUnit\Util\Annotation\Registry::class, - \PHPUnit\Metadata\Annotation\Parser\Registry::class, - ] as $class) { - if (class_exists($class)) { - (function () { - $this->classDocBlocks = []; - $this->methodDocBlocks = []; - })->call($class::getInstance()); - } - } - } - - /** - * Register a callback to be run after the application is created. - * - * @param callable $callback - * @return void - */ - public function afterApplicationCreated(callable $callback) - { - $this->afterApplicationCreatedCallbacks[] = $callback; - - if ($this->setUpHasRun) { - $callback(); - } - } - - /** - * Register a callback to be run before the application is destroyed. - * - * @param callable $callback - * @return void - */ - protected function beforeApplicationDestroyed(callable $callback) - { - $this->beforeApplicationDestroyedCallbacks[] = $callback; - } - - /** - * Execute the application's pre-destruction callbacks. - * - * @return void - */ - protected function callBeforeApplicationDestroyedCallbacks() - { - foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { - try { - $callback(); - } catch (Throwable $e) { - if (! $this->callbackException) { - $this->callbackException = $e; - } - } - } + static::tearDownAfterClassUsingTestCase(); } } diff --git a/src/Illuminate/Foundation/Vite.php b/src/Illuminate/Foundation/Vite.php index 9c0a24d04f98..a81ca1616b96 100644 --- a/src/Illuminate/Foundation/Vite.php +++ b/src/Illuminate/Foundation/Vite.php @@ -55,6 +55,13 @@ class Vite implements Htmlable */ protected $manifestFilename = 'manifest.json'; + /** + * The custom asset path resolver. + * + * @var callable|null + */ + protected $assetPathResolver = null; + /** * The script tag attributes resolvers. * @@ -160,6 +167,19 @@ public function useManifestFilename($filename) return $this; } + /** + * Resolve asset paths using the provided resolver. + * + * @param callable|null $urlResolver + * @return $this + */ + public function createAssetPathsUsing($resolver) + { + $this->assetPathResolver = $resolver; + + return $this; + } + /** * Get the Vite "hot" file path. * @@ -688,7 +708,7 @@ public function content($asset, $buildDirectory = null) */ protected function assetPath($path, $secure = null) { - return asset($path, $secure); + return ($this->assetPathResolver ?? asset(...))($path, $secure); } /** diff --git a/src/Illuminate/Foundation/helpers.php b/src/Illuminate/Foundation/helpers.php index e4c15edca725..c698bbdfaa8e 100644 --- a/src/Illuminate/Foundation/helpers.php +++ b/src/Illuminate/Foundation/helpers.php @@ -929,7 +929,7 @@ function trans($key = null, $replace = [], $locale = null) * Translates the given message based on a count. * * @param string $key - * @param \Countable|int|array $number + * @param \Countable|int|float|array $number * @param array $replace * @param string|null $locale * @return string diff --git a/src/Illuminate/Foundation/resources/health-up.blade.php b/src/Illuminate/Foundation/resources/health-up.blade.php new file mode 100644 index 000000000000..cb4689a89ed2 --- /dev/null +++ b/src/Illuminate/Foundation/resources/health-up.blade.php @@ -0,0 +1,52 @@ + + + + + + + {{ config('app.name', 'Laravel') }} + + + + + + + + + + + +
+
+
+
+ + +
+ +
+

Application up

+ +

+ HTTP request received. + + @if (defined('LARAVEL_START')) + Response successfully rendered in {{ round((microtime(true) - LARAVEL_START) * 1000) }}ms. + @endif +

+
+
+
+
+ + diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index d3411ce18777..4bcd13d21b09 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -36,6 +36,13 @@ class Factory */ protected $globalMiddleware = []; + /** + * The options to apply to every request. + * + * @var array + */ + protected $globalOptions = []; + /** * The stub callables that will handle requests. * @@ -123,6 +130,19 @@ public function globalResponseMiddleware($middleware) return $this; } + /** + * Set the options to apply to every request. + * + * @param array $options + * @return $this + */ + public function globalOptions($options) + { + $this->globalOptions = $options; + + return $this; + } + /** * Create a new response instance for use during stubbing. * @@ -400,7 +420,7 @@ public function recorded($callback = null) */ protected function newPendingRequest() { - return new PendingRequest($this, $this->globalMiddleware); + return (new PendingRequest($this, $this->globalMiddleware))->withOptions($this->globalOptions); } /** diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 6966a0f071bf..ea1894557183 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -78,7 +78,7 @@ class PendingRequest /** * The raw body for the request. * - * @var string + * @var \Psr\Http\Message\StreamInterface|string */ protected $pendingBody; @@ -260,7 +260,7 @@ public function baseUrl(string $url) /** * Attach a raw body to the request. * - * @param string $content + * @param \Psr\Http\Message\StreamInterface|string $content * @param string $contentType * @return $this */ @@ -601,13 +601,13 @@ public function connectTimeout(int $seconds) /** * Specify the number of times the request should be attempted. * - * @param int $times + * @param array|int $times * @param Closure|int $sleepMilliseconds * @param callable|null $when * @param bool $throw * @return $this */ - public function retry(int $times, Closure|int $sleepMilliseconds = 0, ?callable $when = null, bool $throw = true) + public function retry(array|int $times, Closure|int $sleepMilliseconds = 0, ?callable $when = null, bool $throw = true) { $this->tries = $times; $this->retryDelay = $sleepMilliseconds; @@ -922,7 +922,7 @@ public function send(string $method, string $url, array $options = []) } }); } catch (ConnectException $e) { - $this->dispatchConnectionFailedEvent(); + $this->dispatchConnectionFailedEvent(new Request($e->getRequest())); throw new ConnectionException($e->getMessage(), 0, $e); } @@ -1012,7 +1012,7 @@ protected function makePromise(string $method, string $url, array $options = [], }) ->otherwise(function (OutOfBoundsException|TransferException $e) { if ($e instanceof ConnectException) { - $this->dispatchConnectionFailedEvent(); + $this->dispatchConnectionFailedEvent(new Request($e->getRequest())); return new ConnectionException($e->getMessage(), 0, $e); } @@ -1465,8 +1465,7 @@ protected function dispatchRequestSendingEvent() */ protected function dispatchResponseReceivedEvent(Response $response) { - if (! ($dispatcher = $this->factory?->getDispatcher()) || - ! $this->request) { + if (! ($dispatcher = $this->factory?->getDispatcher()) || ! $this->request) { return; } @@ -1476,12 +1475,13 @@ protected function dispatchResponseReceivedEvent(Response $response) /** * Dispatch the ConnectionFailed event if a dispatcher is available. * + * @param \Illuminate\Http\Client\Request $request * @return void */ - protected function dispatchConnectionFailedEvent() + protected function dispatchConnectionFailedEvent(Request $request) { if ($dispatcher = $this->factory?->getDispatcher()) { - $dispatcher->dispatch(new ConnectionFailed($this->request)); + $dispatcher->dispatch(new ConnectionFailed($request)); } } diff --git a/src/Illuminate/Http/Middleware/TrustHosts.php b/src/Illuminate/Http/Middleware/TrustHosts.php index c2657f02756e..13dd1f00b62c 100644 --- a/src/Illuminate/Http/Middleware/TrustHosts.php +++ b/src/Illuminate/Http/Middleware/TrustHosts.php @@ -14,6 +14,13 @@ class TrustHosts */ protected $app; + /** + * The trusted hosts that have been configured to always be trusted. + * + * @var array|null + */ + protected static $alwaysTrust; + /** * Create a new middleware instance. * @@ -32,9 +39,9 @@ public function __construct(Application $app) */ public function hosts() { - return [ - $this->allSubdomainsOfApplicationUrl(), - ]; + return is_array(static::$alwaysTrust) + ? static::$alwaysTrust + : [$this->allSubdomainsOfApplicationUrl()]; } /** @@ -53,6 +60,24 @@ public function handle(Request $request, $next) return $next($request); } + /** + * Specify the hosts that should always be trusted. + * + * @param array $hosts + * @param bool $subdomains + * @return void + */ + public static function at(array $hosts, bool $subdomains = true) + { + if ($subdomains) { + if ($host = parse_url(config('app.url'), PHP_URL_HOST)) { + $hosts[] = '^(.+\.)?'.preg_quote($host).'$'; + } + } + + static::$alwaysTrust = $hosts; + } + /** * Determine if the application should specify trusted hosts. * @@ -75,4 +100,14 @@ protected function allSubdomainsOfApplicationUrl() return '^(.+\.)?'.preg_quote($host).'$'; } } + + /** + * Flush the state of the middleware. + * + * @return void + */ + public static function flushState() + { + static::$alwaysTrust = null; + } } diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index 12a2f9b3692e..4b7b0f62b9b1 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -12,10 +12,10 @@ class TrustProxies * * @var array|string|null */ - protected $proxies = '*'; + protected $proxies; /** - * The proxy header mappings. + * The trusted proxies headers for the application. * * @var int */ @@ -25,6 +25,20 @@ class TrustProxies Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB; + /** + * The proxies that have been configured to always be trusted. + * + * @var array|string|null + */ + protected static $alwaysTrustProxies; + + /** + * The proxies headers that have been configured to always be trusted. + * + * @var int|null + */ + protected static $alwaysTrustHeaders; + /** * Handle an incoming request. * @@ -96,11 +110,13 @@ protected function setTrustedProxyIpAddressesToTheCallingIp(Request $request) */ protected function getTrustedHeaderNames() { - if (is_int($this->headers)) { - return $this->headers; + $headers = $this->headers(); + + if (is_int($headers)) { + return $headers; } - return match ($this->headers) { + return match ($headers) { 'HEADER_X_FORWARDED_AWS_ELB' => Request::HEADER_X_FORWARDED_AWS_ELB, 'HEADER_FORWARDED' => Request::HEADER_FORWARDED, 'HEADER_X_FORWARDED_FOR' => Request::HEADER_X_FORWARDED_FOR, @@ -112,6 +128,16 @@ protected function getTrustedHeaderNames() }; } + /** + * Get the trusted headers. + * + * @return int + */ + protected function headers() + { + return static::$alwaysTrustHeaders ?: $this->headers; + } + /** * Get the trusted proxies. * @@ -119,6 +145,39 @@ protected function getTrustedHeaderNames() */ protected function proxies() { - return $this->proxies; + return static::$alwaysTrustProxies ?: $this->proxies; + } + + /** + * Specify the IP addresses of proxies that should always be trusted. + * + * @param array|string $proxies + * @return void + */ + public static function at(array|string $proxies) + { + static::$alwaysTrustProxies = $proxies; + } + + /** + * Specify the proxy headers that should always be trusted. + * + * @param int $headers + * @return void + */ + public static function withHeaders(int $headers) + { + static::$alwaysTrustHeaders = $headers; + } + + /** + * Flush the state of the middleware. + * + * @return void + */ + public static function flushState() + { + static::$alwaysTrustHeaders = null; + static::$alwaysTrustProxies = null; } } diff --git a/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php b/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php index 3f25ca052407..252fa8ba4e59 100644 --- a/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php +++ b/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php @@ -266,15 +266,17 @@ protected function whenLoaded($relationship, $value = null, $default = null) return value($default); } + $loadedValue = $this->resource->{$relationship}; + if (func_num_args() === 1) { - return $this->resource->{$relationship}; + return $loadedValue; } - if ($this->resource->{$relationship} === null) { + if ($loadedValue === null) { return; } - return value($value); + return value($value, $loadedValue); } /** @@ -293,7 +295,7 @@ public function whenCounted($relationship, $value = null, $default = null) $attribute = (string) Str::of($relationship)->snake()->finish('_count'); - if (! isset($this->resource->getAttributes()[$attribute])) { + if (! array_key_exists($attribute, $this->resource->getAttributes())) { return value($default); } @@ -320,9 +322,13 @@ public function whenCounted($relationship, $value = null, $default = null) */ public function whenAggregated($relationship, $column, $aggregate, $value = null, $default = null) { + if (func_num_args() < 5) { + $default = new MissingValue; + } + $attribute = (string) Str::of($relationship)->snake()->append('_')->append($aggregate)->append('_')->finish($column); - if (! isset($this->resource->getAttributes()[$attribute])) { + if (! array_key_exists($attribute, $this->resource->getAttributes())) { return value($default); } diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index f683ad8e8390..9222aedd597b 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -17,6 +17,7 @@ "php": "^8.2", "ext-filter": "*", "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8", "guzzlehttp/uri-template": "^1.0", "illuminate/collections": "^11.0", "illuminate/macroable": "^11.0", @@ -33,8 +34,7 @@ } }, "suggest": { - "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", - "guzzlehttp/guzzle": "Required to use the HTTP Client (^7.6)." + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image()." }, "extra": { "branch-alias": { diff --git a/src/Illuminate/Log/LogManager.php b/src/Illuminate/Log/LogManager.php index 60498f4ef06d..b3ece35d811c 100644 --- a/src/Illuminate/Log/LogManager.php +++ b/src/Illuminate/Log/LogManager.php @@ -501,6 +501,22 @@ public function sharedContext() return $this->sharedContext; } + /** + * Flush the log context on all currently resolved channels. + * + * @return $this + */ + public function withoutContext() + { + foreach ($this->channels as $channel) { + if (method_exists($channel, 'withoutContext')) { + $channel->withoutContext(); + } + } + + return $this; + } + /** * Flush the shared context. * @@ -614,7 +630,7 @@ public function getChannels() /** * System is unusable. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -629,7 +645,7 @@ public function emergency($message, array $context = []): void * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -643,7 +659,7 @@ public function alert($message, array $context = []): void * * Example: Application component unavailable, unexpected exception. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -656,7 +672,7 @@ public function critical($message, array $context = []): void * Runtime errors that do not require immediate action but should typically * be logged and monitored. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -671,7 +687,7 @@ public function error($message, array $context = []): void * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -683,7 +699,7 @@ public function warning($message, array $context = []): void /** * Normal but significant events. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -697,7 +713,7 @@ public function notice($message, array $context = []): void * * Example: User logs in, SQL logs. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -709,7 +725,7 @@ public function info($message, array $context = []): void /** * Detailed debug information. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -722,7 +738,7 @@ public function debug($message, array $context = []): void * Logs with an arbitrary level. * * @param mixed $level - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ diff --git a/src/Illuminate/Mail/MailManager.php b/src/Illuminate/Mail/MailManager.php index ecf094af2c15..80cab9856388 100644 --- a/src/Illuminate/Mail/MailManager.php +++ b/src/Illuminate/Mail/MailManager.php @@ -9,6 +9,7 @@ use Illuminate\Log\LogManager; use Illuminate\Mail\Transport\ArrayTransport; use Illuminate\Mail\Transport\LogTransport; +use Illuminate\Mail\Transport\ResendTransport; use Illuminate\Mail\Transport\SesTransport; use Illuminate\Mail\Transport\SesV2Transport; use Illuminate\Support\Arr; @@ -16,11 +17,13 @@ use Illuminate\Support\Str; use InvalidArgumentException; use Psr\Log\LoggerInterface; +use Resend; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; use Symfony\Component\Mailer\Transport\Dsn; use Symfony\Component\Mailer\Transport\FailoverTransport; +use Symfony\Component\Mailer\Transport\RoundRobinTransport; use Symfony\Component\Mailer\Transport\SendmailTransport; use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory; @@ -291,6 +294,19 @@ protected function addSesCredentials(array $config) return Arr::except($config, ['token']); } + /** + * Create an instance of the Resend Transport driver. + * + * @param array $config + * @return \Illuminate\Mail\Transport\ResendTransprot + */ + protected function createResendTransport(array $config) + { + return new ResendTransport( + Resend::client($this->app['config']->get('services.resend.key')), + ); + } + /** * Create an instance of the Symfony Mail Transport driver. * @@ -375,6 +391,34 @@ protected function createFailoverTransport(array $config) return new FailoverTransport($transports); } + /** + * Create an instance of the Symfony Roundrobin Transport driver. + * + * @param array $config + * @return \Symfony\Component\Mailer\Transport\RoundRobinTransport + */ + protected function createRoundrobinTransport(array $config) + { + $transports = []; + + foreach ($config['mailers'] as $name) { + $config = $this->getConfig($name); + + if (is_null($config)) { + throw new InvalidArgumentException("Mailer [{$name}] is not defined."); + } + + // Now, we will check if the "driver" key exists and if it does we will set + // the transport configuration parameter in order to offer compatibility + // with any Laravel <= 6.x application style mail configuration files. + $transports[] = $this->app['config']['mail.driver'] + ? $this->createSymfonyTransport(array_merge($config, ['transport' => $name])) + : $this->createSymfonyTransport($config); + } + + return new RoundRobinTransport($transports); + } + /** * Create an instance of the Log Transport driver. * diff --git a/src/Illuminate/Mail/Mailable.php b/src/Illuminate/Mail/Mailable.php index 7bdf38ad8541..48831c0e551a 100644 --- a/src/Illuminate/Mail/Mailable.php +++ b/src/Illuminate/Mail/Mailable.php @@ -347,7 +347,7 @@ public function buildViewData() } foreach ((new ReflectionClass($this))->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { - if ($property->getDeclaringClass()->getName() !== self::class) { + if ($property->isInitialized($this) && $property->getDeclaringClass()->getName() !== self::class) { $data[$property->getName()] = $property->getValue($this); } } @@ -1231,7 +1231,7 @@ public function assertTo($address, $name = null) PHPUnit::assertTrue( $this->hasTo($address, $name), - "Did not see expected recipient [{$recipient}] in email recipients." + "Did not see expected recipient [{$recipient}] in email 'to' recipients." ); return $this; @@ -1264,7 +1264,7 @@ public function assertHasCc($address, $name = null) PHPUnit::assertTrue( $this->hasCc($address, $name), - "Did not see expected recipient [{$recipient}] in email recipients." + "Did not see expected recipient [{$recipient}] in email 'cc' recipients." ); return $this; @@ -1285,7 +1285,7 @@ public function assertHasBcc($address, $name = null) PHPUnit::assertTrue( $this->hasBcc($address, $name), - "Did not see expected recipient [{$recipient}] in email recipients." + "Did not see expected recipient [{$recipient}] in email 'bcc' recipients." ); return $this; diff --git a/src/Illuminate/Mail/Mailer.php b/src/Illuminate/Mail/Mailer.php index 132d95683754..bb8a82069132 100755 --- a/src/Illuminate/Mail/Mailer.php +++ b/src/Illuminate/Mail/Mailer.php @@ -357,6 +357,21 @@ protected function sendMailable(MailableContract $mailable) : $mailable->mailer($this->name)->send($this); } + /** + * Send a new message synchronously using a view. + * + * @param \Illuminate\Contracts\Mail\Mailable|string|array $view + * @param array $data + * @param \Closure|string|null $callback + * @return \Illuminate\Mail\SentMessage|null + */ + public function sendNow($mailable, array $data = [], $callback = null) + { + return $mailable instanceof MailableContract + ? $mailable->mailer($this->name)->send($this) + : $this->send($mailable, $data, $callback); + } + /** * Parse the given view name or array. * @@ -450,7 +465,7 @@ protected function setGlobalToAndRemoveCcAndBcc($message) } /** - * Queue a new e-mail message for sending. + * Queue a new mail message for sending. * * @param \Illuminate\Contracts\Mail\Mailable|string|array $view * @param string|null $queue @@ -472,7 +487,7 @@ public function queue($view, $queue = null) } /** - * Queue a new e-mail message for sending on the given queue. + * Queue a new mail message for sending on the given queue. * * @param string $queue * @param \Illuminate\Contracts\Mail\Mailable $view @@ -484,7 +499,7 @@ public function onQueue($queue, $view) } /** - * Queue a new e-mail message for sending on the given queue. + * Queue a new mail message for sending on the given queue. * * This method didn't match rest of framework's "onQueue" phrasing. Added "onQueue". * @@ -498,7 +513,7 @@ public function queueOn($queue, $view) } /** - * Queue a new e-mail message for sending after (n) seconds. + * Queue a new mail message for sending after (n) seconds. * * @param \DateTimeInterface|\DateInterval|int $delay * @param \Illuminate\Contracts\Mail\Mailable $view @@ -519,7 +534,7 @@ public function later($delay, $view, $queue = null) } /** - * Queue a new e-mail message for sending after (n) seconds on the given queue. + * Queue a new mail message for sending after (n) seconds on the given queue. * * @param string $queue * @param \DateTimeInterface|\DateInterval|int $delay diff --git a/src/Illuminate/Mail/PendingMail.php b/src/Illuminate/Mail/PendingMail.php index 330b6438bada..1aa3d9a6cc2f 100644 --- a/src/Illuminate/Mail/PendingMail.php +++ b/src/Illuminate/Mail/PendingMail.php @@ -124,6 +124,17 @@ public function send(MailableContract $mailable) return $this->mailer->send($this->fill($mailable)); } + /** + * Send a new mailable message instance synchronously. + * + * @param \Illuminate\Contracts\Mail\Mailable $mailable + * @return \Illuminate\Mail\SentMessage|null + */ + public function sendNow(MailableContract $mailable) + { + return $this->mailer->sendNow($this->fill($mailable)); + } + /** * Push the given mailable onto the queue. * diff --git a/src/Illuminate/Mail/Transport/LogTransport.php b/src/Illuminate/Mail/Transport/LogTransport.php index 3d05a9956558..848733700586 100644 --- a/src/Illuminate/Mail/Transport/LogTransport.php +++ b/src/Illuminate/Mail/Transport/LogTransport.php @@ -2,6 +2,7 @@ namespace Illuminate\Mail\Transport; +use Illuminate\Support\Str; use Psr\Log\LoggerInterface; use Stringable; use Symfony\Component\Mailer\Envelope; @@ -34,17 +35,48 @@ public function __construct(LoggerInterface $logger) */ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage { - $string = $message->toString(); + $string = Str::of($message->toString()); - if (str_contains($string, 'Content-Transfer-Encoding: quoted-printable')) { - $string = quoted_printable_decode($string); + if ($string->contains('Content-Type: multipart/')) { + $boundary = $string + ->after('boundary=') + ->before("\r\n") + ->prepend('--') + ->append("\r\n"); + + $string = $string + ->explode($boundary) + ->map($this->decodeQuotedPrintableContent(...)) + ->implode($boundary); + } elseif ($string->contains('Content-Transfer-Encoding: quoted-printable')) { + $string = $this->decodeQuotedPrintableContent($string); } - $this->logger->debug($string); + $this->logger->debug((string) $string); return new SentMessage($message, $envelope ?? Envelope::create($message)); } + /** + * Decode the given quoted printable content. + * + * @param string $part + * @return string + */ + protected function decodeQuotedPrintableContent(string $part) + { + if (! str_contains($part, 'Content-Transfer-Encoding: quoted-printable')) { + return $part; + } + + [$headers, $content] = explode("\r\n\r\n", $part, 2); + + return implode("\r\n\r\n", [ + $headers, + quoted_printable_decode($content), + ]); + } + /** * Get the logger for the LogTransport instance. * diff --git a/src/Illuminate/Mail/Transport/ResendTransport.php b/src/Illuminate/Mail/Transport/ResendTransport.php new file mode 100644 index 000000000000..a62f4f0f4926 --- /dev/null +++ b/src/Illuminate/Mail/Transport/ResendTransport.php @@ -0,0 +1,129 @@ +getOriginalMessage()); + + $envelope = $message->getEnvelope(); + + $headers = []; + + $headersToBypass = ['from', 'to', 'cc', 'bcc', 'reply-to', 'sender', 'subject', 'content-type']; + + foreach ($email->getHeaders()->all() as $name => $header) { + if (in_array($name, $headersToBypass, true)) { + continue; + } + + $headers[$header->getName()] = $header->getBodyAsString(); + } + + $attachments = []; + + if ($email->getAttachments()) { + foreach ($email->getAttachments() as $attachment) { + $headers = $attachment->getPreparedHeaders(); + + $filename = $headers->getHeaderParameter('Content-Disposition', 'filename'); + + $item = [ + 'content' => str_replace("\r\n", '', $attachment->bodyToString()), + 'filename' => $filename, + ]; + + $attachments[] = $item; + } + } + + try { + $result = $this->resend->emails->send([ + 'from' => $envelope->getSender()->toString(), + 'to' => $this->stringifyAddresses($this->getRecipients($email, $envelope)), + 'cc' => $this->stringifyAddresses($email->getCc()), + 'bcc' => $this->stringifyAddresses($email->getBcc()), + 'reply_to' => $this->stringifyAddresses($email->getReplyTo()), + 'headers' => $headers, + 'subject' => $email->getSubject(), + 'html' => $email->getHtmlBody(), + 'text' => $email->getTextBody(), + 'attachments' => $attachments, + ]); + } catch (Exception $exception) { + throw new TransportException( + sprintf('Request to Resend API failed. Reason: %s.', $exception->getMessage()), + is_int($exception->getCode()) ? $exception->getCode() : 0, + $exception + ); + } + + $messageId = $result->id; + + $email->getHeaders()->addHeader('X-Resend-Email-ID', $messageId); + } + + /** + * Get the recipients without CC or BCC. + */ + protected function getRecipients(Email $email, Envelope $envelope): array + { + return array_filter($envelope->getRecipients(), function (Address $address) use ($email) { + return in_array($address, array_merge($email->getCc(), $email->getBcc()), true) === false; + }); + } + + /** + * Get the string representation of the transport. + */ + public function __toString(): string + { + return 'resend'; + } +} diff --git a/src/Illuminate/Mail/composer.json b/src/Illuminate/Mail/composer.json index ad0211e96d7c..cf00d7cc483c 100755 --- a/src/Illuminate/Mail/composer.json +++ b/src/Illuminate/Mail/composer.json @@ -37,6 +37,7 @@ }, "suggest": { "aws/aws-sdk-php": "Required to use the SES mail driver (^3.235.5).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", "symfony/http-client": "Required to use the Symfony API mail transports (^7.0).", "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0)." diff --git a/src/Illuminate/Mail/resources/views/html/message.blade.php b/src/Illuminate/Mail/resources/views/html/message.blade.php index f272460daa32..1a874fc26de5 100644 --- a/src/Illuminate/Mail/resources/views/html/message.blade.php +++ b/src/Illuminate/Mail/resources/views/html/message.blade.php @@ -21,7 +21,7 @@ {{-- Footer --}} -© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') +© {{ date('Y') }} {{ config('app.name') }}. {{ __('All rights reserved.') }} 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/Pagination/resources/views/simple-tailwind.blade.php b/src/Illuminate/Pagination/resources/views/simple-tailwind.blade.php index 6872cca360d5..ea02400f468c 100644 --- a/src/Illuminate/Pagination/resources/views/simple-tailwind.blade.php +++ b/src/Illuminate/Pagination/resources/views/simple-tailwind.blade.php @@ -2,22 +2,22 @@