Updated to v1.3.4

This commit is contained in:
Henry Whitaker
2020-05-07 12:12:08 +01:00
parent 3a99dc5495
commit d6f3c02bdb
80 changed files with 1364 additions and 227 deletions

View File

@@ -1,6 +1,6 @@
# Speedtest Tracker
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [![commit_freq](https://img.shields.io/github/commit-activity/m/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) ![version](https://img.shields.io/badge/version-v1.3.3-success) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [![commit_freq](https://img.shields.io/github/commit-activity/m/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) ![version](https://img.shields.io/badge/version-v1.3.4-success) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses the [speedtest-cli](https://github.com/sivel/speedtest-cli) package to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.

View File

@@ -1,6 +1,6 @@
# Speedtest Tracker
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) ![version](https://img.shields.io/badge/version-v1.3.2-success) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) ![version](https://img.shields.io/badge/version-v1.3.4-success) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses the [speedtest-cli](https://github.com/sivel/speedtest-cli) package to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.

View File

@@ -1,4 +1,10 @@
{
"1.3.4": [
{
"description": "Updated laravel framework",
"link": "https://github.com/henrywhitaker3/Speedtest-Tracker/pull/37"
}
],
"1.3.3": [
{
"description": "Updated jQuery",

View File

@@ -691,16 +691,16 @@
},
{
"name": "laravel/framework",
"version": "v7.9.2",
"version": "v7.10.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "757b155658ae6da429065ba8f22242fe599824f7"
"reference": "6e927e78aafd578d59c99608e7f0e23a5f7bfc5a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/757b155658ae6da429065ba8f22242fe599824f7",
"reference": "757b155658ae6da429065ba8f22242fe599824f7",
"url": "https://api.github.com/repos/laravel/framework/zipball/6e927e78aafd578d59c99608e7f0e23a5f7bfc5a",
"reference": "6e927e78aafd578d59c99608e7f0e23a5f7bfc5a",
"shasum": ""
},
"require": {
@@ -839,7 +839,7 @@
"framework",
"laravel"
],
"time": "2020-04-28T16:09:20+00:00"
"time": "2020-05-06T15:36:00+00:00"
},
{
"name": "laravel/slack-notification-channel",
@@ -1074,16 +1074,16 @@
},
{
"name": "league/commonmark",
"version": "1.4.2",
"version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "9e780d972185e4f737a03bade0fd34a9e67bbf31"
"reference": "412639f7cfbc0b31ad2455b2fe965095f66ae505"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/9e780d972185e4f737a03bade0fd34a9e67bbf31",
"reference": "9e780d972185e4f737a03bade0fd34a9e67bbf31",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/412639f7cfbc0b31ad2455b2fe965095f66ae505",
"reference": "412639f7cfbc0b31ad2455b2fe965095f66ae505",
"shasum": ""
},
"require": {
@@ -1144,7 +1144,7 @@
"md",
"parser"
],
"time": "2020-04-24T13:39:56+00:00"
"time": "2020-05-04T22:15:21+00:00"
},
{
"name": "league/flysystem",
@@ -1376,21 +1376,22 @@
},
{
"name": "nesbot/carbon",
"version": "2.32.2",
"version": "2.33.0",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc"
"reference": "4d93cb95a80d9ffbff4018fe58ae3b7dd7f4b99b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f10e22cf546704fab1db4ad4b9dedbc5c797a0dc",
"reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4d93cb95a80d9ffbff4018fe58ae3b7dd7f4b99b",
"reference": "4d93cb95a80d9ffbff4018fe58ae3b7dd7f4b99b",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.1.8 || ^8.0",
"symfony/polyfill-mbstring": "^1.0",
"symfony/translation": "^3.4 || ^4.0 || ^5.0"
},
"require-dev": {
@@ -1443,7 +1444,7 @@
"datetime",
"time"
],
"time": "2020-03-31T13:43:19+00:00"
"time": "2020-04-20T15:05:43+00:00"
},
{
"name": "nikic/php-parser",
@@ -3720,20 +3721,20 @@
},
{
"name": "vlucas/phpdotenv",
"version": "v4.1.4",
"version": "v4.1.5",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "feb6dad5ae24b1380827aee1629b730080fde500"
"reference": "539bb6927c101a5605d31d11a2d17185a2ce2bf1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/feb6dad5ae24b1380827aee1629b730080fde500",
"reference": "feb6dad5ae24b1380827aee1629b730080fde500",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/539bb6927c101a5605d31d11a2d17185a2ce2bf1",
"reference": "539bb6927c101a5605d31d11a2d17185a2ce2bf1",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0",
"php": "^5.5.9 || ^7.0 || ^8.0",
"phpoption/phpoption": "^1.7.2",
"symfony/polyfill-ctype": "^1.9"
},
@@ -3780,7 +3781,7 @@
"env",
"environment"
],
"time": "2020-04-12T15:20:09+00:00"
"time": "2020-05-02T14:08:57+00:00"
},
{
"name": "voku/portable-ascii",

View File

@@ -7,7 +7,7 @@ return [
|--------------------------------------------------------------------------
*/
'version' => '1.3.3',
'version' => '1.3.4',
/*
|--------------------------------------------------------------------------

View File

@@ -1,56 +1,19 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Composer
Upstream-Contact: Jordi Boggiano <j.boggiano@seld.be>
Source: https://github.com/composer/composer
Copyright (c) Nils Adermann, Jordi Boggiano
Files: *
Copyright: 2016, Nils Adermann <naderman@naderman.de>
2016, Jordi Boggiano <j.boggiano@seld.be>
License: Expat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
Files: src/Composer/Util/TlsHelper.php
Copyright: 2016, Nils Adermann <naderman@naderman.de>
2016, Jordi Boggiano <j.boggiano@seld.be>
2013, Evan Coury <me@evancoury.com>
License: Expat and BSD-2-Clause
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
License: BSD-2-Clause
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
.
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License: Expat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1042,6 +1042,7 @@ return array(
'Illuminate\\Cache\\Console\\CacheTableCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/Console/CacheTableCommand.php',
'Illuminate\\Cache\\Console\\ClearCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php',
'Illuminate\\Cache\\Console\\ForgetCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/Console/ForgetCommand.php',
'Illuminate\\Cache\\DatabaseLock' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/DatabaseLock.php',
'Illuminate\\Cache\\DatabaseStore' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/DatabaseStore.php',
'Illuminate\\Cache\\DynamoDbLock' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/DynamoDbLock.php',
'Illuminate\\Cache\\DynamoDbStore' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/DynamoDbStore.php',
@@ -1368,6 +1369,7 @@ return array(
'Illuminate\\Foundation\\Bus\\PendingChain' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Bus/PendingChain.php',
'Illuminate\\Foundation\\Bus\\PendingDispatch' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Bus/PendingDispatch.php',
'Illuminate\\Foundation\\ComposerScripts' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/ComposerScripts.php',
'Illuminate\\Foundation\\Console\\CastMakeCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/CastMakeCommand.php',
'Illuminate\\Foundation\\Console\\ChannelMakeCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/ChannelMakeCommand.php',
'Illuminate\\Foundation\\Console\\ClearCompiledCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php',
'Illuminate\\Foundation\\Console\\ClosureCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/ClosureCommand.php',
@@ -1762,10 +1764,12 @@ return array(
'Illuminate\\Support\\Traits\\ForwardsCalls' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php',
'Illuminate\\Support\\Traits\\Localizable' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Traits/Localizable.php',
'Illuminate\\Support\\Traits\\Macroable' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Traits/Macroable.php',
'Illuminate\\Support\\Traits\\ReflectsClosures' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Traits/ReflectsClosures.php',
'Illuminate\\Support\\Traits\\Tappable' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Traits/Tappable.php',
'Illuminate\\Support\\ViewErrorBag' => $vendorDir . '/laravel/framework/src/Illuminate/Support/ViewErrorBag.php',
'Illuminate\\Testing\\Assert' => $vendorDir . '/laravel/framework/src/Illuminate/Testing/Assert.php',
'Illuminate\\Testing\\Constraints\\ArraySubset' => $vendorDir . '/laravel/framework/src/Illuminate/Testing/Constraints/ArraySubset.php',
'Illuminate\\Testing\\Constraints\\CountInDatabase' => $vendorDir . '/laravel/framework/src/Illuminate/Testing/Constraints/CountInDatabase.php',
'Illuminate\\Testing\\Constraints\\HasInDatabase' => $vendorDir . '/laravel/framework/src/Illuminate/Testing/Constraints/HasInDatabase.php',
'Illuminate\\Testing\\Constraints\\SeeInOrder' => $vendorDir . '/laravel/framework/src/Illuminate/Testing/Constraints/SeeInOrder.php',
'Illuminate\\Testing\\Constraints\\SoftDeletedInDatabase' => $vendorDir . '/laravel/framework/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php',

View File

@@ -13,6 +13,9 @@ class ComposerAutoloaderInit5f38f71e2023aa5bb7a383f21d759786
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {

View File

@@ -1532,6 +1532,7 @@ class ComposerStaticInit5f38f71e2023aa5bb7a383f21d759786
'Illuminate\\Cache\\Console\\CacheTableCommand' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Cache/Console/CacheTableCommand.php',
'Illuminate\\Cache\\Console\\ClearCommand' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php',
'Illuminate\\Cache\\Console\\ForgetCommand' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Cache/Console/ForgetCommand.php',
'Illuminate\\Cache\\DatabaseLock' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Cache/DatabaseLock.php',
'Illuminate\\Cache\\DatabaseStore' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Cache/DatabaseStore.php',
'Illuminate\\Cache\\DynamoDbLock' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Cache/DynamoDbLock.php',
'Illuminate\\Cache\\DynamoDbStore' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Cache/DynamoDbStore.php',
@@ -1858,6 +1859,7 @@ class ComposerStaticInit5f38f71e2023aa5bb7a383f21d759786
'Illuminate\\Foundation\\Bus\\PendingChain' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/Bus/PendingChain.php',
'Illuminate\\Foundation\\Bus\\PendingDispatch' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/Bus/PendingDispatch.php',
'Illuminate\\Foundation\\ComposerScripts' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/ComposerScripts.php',
'Illuminate\\Foundation\\Console\\CastMakeCommand' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/Console/CastMakeCommand.php',
'Illuminate\\Foundation\\Console\\ChannelMakeCommand' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/Console/ChannelMakeCommand.php',
'Illuminate\\Foundation\\Console\\ClearCompiledCommand' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php',
'Illuminate\\Foundation\\Console\\ClosureCommand' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/Console/ClosureCommand.php',
@@ -2252,10 +2254,12 @@ class ComposerStaticInit5f38f71e2023aa5bb7a383f21d759786
'Illuminate\\Support\\Traits\\ForwardsCalls' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php',
'Illuminate\\Support\\Traits\\Localizable' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/Traits/Localizable.php',
'Illuminate\\Support\\Traits\\Macroable' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/Traits/Macroable.php',
'Illuminate\\Support\\Traits\\ReflectsClosures' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/Traits/ReflectsClosures.php',
'Illuminate\\Support\\Traits\\Tappable' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/Traits/Tappable.php',
'Illuminate\\Support\\ViewErrorBag' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/ViewErrorBag.php',
'Illuminate\\Testing\\Assert' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Testing/Assert.php',
'Illuminate\\Testing\\Constraints\\ArraySubset' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Testing/Constraints/ArraySubset.php',
'Illuminate\\Testing\\Constraints\\CountInDatabase' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Testing/Constraints/CountInDatabase.php',
'Illuminate\\Testing\\Constraints\\HasInDatabase' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Testing/Constraints/HasInDatabase.php',
'Illuminate\\Testing\\Constraints\\SeeInOrder' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Testing/Constraints/SeeInOrder.php',
'Illuminate\\Testing\\Constraints\\SoftDeletedInDatabase' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Testing/Constraints/SoftDeletedInDatabase.php',

View File

@@ -1106,17 +1106,17 @@
},
{
"name": "laravel/framework",
"version": "v7.9.2",
"version_normalized": "7.9.2.0",
"version": "v7.10.3",
"version_normalized": "7.10.3.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "757b155658ae6da429065ba8f22242fe599824f7"
"reference": "6e927e78aafd578d59c99608e7f0e23a5f7bfc5a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/757b155658ae6da429065ba8f22242fe599824f7",
"reference": "757b155658ae6da429065ba8f22242fe599824f7",
"url": "https://api.github.com/repos/laravel/framework/zipball/6e927e78aafd578d59c99608e7f0e23a5f7bfc5a",
"reference": "6e927e78aafd578d59c99608e7f0e23a5f7bfc5a",
"shasum": ""
},
"require": {
@@ -1224,7 +1224,7 @@
"symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).",
"wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)."
},
"time": "2020-04-28T16:09:20+00:00",
"time": "2020-05-06T15:36:00+00:00",
"type": "library",
"extra": {
"branch-alias": {
@@ -1499,17 +1499,17 @@
},
{
"name": "league/commonmark",
"version": "1.4.2",
"version_normalized": "1.4.2.0",
"version": "1.4.3",
"version_normalized": "1.4.3.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "9e780d972185e4f737a03bade0fd34a9e67bbf31"
"reference": "412639f7cfbc0b31ad2455b2fe965095f66ae505"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/9e780d972185e4f737a03bade0fd34a9e67bbf31",
"reference": "9e780d972185e4f737a03bade0fd34a9e67bbf31",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/412639f7cfbc0b31ad2455b2fe965095f66ae505",
"reference": "412639f7cfbc0b31ad2455b2fe965095f66ae505",
"shasum": ""
},
"require": {
@@ -1532,7 +1532,7 @@
"scrutinizer/ocular": "^1.5",
"symfony/finder": "^4.2"
},
"time": "2020-04-24T13:39:56+00:00",
"time": "2020-05-04T22:15:21+00:00",
"bin": [
"bin/commonmark"
],
@@ -1926,22 +1926,23 @@
},
{
"name": "nesbot/carbon",
"version": "2.32.2",
"version_normalized": "2.32.2.0",
"version": "2.33.0",
"version_normalized": "2.33.0.0",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc"
"reference": "4d93cb95a80d9ffbff4018fe58ae3b7dd7f4b99b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f10e22cf546704fab1db4ad4b9dedbc5c797a0dc",
"reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4d93cb95a80d9ffbff4018fe58ae3b7dd7f4b99b",
"reference": "4d93cb95a80d9ffbff4018fe58ae3b7dd7f4b99b",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.1.8 || ^8.0",
"symfony/polyfill-mbstring": "^1.0",
"symfony/translation": "^3.4 || ^4.0 || ^5.0"
},
"require-dev": {
@@ -1953,7 +1954,7 @@
"phpunit/phpunit": "^7.5 || ^8.0",
"squizlabs/php_codesniffer": "^3.4"
},
"time": "2020-03-31T13:43:19+00:00",
"time": "2020-04-20T15:05:43+00:00",
"bin": [
"bin/carbon"
],
@@ -5844,21 +5845,21 @@
},
{
"name": "vlucas/phpdotenv",
"version": "v4.1.4",
"version_normalized": "4.1.4.0",
"version": "v4.1.5",
"version_normalized": "4.1.5.0",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "feb6dad5ae24b1380827aee1629b730080fde500"
"reference": "539bb6927c101a5605d31d11a2d17185a2ce2bf1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/feb6dad5ae24b1380827aee1629b730080fde500",
"reference": "feb6dad5ae24b1380827aee1629b730080fde500",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/539bb6927c101a5605d31d11a2d17185a2ce2bf1",
"reference": "539bb6927c101a5605d31d11a2d17185a2ce2bf1",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0",
"php": "^5.5.9 || ^7.0 || ^8.0",
"phpoption/phpoption": "^1.7.2",
"symfony/polyfill-ctype": "^1.9"
},
@@ -5872,7 +5873,7 @@
"ext-filter": "Required to use the boolean validator.",
"ext-pcre": "Required to use most of the library."
},
"time": "2020-04-12T15:20:09+00:00",
"time": "2020-05-02T14:08:57+00:00",
"type": "library",
"extra": {
"branch-alias": {

View File

@@ -214,7 +214,11 @@ class CacheManager implements FactoryContract
return $this->repository(
new DatabaseStore(
$connection, $config['table'], $this->getPrefix($config)
$connection,
$config['table'],
$this->getPrefix($config),
$config['lock_table'] ?? 'cache_locks',
$config['lock_lottery'] ?? [2, 100]
)
);
}

View File

@@ -0,0 +1,138 @@
<?php
namespace Illuminate\Cache;
use Illuminate\Database\Connection;
use Illuminate\Database\QueryException;
class DatabaseLock extends Lock
{
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* The database table name.
*
* @var string
*/
protected $table;
/**
* The prune probability odds.
*
* @var array
*/
protected $lottery;
/**
* Create a new lock instance.
*
* @param \Illuminate\Database\Connection $connection
* @param string $table
* @param string $name
* @param int $seconds
* @param string|null $owner
* @param array $lottery
* @return void
*/
public function __construct(Connection $connection, $table, $name, $seconds, $owner = null, $lottery = [2, 100])
{
parent::__construct($name, $seconds, $owner);
$this->connection = $connection;
$this->table = $table;
$this->lottery = $lottery;
}
/**
* Attempt to acquire the lock.
*
* @return bool
*/
public function acquire()
{
$acquired = false;
try {
$this->connection->table($this->table)->insert([
'key' => $this->name,
'owner' => $this->owner,
'expiration' => $this->expiresAt(),
]);
$acquired = true;
} catch (QueryException $e) {
$updated = $this->connection->table($this->table)
->where('key', $this->name)
->where(function ($query) {
return $query->where('owner', $this->owner)->orWhere('expiration', '<=', time());
})->update([
'owner' => $this->owner,
'expiration' => $this->expiresAt(),
]);
$acquired = $updated >= 1;
}
if (random_int(1, $this->lottery[1]) <= $this->lottery[0]) {
$this->connection->table($this->table)->where('expiration', '<=', time())->delete();
}
return $acquired;
}
/**
* Get the UNIX timestamp indicating when the lock should expire.
*
* @return int
*/
protected function expiresAt()
{
return $this->seconds > 0 ? time() + $this->seconds : now()->addDays(1)->getTimestamp();
}
/**
* Release the lock.
*
* @return bool
*/
public function release()
{
if ($this->isOwnedByCurrentProcess()) {
$this->connection->table($this->table)
->where('key', $this->name)
->where('owner', $this->owner)
->delete();
return true;
}
return false;
}
/**
* Releases this lock in disregard of ownership.
*
* @return void
*/
public function forceRelease()
{
$this->connection->table($this->table)
->where('key', $this->name)
->delete();
}
/**
* Returns the owner value written into the driver for this lock.
*
* @return string
*/
protected function getCurrentOwner()
{
return optional($this->connection->table($this->table)->where('key', $this->name)->first())->owner;
}
}

View File

@@ -35,19 +35,41 @@ class DatabaseStore implements Store
*/
protected $prefix;
/**
* The name of the cache locks table.
*
* @var string
*/
protected $lockTable;
/**
* A array representation of the lock lottery odds.
*
* @var array
*/
protected $lockLottery;
/**
* Create a new database store.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param string $table
* @param string $prefix
* @param string $lockTable
* @param array $lockLottery
* @return void
*/
public function __construct(ConnectionInterface $connection, $table, $prefix = '')
public function __construct(ConnectionInterface $connection,
$table,
$prefix = '',
$lockTable = 'cache_locks',
$lockLottery = [2, 100])
{
$this->table = $table;
$this->prefix = $prefix;
$this->connection = $connection;
$this->lockTable = $lockTable;
$this->lockLottery = $lockLottery;
}
/**
@@ -205,6 +227,38 @@ class DatabaseStore implements Store
return $this->put($key, $value, 315360000);
}
/**
* Get a lock instance.
*
* @param string $name
* @param int $seconds
* @param string|null $owner
* @return \Illuminate\Contracts\Cache\Lock
*/
public function lock($name, $seconds = 0, $owner = null)
{
return new DatabaseLock(
$this->connection,
$this->lockTable,
$this->prefix.$name,
$seconds,
$owner,
$this->lockLottery
);
}
/**
* Restore a lock instance using the owner identifier.
*
* @param string $name
* @param string $owner
* @return \Illuminate\Contracts\Cache\Lock
*/
public function restoreLock($name, $owner)
{
return $this->lock($name, 0, $owner);
}
/**
* Remove an item from the cache.
*

View File

@@ -32,6 +32,13 @@ abstract class Lock implements LockContract
*/
protected $owner;
/**
* The number of milliseconds to wait before re-attempting to acquire a lock while blocking.
*
* @var int
*/
protected $sleepMilliseconds = 250;
/**
* Create a new lock instance.
*
@@ -107,7 +114,7 @@ abstract class Lock implements LockContract
$starting = $this->currentTime();
while (! $this->acquire()) {
usleep(250 * 1000);
usleep($this->sleepMilliseconds * 1000);
if ($this->currentTime() - $seconds >= $starting) {
throw new LockTimeoutException;
@@ -144,4 +151,17 @@ abstract class Lock implements LockContract
{
return $this->getCurrentOwner() === $this->owner;
}
/**
* Specify the number of milliseconds to sleep in between blocked lock aquisition attempts.
*
* @param int $milliseconds
* @return $this
*/
public function betweenBlockedAttemptsSleepFor($milliseconds)
{
$this->sleepMilliseconds = $milliseconds;
return $this;
}
}

View File

@@ -13,12 +13,13 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\Traits\ReflectsClosures;
use Psr\Http\Client\ClientExceptionInterface;
use Symfony\Component\Process\Process;
class Event
{
use Macroable, ManagesFrequencies;
use Macroable, ManagesFrequencies, ReflectsClosures;
/**
* The command string.
@@ -731,6 +732,20 @@ class Event
return $this;
}
/**
* Register a callback that uses the output after the job runs.
*
* @param \Closure $callback
* @param bool $onlyIfOutputExists
* @return $this
*/
public function thenWithOutput(Closure $callback, $onlyIfOutputExists = false)
{
$this->ensureOutputIsBeingCaptured();
return $this->then($this->withOutputCallback($callback, $onlyIfOutputExists));
}
/**
* Register a callback to be called if the operation succeeds.
*
@@ -746,6 +761,20 @@ class Event
});
}
/**
* Register a callback that uses the output if the operation succeeds.
*
* @param \Closure $callback
* @param bool $onlyIfOutputExists
* @return $this
*/
public function onSuccessWithOutput(Closure $callback, $onlyIfOutputExists = false)
{
$this->ensureOutputIsBeingCaptured();
return $this->onSuccess($this->withOutputCallback($callback, $onlyIfOutputExists));
}
/**
* Register a callback to be called if the operation fails.
*
@@ -761,6 +790,38 @@ class Event
});
}
/**
* Register a callback that uses the output if the operation fails.
*
* @param \Closure $callback
* @param bool $onlyIfOutputExists
* @return $this
*/
public function onFailureWithOutput(Closure $callback, $onlyIfOutputExists = false)
{
$this->ensureOutputIsBeingCaptured();
return $this->onFailure($this->withOutputCallback($callback, $onlyIfOutputExists));
}
/**
* Get a callback that provides output.
*
* @param \Closure $callback
* @param bool $onlyIfOutputExists
* @return \Closure
*/
protected function withOutputCallback(Closure $callback, $onlyIfOutputExists = false)
{
return function (Container $container) use ($callback, $onlyIfOutputExists) {
$output = $this->output && file_exists($this->output) ? file_get_contents($this->output) : '';
return $onlyIfOutputExists && empty($output)
? null
: $container->call($callback, ['output' => $output]);
};
}
/**
* Set the human-friendly description of the event.
*

View File

@@ -11,6 +11,7 @@ use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\CallQueuedClosure;
use Illuminate\Support\ProcessUtils;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use RuntimeException;
@@ -199,10 +200,10 @@ class Schedule
{
return collect($parameters)->map(function ($value, $key) {
if (is_array($value)) {
$value = collect($value)->map(function ($value) {
return ProcessUtils::escapeArgument($value);
})->implode(' ');
} elseif (! is_numeric($value) && ! preg_match('/^(-.$|--.*)/i', $value)) {
return $this->compileArrayInput($key, $value);
}
if (! is_numeric($value) && ! preg_match('/^(-.$|--.*)/i', $value)) {
$value = ProcessUtils::escapeArgument($value);
}
@@ -210,6 +211,32 @@ class Schedule
})->implode(' ');
}
/**
* Compile array input for a command.
*
* @param string|int $key
* @param array $value
* @return string
*/
public function compileArrayInput($key, $value)
{
$value = collect($value)->map(function ($value) {
return ProcessUtils::escapeArgument($value);
});
if (Str::startsWith($key, '--')) {
$value = $value->map(function ($value) use ($key) {
return "{$key}={$value}";
});
} elseif (Str::startsWith($key, '-')) {
$value = $value->map(function ($value) use ($key) {
return "{$key} {$value}";
});
}
return $value->implode(' ');
}
/**
* Determine if the server is allowed to run this event.
*

View File

@@ -22,7 +22,7 @@ interface CastsAttributes
* @param string $key
* @param mixed $value
* @param array $attributes
* @return array
* @return array|string
*/
public function set($model, string $key, $value, array $attributes);
}

View File

@@ -37,7 +37,7 @@ interface ResponseFactory
/**
* Create a new JSON response instance.
*
* @param string|array|object $data
* @param mixed $data
* @param int $status
* @param array $headers
* @param int $options
@@ -49,7 +49,7 @@ interface ResponseFactory
* Create a new JSONP response instance.
*
* @param string $callback
* @param string|array|object $data
* @param mixed $data
* @param int $status
* @param array $headers
* @param int $options

View File

@@ -13,6 +13,13 @@ use Illuminate\Support\ServiceProvider;
class DatabaseServiceProvider extends ServiceProvider
{
/**
* The array of resolved Faker instances.
*
* @var array
*/
protected static $fakers = [];
/**
* Bootstrap the application events.
*
@@ -75,7 +82,15 @@ class DatabaseServiceProvider extends ServiceProvider
protected function registerEloquentFactory()
{
$this->app->singleton(FakerGenerator::class, function ($app, $parameters) {
return FakerFactory::create($parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US'));
$locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US');
if (! isset(static::$fakers[$locale])) {
static::$fakers[$locale] = FakerFactory::create($locale);
}
static::$fakers[$locale]->unique(true);
return static::$fakers[$locale];
});
$this->app->singleton(EloquentFactory::class, function ($app) {

View File

@@ -44,6 +44,7 @@ trait DetectsLostConnections
'running with the --read-only option so it cannot execute this statement',
'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',
]);
}
}

View File

@@ -890,6 +890,17 @@ class Builder
$this->onDelete = $callback;
}
/**
* Determine if the given model has a scope.
*
* @param string $scope
* @return bool
*/
public function hasNamedScope($scope)
{
return $this->model && $this->model->hasNamedScope($scope);
}
/**
* Call the given local model scopes.
*
@@ -911,10 +922,7 @@ class Builder
// Next we'll pass the scope callback to the callScope method which will take
// care of grouping the "wheres" properly so the logical order doesn't get
// messed up when adding scopes. Then we'll return back out the builder.
$builder = $builder->callScope(
[$this->model, 'scope'.ucfirst($scope)],
(array) $parameters
);
$builder = $builder->callNamedScope($scope, (array) $parameters);
}
return $builder;
@@ -965,7 +973,7 @@ class Builder
* @param array $parameters
* @return mixed
*/
protected function callScope(callable $scope, $parameters = [])
protected function callScope(callable $scope, array $parameters = [])
{
array_unshift($parameters, $this);
@@ -986,6 +994,20 @@ class Builder
return $result;
}
/**
* Apply the given named scope on the current builder instance.
*
* @param string $scope
* @param array $parameters
* @return mixed
*/
protected function callNamedScope($scope, array $parameters = [])
{
return $this->callScope(function (...$parameters) use ($scope) {
return $this->model->callNamedScope($scope, $parameters);
}, $parameters);
}
/**
* Nest where conditions by slicing them at the given where count.
*
@@ -1373,8 +1395,8 @@ class Builder
return call_user_func_array(static::$macros[$method], $parameters);
}
if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
return $this->callScope([$this->model, $scope], $parameters);
if ($this->hasNamedScope($method)) {
return $this->callNamedScope($method, $parameters);
}
if (in_array($method, $this->passthru)) {

View File

@@ -568,7 +568,19 @@ class Collection extends BaseCollection implements QueueableCollection
*/
public function getQueueableRelations()
{
return $this->isNotEmpty() ? $this->first()->getQueueableRelations() : [];
if ($this->isEmpty()) {
return [];
}
$relations = $this->map->getQueueableRelations()->all();
if (count($relations) === 0 || $relations === [[]]) {
return [];
} elseif (count($relations) === 1) {
return $relations[0];
} else {
return array_intersect(...$relations);
}
}
/**

View File

@@ -50,6 +50,19 @@ trait GuardsAttributes
return $this;
}
/**
* Merge new fillable attributes with existing fillable attributes on the model.
*
* @param array $fillable
* @return $this
*/
public function mergeFillable(array $fillable)
{
$this->fillable = array_merge($this->fillable, $fillable);
return $this;
}
/**
* Get the guarded attributes for the model.
*
@@ -73,6 +86,19 @@ trait GuardsAttributes
return $this;
}
/**
* Merge new guarded attributes with existing guarded attributes on the model.
*
* @param array $guarded
* @return $this
*/
public function mergeGuarded(array $guarded)
{
$this->guarded = array_merge($this->guarded, $guarded);
return $this;
}
/**
* Disable all mass assignable restrictions.
*

View File

@@ -369,8 +369,12 @@ trait HasRelationships
$secondKey = $secondKey ?: $through->getForeignKey();
return $this->newHasManyThrough(
$this->newRelatedInstance($related)->newQuery(), $this, $through,
$firstKey, $secondKey, $localKey ?: $this->getKeyName(),
$this->newRelatedInstance($related)->newQuery(),
$this,
$through,
$firstKey,
$secondKey,
$localKey ?: $this->getKeyName(),
$secondLocalKey ?: $through->getKeyName()
);
}

View File

@@ -204,7 +204,7 @@ trait QueriesRelationships
$types = (array) $types;
if ($types === ['*']) {
$types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->all();
$types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all();
foreach ($types as &$type) {
$type = Relation::getMorphedModel($type) ?? $type;

View File

@@ -11,6 +11,7 @@ use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection as BaseCollection;
@@ -871,7 +872,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
/**
* Destroy the models for the given IDs.
*
* @param \Illuminate\Support\Collection|array|int $ids
* @param \Illuminate\Support\Collection|array|int|string $ids
* @return int
*/
public static function destroy($ids)
@@ -1107,6 +1108,29 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
: Pivot::fromAttributes($parent, $attributes, $table, $exists);
}
/**
* Determine if the model has a given scope.
*
* @param string $scope
* @return bool
*/
public function hasNamedScope($scope)
{
return method_exists($this, 'scope'.ucfirst($scope));
}
/**
* Apply the given named scope if possible.
*
* @param string $scope
* @param array $parameters
* @return mixed
*/
public function callNamedScope($scope, array $parameters = [])
{
return $this->{'scope'.ucfirst($scope)}(...$parameters);
}
/**
* Convert the model instance to an array.
*
@@ -1522,7 +1546,13 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
public function resolveChildRouteBinding($childType, $value, $field)
{
return $this->{Str::plural(Str::camel($childType))}()->where($field, $value)->first();
$relationship = $this->{Str::plural(Str::camel($childType))}();
if ($relationship instanceof HasManyThrough) {
return $relationship->where($relationship->getRelated()->getTable().'.'.$field, $value)->first();
} else {
return $relationship->where($field, $value)->first();
}
}
/**

View File

@@ -23,7 +23,7 @@ class RelationNotFoundException extends RuntimeException
/**
* Create a new exception instance.
*
* @param mixed $model
* @param object $model
* @param string $relation
* @return static
*/
@@ -33,7 +33,7 @@ class RelationNotFoundException extends RuntimeException
$instance = new static("Call to undefined relationship [{$relation}] on model [{$class}].");
$instance->model = $model;
$instance->model = $class;
$instance->relation = $relation;
return $instance;

View File

@@ -77,7 +77,7 @@ trait AsPivot
$instance->timestamps = $instance->hasTimestampAttributes($attributes);
$instance->setRawAttributes($attributes, true);
$instance->setRawAttributes($attributes, $exists);
return $instance;
}

View File

@@ -115,7 +115,9 @@ class HasManyThrough extends Relation
$query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $farKey);
if ($this->throughParentSoftDeletes()) {
$query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
$query->withGlobalScope('SoftDeletableHasManyThrough', function ($query) {
$query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
});
}
}
@@ -139,6 +141,18 @@ class HasManyThrough extends Relation
return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent));
}
/**
* Indicate that trashed "through" parents should be included in the query.
*
* @return $this
*/
public function withTrashedParents()
{
$this->query->withoutGlobalScope('SoftDeletableHasManyThrough');
return $this;
}
/**
* Set the constraints for an eager load of the relation.
*

View File

@@ -151,7 +151,11 @@ class MorphTo extends BelongsTo
{
$class = Model::getActualClassNameForMorph($type);
return new $class;
return tap(new $class, function ($instance) {
if (! $instance->getConnectionName()) {
$instance->setConnection($this->getConnection()->getName());
}
});
}
/**

View File

@@ -2238,9 +2238,7 @@ class Builder
// Once we have run the pagination count query, we will get the resulting count and
// take into account what type of query it was. When there is a group by we will
// just return the count of the entire results set since that will be correct.
if (isset($this->groups)) {
return count($results);
} elseif (! isset($results[0])) {
if (! isset($results[0])) {
return 0;
} elseif (is_object($results[0])) {
return (int) $results[0]->aggregate;
@@ -2257,6 +2255,20 @@ class Builder
*/
protected function runPaginationCountQuery($columns = ['*'])
{
if ($this->groups || $this->havings) {
$clone = $this->cloneForPaginationCount();
if (is_null($clone->columns) && ! empty($this->joins)) {
$clone->select($this->from.'.*');
}
return $this->newQuery()
->from(new Expression('('.$clone->toSql().') as '.$this->grammar->wrap('aggregate_table')))
->mergeBindings($clone)
->setAggregate('count', $this->withoutSelectAliases($columns))
->get()->all();
}
$without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
return $this->cloneWithout($without)
@@ -2265,6 +2277,17 @@ class Builder
->get()->all();
}
/**
* Clone the existing query instance for usage in a pagination subquery.
*
* @return self
*/
protected function cloneForPaginationCount()
{
return $this->cloneWithout(['orders', 'limit', 'offset'])
->cloneWithoutBindings(['order']);
}
/**
* Remove the column aliases since they will break count queries.
*

View File

@@ -4,6 +4,7 @@ namespace Illuminate\Database\Query\Grammars;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class SqlServerGrammar extends Grammar
{
@@ -232,6 +233,23 @@ class SqlServerGrammar extends Grammar
return ">= {$start}";
}
/**
* Compile a delete statement without joins into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param string $table
* @param string $where
* @return string
*/
protected function compileDeleteWithoutJoins(Builder $query, $table, $where)
{
$sql = parent::compileDeleteWithoutJoins($query, $table, $where);
return ! is_null($query->limit) && $query->limit > 0 && $query->offset <= 0
? Str::replaceFirst('delete', 'delete top ('.$query->limit.')', $sql)
: $sql;
}
/**
* Compile the random statement into SQL.
*

View File

@@ -125,7 +125,7 @@ class ChangeColumn
$options['length'] = static::calculateDoctrineTextLength($fluent['type']);
}
if (in_array($fluent['type'], ['json', 'binary'])) {
if (static::doesntNeedCharacterOptions($fluent['type'])) {
$options['customSchemaOptions'] = [
'collation' => '',
'charset' => '',
@@ -185,6 +185,30 @@ class ChangeColumn
}
}
/**
* 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',
'date',
'decimal',
'double',
'float',
'integer',
'json',
'mediumInteger',
'smallInteger',
'time',
'tinyInteger',
]);
}
/**
* Get the matching Doctrine option for a given Fluent attribute name.
*

View File

@@ -33,7 +33,7 @@ class Application extends Container implements ApplicationContract, CachesConfig
*
* @var string
*/
const VERSION = '7.9.2';
const VERSION = '7.10.3';
/**
* The base path for the Laravel installation.

View File

@@ -0,0 +1,50 @@
<?php
namespace Illuminate\Foundation\Console;
use Illuminate\Console\GeneratorCommand;
class CastMakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'make:cast';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new custom Eloquent cast class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Cast';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/cast.stub';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Casts';
}
}

View File

@@ -35,7 +35,8 @@ class EventMakeCommand extends GeneratorCommand
*/
protected function alreadyExists($rawName)
{
return class_exists($rawName);
return class_exists($rawName) ||
$this->files->exists($this->getPath($this->qualifyClass($rawName)));
}
/**

View File

@@ -0,0 +1,36 @@
<?php
namespace DummyNamespace;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class DummyClass implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return array
*/
public function get($model, $key, $value, $attributes)
{
return $value;
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $value
* @param array $attributes
* @return string
*/
public function set($model, $key, $value, $attributes)
{
return $value;
}
}

View File

@@ -106,8 +106,6 @@ class PackageManifest
$this->build();
}
$this->files->get($this->manifestPath);
return $this->manifest = file_exists($this->manifestPath) ?
$this->files->getRequire($this->manifestPath) : [];
}

View File

@@ -13,6 +13,7 @@ use Illuminate\Database\Console\Factories\FactoryMakeCommand;
use Illuminate\Database\Console\Seeds\SeedCommand;
use Illuminate\Database\Console\Seeds\SeederMakeCommand;
use Illuminate\Database\Console\WipeCommand;
use Illuminate\Foundation\Console\CastMakeCommand;
use Illuminate\Foundation\Console\ChannelMakeCommand;
use Illuminate\Foundation\Console\ClearCompiledCommand;
use Illuminate\Foundation\Console\ComponentMakeCommand;
@@ -118,6 +119,7 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid
*/
protected $devCommands = [
'CacheTable' => 'command.cache.table',
'CastMake' => 'command.cast.make',
'ChannelMake' => 'command.channel.make',
'ComponentMake' => 'command.component.make',
'ConsoleMake' => 'command.console.make',
@@ -212,6 +214,18 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid
});
}
/**
* Register the command.
*
* @return void
*/
protected function registerCastMakeCommand()
{
$this->app->singleton('command.cast.make', function ($app) {
return new CastMakeCommand($app['files']);
});
}
/**
* Register the command.
*

View File

@@ -5,6 +5,7 @@ namespace Illuminate\Foundation\Testing\Concerns;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Arr;
use Illuminate\Testing\Constraints\CountInDatabase;
use Illuminate\Testing\Constraints\HasInDatabase;
use Illuminate\Testing\Constraints\SoftDeletedInDatabase;
use PHPUnit\Framework\Constraint\LogicalNot as ReverseConstraint;
@@ -47,6 +48,23 @@ trait InteractsWithDatabase
return $this;
}
/**
* Assert the count of table entries.
*
* @param string $table
* @param int $count
* @param string|null $connection
* @return $this
*/
protected function assertDatabaseCount($table, int $count, $connection = null)
{
$this->assertThat(
$table, new CountInDatabase($this->getConnection($connection), $count)
);
return $this;
}
/**
* Assert the given record has been deleted.
*

View File

@@ -144,6 +144,16 @@ class Response implements ArrayAccess
return $this->status() >= 300 && $this->status() < 400;
}
/**
* Determine if the response indicates a client or server error occurred.
*
* @return bool
*/
public function failed()
{
return $this->serverError() || $this->clientError();
}
/**
* Determine if the response indicates a client error occurred.
*

View File

@@ -294,7 +294,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
/**
* Get the client user agent.
*
* @return string
* @return string|null
*/
public function userAgent()
{

View File

@@ -232,6 +232,10 @@ class MailManager implements FactoryContract
$transport->setTimeout($config['timeout']);
}
if (isset($config['auth_mode'])) {
$transport->setAuthMode($config['auth_mode']);
}
return $transport;
}

View File

@@ -358,7 +358,7 @@ class Mailer implements MailerContract, MailQueueContract
if (isset($plain)) {
$method = isset($view) ? 'addPart' : 'setBody';
$message->$method($this->renderView($plain, $data), 'text/plain');
$message->$method($this->renderView($plain, $data) ?: ' ', 'text/plain');
}
if (isset($raw)) {

View File

@@ -144,6 +144,8 @@ abstract class AbstractRouteCollection implements Countable, IteratorAggregate,
'defaults' => $route->defaults,
'wheres' => $route->wheres,
'bindingFields' => $route->bindingFields(),
'lockSeconds' => $route->locksFor(),
'waitSeconds' => $route->waitsFor(),
];
}

View File

@@ -297,7 +297,8 @@ class CompiledRouteCollection extends AbstractRouteCollection
->setFallback($attributes['fallback'])
->setDefaults($attributes['defaults'])
->setWheres($attributes['wheres'])
->setBindingFields($attributes['bindingFields']);
->setBindingFields($attributes['bindingFields'])
->block($attributes['lockSeconds'] ?? null, $attributes['waitSeconds'] ?? null);
}
/**

View File

@@ -92,6 +92,20 @@ class Route
*/
protected $originalParameters;
/**
* Indicates the maximum number of seconds the route should acquire a session lock for.
*
* @var int|null
*/
protected $lockSeconds;
/**
* Indicates the maximum number of seconds the route should wait while attempting to acquire a session lock.
*
* @var int|null
*/
protected $waitSeconds;
/**
* The computed gathered middleware.
*
@@ -972,6 +986,51 @@ class Route
return (array) ($this->action['excluded_middleware'] ?? []);
}
/**
* Specify that the route should not allow concurrent requests from the same session.
*
* @param int|null $lockSeconds
* @param int|null $waitSeconds
* @return $this
*/
public function block($lockSeconds = 10, $waitSeconds = 10)
{
$this->lockSeconds = $lockSeconds;
$this->waitSeconds = $waitSeconds;
return $this;
}
/**
* Specify that the route should allow concurrent requests from the same session.
*
* @return $this
*/
public function withoutBlocking()
{
return $this->block(null, null);
}
/**
* Get the maximum number of seconds the route's session lock should be held for.
*
* @return int|null
*/
public function locksFor()
{
return $this->lockSeconds;
}
/**
* Get the maximum number of seconds to wait while attempting to acquire a session lock.
*
* @return int|null
*/
public function waitsFor()
{
return $this->waitSeconds;
}
/**
* Get the dispatcher for the route's controller.
*

View File

@@ -20,15 +20,24 @@ class StartSession
*/
protected $manager;
/**
* The callback that can resolve an instance of the cache factory.
*
* @var callable|null
*/
protected $cacheFactoryResolver;
/**
* Create a new session middleware.
*
* @param \Illuminate\Session\SessionManager $manager
* @param callable|null $cacheFactoryResolver
* @return void
*/
public function __construct(SessionManager $manager)
public function __construct(SessionManager $manager, callable $cacheFactoryResolver = null)
{
$this->manager = $manager;
$this->cacheFactoryResolver = $cacheFactoryResolver;
}
/**
@@ -44,11 +53,62 @@ class StartSession
return $next($request);
}
$session = $this->getSession($request);
if ($this->manager->shouldBlock() ||
($request->route() && $request->route()->locksFor())) {
return $this->handleRequestWhileBlocking($request, $session, $next);
} else {
return $this->handleStatefulRequest($request, $session, $next);
}
}
/**
* Handle the given request within session state.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Session\Session $session
* @param \Closure $next
* @return mixed
*/
protected function handleRequestWhileBlocking(Request $request, $session, Closure $next)
{
$lockFor = $request->route() && $request->route()->locksFor()
? $request->route()->locksFor()
: 10;
$lock = $this->cache($this->manager->blockDriver())
->lock('session:'.$session->getId(), $lockFor)
->betweenBlockedAttemptsSleepFor(50);
try {
$lock->block(
! is_null($request->route()->waitsFor())
? $request->route()->waitsFor()
: 10
);
return $this->handleStatefulRequest($request, $session, $next);
} finally {
optional($lock)->release();
}
}
/**
* Handle the given request within session state.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Session\Session $session
* @param \Closure $next
* @return mixed
*/
protected function handleStatefulRequest(Request $request, $session, Closure $next)
{
// If a session driver has been configured, we will need to start the session here
// so that the data is ready for an application. Note that the Laravel sessions
// do not make use of PHP "native" sessions in any way since they are crappy.
$request->setLaravelSession(
$session = $this->startSession($request)
$this->startSession($request, $session)
);
$this->collectGarbage($session);
@@ -71,11 +131,12 @@ class StartSession
* Start the session for the given request.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Session\Session $session
* @return \Illuminate\Contracts\Session\Session
*/
protected function startSession(Request $request)
protected function startSession(Request $request, $session)
{
return tap($this->getSession($request), function ($session) use ($request) {
return tap($session, function ($session) use ($request) {
$session->setRequestOnHandler($request);
$session->start();
@@ -216,4 +277,15 @@ class StartSession
return ! is_null($config['driver'] ?? null);
}
/**
* Resolve the given cache driver.
*
* @param string $cache
* @return \Illuminate\Cache\Store
*/
protected function cache($driver)
{
return call_user_func($this->cacheFactoryResolver)->driver($driver);
}
}

View File

@@ -202,6 +202,26 @@ class SessionManager extends Manager
);
}
/**
* Determine if requests for the same session should wait for each to finish before executing.
*
* @return bool
*/
public function shouldBlock()
{
return $this->config->get('session.block', false);
}
/**
* Get the name of the cache store / driver that should be used to acquire session locks.
*
* @return string|null
*/
public function blockDriver()
{
return $this->config->get('session.block_store');
}
/**
* Get the session configuration.
*

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Session;
use Illuminate\Contracts\Cache\Factory as CacheFactory;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\ServiceProvider;
@@ -18,7 +19,11 @@ class SessionServiceProvider extends ServiceProvider
$this->registerSessionDriver();
$this->app->singleton(StartSession::class);
$this->app->singleton(StartSession::class, function () {
return new StartSession($this->app->make(SessionManager::class), function () {
return $this->app->make(CacheFactory::class);
});
});
}
/**

View File

@@ -978,6 +978,28 @@ class Collection implements ArrayAccess, Enumerable
return $this->slice($count);
}
/**
* Skip items in the collection until the given condition is met.
*
* @param mixed $value
* @return static
*/
public function skipUntil($value)
{
return new static($this->lazy()->skipUntil($value)->all());
}
/**
* Skip items in the collection while the given condition is met.
*
* @param mixed $value
* @return static
*/
public function skipWhile($value)
{
return new static($this->lazy()->skipWhile($value)->all());
}
/**
* Slice the underlying collection array.
*

View File

@@ -27,6 +27,8 @@ use Illuminate\Filesystem\Filesystem;
* @method static array allDirectories(string|null $directory = null)
* @method static bool makeDirectory(string $path)
* @method static bool deleteDirectory(string $directory)
* @method static string url(string $path)
* @method static string temporaryUrl(string $path, \DateTimeInterface $expiration, array $options = [])
* @method static \Illuminate\Contracts\Filesystem\Filesystem assertExists(string|array $path)
* @method static \Illuminate\Contracts\Filesystem\Filesystem assertMissing(string|array $path)
*

View File

@@ -947,6 +947,44 @@ class LazyCollection implements Enumerable
});
}
/**
* Skip items in the collection until the given condition is met.
*
* @param mixed $value
* @return static
*/
public function skipUntil($value)
{
$callback = $this->useAsCallable($value) ? $value : $this->equality($value);
return $this->skipWhile($this->negate($callback));
}
/**
* Skip items in the collection while the given condition is met.
*
* @param mixed $value
* @return static
*/
public function skipWhile($value)
{
$callback = $this->useAsCallable($value) ? $value : $this->equality($value);
return new static(function () use ($callback) {
$iterator = $this->getIterator();
while ($iterator->valid() && $callback($iterator->current(), $iterator->key())) {
$iterator->next();
}
while ($iterator->valid()) {
yield $iterator->key() => $iterator->current();
$iterator->next();
}
});
}
/**
* Get a slice of items from the enumerable.
*
@@ -1145,9 +1183,7 @@ class LazyCollection implements Enumerable
{
$callback = $this->useAsCallable($value) ? $value : $this->equality($value);
return $this->takeUntil(function ($item, $key) use ($callback) {
return ! $callback($item, $key);
});
return $this->takeUntil($this->negate($callback));
}
/**

View File

@@ -5,10 +5,13 @@ namespace Illuminate\Support\Testing\Fakes;
use Closure;
use Illuminate\Contracts\Bus\QueueingDispatcher;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\ReflectsClosures;
use PHPUnit\Framework\Assert as PHPUnit;
class BusFake implements QueueingDispatcher
{
use ReflectsClosures;
/**
* The original Bus dispatcher implementation.
*
@@ -54,12 +57,16 @@ class BusFake implements QueueingDispatcher
/**
* Assert if a job was dispatched based on a truth-test callback.
*
* @param string $command
* @param string|\Closure $command
* @param callable|int|null $callback
* @return void
*/
public function assertDispatched($command, $callback = null)
{
if ($command instanceof Closure) {
[$command, $callback] = [$this->firstClosureParameterType($command), $command];
}
if (is_numeric($callback)) {
return $this->assertDispatchedTimes($command, $callback);
}
@@ -92,12 +99,16 @@ class BusFake implements QueueingDispatcher
/**
* Determine if a job was dispatched based on a truth-test callback.
*
* @param string $command
* @param string|\Closure $command
* @param callable|null $callback
* @return void
*/
public function assertNotDispatched($command, $callback = null)
{
if ($command instanceof Closure) {
[$command, $callback] = [$this->firstClosureParameterType($command), $command];
}
PHPUnit::assertTrue(
$this->dispatched($command, $callback)->count() === 0 &&
$this->dispatchedAfterResponse($command, $callback)->count() === 0,
@@ -108,12 +119,16 @@ class BusFake implements QueueingDispatcher
/**
* Assert if a job was dispatched after the response was sent based on a truth-test callback.
*
* @param string $command
* @param string|\Closure $command
* @param callable|int|null $callback
* @return void
*/
public function assertDispatchedAfterResponse($command, $callback = null)
{
if ($command instanceof Closure) {
[$command, $callback] = [$this->firstClosureParameterType($command), $command];
}
if (is_numeric($callback)) {
return $this->assertDispatchedAfterResponseTimes($command, $callback);
}
@@ -144,12 +159,16 @@ class BusFake implements QueueingDispatcher
/**
* Determine if a job was dispatched based on a truth-test callback.
*
* @param string $command
* @param string|\Closure $command
* @param callable|null $callback
* @return void
*/
public function assertNotDispatchedAfterResponse($command, $callback = null)
{
if ($command instanceof Closure) {
[$command, $callback] = [$this->firstClosureParameterType($command), $command];
}
PHPUnit::assertCount(
0, $this->dispatchedAfterResponse($command, $callback),
"The unexpected [{$command}] job was dispatched for after sending the response."

View File

@@ -5,10 +5,13 @@ namespace Illuminate\Support\Testing\Fakes;
use Closure;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\ReflectsClosures;
use PHPUnit\Framework\Assert as PHPUnit;
class EventFake implements Dispatcher
{
use ReflectsClosures;
/**
* The original event dispatcher.
*
@@ -47,12 +50,16 @@ class EventFake implements Dispatcher
/**
* Assert if an event was dispatched based on a truth-test callback.
*
* @param string $event
* @param string|\Closure $event
* @param callable|int|null $callback
* @return void
*/
public function assertDispatched($event, $callback = null)
{
if ($event instanceof Closure) {
[$event, $callback] = [$this->firstClosureParameterType($event), $event];
}
if (is_int($callback)) {
return $this->assertDispatchedTimes($event, $callback);
}
@@ -83,12 +90,16 @@ class EventFake implements Dispatcher
/**
* Determine if an event was dispatched based on a truth-test callback.
*
* @param string $event
* @param string|\Closure $event
* @param callable|null $callback
* @return void
*/
public function assertNotDispatched($event, $callback = null)
{
if ($event instanceof Closure) {
[$event, $callback] = [$this->firstClosureParameterType($event), $event];
}
PHPUnit::assertCount(
0, $this->dispatched($event, $callback),
"The unexpected [{$event}] event was dispatched."

View File

@@ -2,15 +2,19 @@
namespace Illuminate\Support\Testing\Fakes;
use Closure;
use Illuminate\Contracts\Mail\Factory;
use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Mail\MailQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Traits\ReflectsClosures;
use PHPUnit\Framework\Assert as PHPUnit;
class MailFake implements Factory, Mailer, MailQueue
{
use ReflectsClosures;
/**
* The mailer currently being used to send a message.
*
@@ -35,12 +39,16 @@ class MailFake implements Factory, Mailer, MailQueue
/**
* Assert if a mailable was sent based on a truth-test callback.
*
* @param string $mailable
* @param string|\Closure $mailable
* @param callable|int|null $callback
* @return void
*/
public function assertSent($mailable, $callback = null)
{
if ($mailable instanceof Closure) {
[$mailable, $callback] = [$this->firstClosureParameterType($mailable), $mailable];
}
if (is_numeric($callback)) {
return $this->assertSentTimes($mailable, $callback);
}
@@ -106,12 +114,16 @@ class MailFake implements Factory, Mailer, MailQueue
/**
* Assert if a mailable was queued based on a truth-test callback.
*
* @param string $mailable
* @param string|\Closure $mailable
* @param callable|int|null $callback
* @return void
*/
public function assertQueued($mailable, $callback = null)
{
if ($mailable instanceof Closure) {
[$mailable, $callback] = [$this->firstClosureParameterType($mailable), $mailable];
}
if (is_numeric($callback)) {
return $this->assertQueuedTimes($mailable, $callback);
}

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Support\Testing\Fakes;
use Closure;
use Exception;
use Illuminate\Contracts\Notifications\Dispatcher as NotificationDispatcher;
use Illuminate\Contracts\Notifications\Factory as NotificationFactory;
@@ -9,11 +10,12 @@ use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\Traits\ReflectsClosures;
use PHPUnit\Framework\Assert as PHPUnit;
class NotificationFake implements NotificationDispatcher, NotificationFactory
{
use Macroable;
use Macroable, ReflectsClosures;
/**
* All of the notifications that have been sent.
@@ -33,7 +35,7 @@ class NotificationFake implements NotificationDispatcher, NotificationFactory
* Assert if a notification was sent based on a truth-test callback.
*
* @param mixed $notifiable
* @param string $notification
* @param string|\Closure $notification
* @param callable|null $callback
* @return void
*
@@ -53,6 +55,10 @@ class NotificationFake implements NotificationDispatcher, NotificationFactory
return;
}
if ($notification instanceof Closure) {
[$notification, $callback] = [$this->firstClosureParameterType($notification), $notification];
}
if (is_numeric($callback)) {
return $this->assertSentToTimes($notifiable, $notification, $callback);
}
@@ -85,7 +91,7 @@ class NotificationFake implements NotificationDispatcher, NotificationFactory
* Determine if a notification was sent based on a truth-test callback.
*
* @param mixed $notifiable
* @param string $notification
* @param string|\Closure $notification
* @param callable|null $callback
* @return void
*
@@ -105,6 +111,10 @@ class NotificationFake implements NotificationDispatcher, NotificationFactory
return;
}
if ($notification instanceof Closure) {
[$notification, $callback] = [$this->firstClosureParameterType($notification), $notification];
}
PHPUnit::assertCount(
0, $this->sent($notifiable, $notification, $callback),
"The unexpected [{$notification}] notification was sent."

View File

@@ -3,12 +3,16 @@
namespace Illuminate\Support\Testing\Fakes;
use BadMethodCallException;
use Closure;
use Illuminate\Contracts\Queue\Queue;
use Illuminate\Queue\QueueManager;
use Illuminate\Support\Traits\ReflectsClosures;
use PHPUnit\Framework\Assert as PHPUnit;
class QueueFake extends QueueManager implements Queue
{
use ReflectsClosures;
/**
* All of the jobs that have been pushed.
*
@@ -19,12 +23,16 @@ class QueueFake extends QueueManager implements Queue
/**
* Assert if a job was pushed based on a truth-test callback.
*
* @param string $job
* @param string|\Closure $job
* @param callable|int|null $callback
* @return void
*/
public function assertPushed($job, $callback = null)
{
if ($job instanceof Closure) {
[$job, $callback] = [$this->firstClosureParameterType($job), $job];
}
if (is_numeric($callback)) {
return $this->assertPushedTimes($job, $callback);
}
@@ -56,12 +64,16 @@ class QueueFake extends QueueManager implements Queue
* Assert if a job was pushed based on a truth-test callback.
*
* @param string $queue
* @param string $job
* @param string|\Closure $job
* @param callable|null $callback
* @return void
*/
public function assertPushedOn($queue, $job, $callback = null)
{
if ($job instanceof Closure) {
[$job, $callback] = [$this->firstClosureParameterType($job), $job];
}
return $this->assertPushed($job, function ($job, $pushedQueue) use ($callback, $queue) {
if ($pushedQueue !== $queue) {
return false;
@@ -174,12 +186,16 @@ class QueueFake extends QueueManager implements Queue
/**
* Determine if a job was pushed based on a truth-test callback.
*
* @param string $job
* @param string|\Closure $job
* @param callable|null $callback
* @return void
*/
public function assertNotPushed($job, $callback = null)
{
if ($job instanceof Closure) {
[$job, $callback] = [$this->firstClosureParameterType($job), $job];
}
PHPUnit::assertCount(
0, $this->pushed($job, $callback),
"The unexpected [{$job}] job was pushed."

View File

@@ -3,6 +3,7 @@
namespace Illuminate\Support\Traits;
use CachingIterator;
use Closure;
use Exception;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
@@ -61,6 +62,8 @@ trait EnumeratesValues
'min',
'partition',
'reject',
'skipUntil',
'skipWhile',
'some',
'sortBy',
'sortByDesc',
@@ -965,7 +968,7 @@ trait EnumeratesValues
/**
* Make a function to check an item's equality.
*
* @param \Closure|mixed $value
* @param mixed $value
* @return \Closure
*/
protected function equality($value)
@@ -974,4 +977,17 @@ trait EnumeratesValues
return $item === $value;
};
}
/**
* Make a function using another function, by negating its result.
*
* @param \Closure $callback
* @return \Closure
*/
protected function negate(Closure $callback)
{
return function (...$params) use ($callback) {
return ! $callback(...$params);
};
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Illuminate\Support\Traits;
use Closure;
use ReflectionFunction;
use RuntimeException;
trait ReflectsClosures
{
/**
* Get the class names / types of the parameters of the given Closure.
*
* @param \Closure $closure
* @return array
*
* @throws \ReflectionException
*/
protected function closureParameterTypes(Closure $closure)
{
$reflection = new ReflectionFunction($closure);
return collect($reflection->getParameters())->mapWithKeys(function ($parameter) {
if ($parameter->isVariadic()) {
return [$parameter->getName() => null];
}
return [$parameter->getName() => $parameter->getClass()->name ?? null];
})->all();
}
/**
* Get the class name of the first parameter of the given Closure.
*
* @param \Closure $closure
* @return string
*
* @throws \ReflectionException|\RuntimeException
*/
protected function firstClosureParameterType(Closure $closure)
{
$types = array_values($this->closureParameterTypes($closure));
if (! $types) {
throw new RuntimeException('The given Closure has no parameters.');
}
if ($types[0] === null) {
throw new RuntimeException('The first parameter of the given Closure is missing a type hint.');
}
return $types[0];
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Illuminate\Testing\Constraints;
use Illuminate\Database\Connection;
use PHPUnit\Framework\Constraint\Constraint;
use ReflectionClass;
class CountInDatabase extends Constraint
{
/**
* The database connection.
*
* @var \Illuminate\Database\Connection
*/
protected $database;
/**
* The expected table entries count that will be checked against the actual count.
*
* @var int
*/
protected $expectedCount;
/**
* The actual table entries count that will be checked against the expected count.
*
* @var int
*/
protected $actualCount;
/**
* Create a new constraint instance.
*
* @param \Illuminate\Database\Connection $database
* @param int $expectedCount
* @return void
*/
public function __construct(Connection $database, int $expectedCount)
{
$this->expectedCount = $expectedCount;
$this->database = $database;
}
/**
* Check if the expected and actual count are equal.
*
* @param string $table
* @return bool
*/
public function matches($table): bool
{
$this->actualCount = $this->database->table($table)->count();
return $this->actualCount === $this->expectedCount;
}
/**
* Get the description of the failure.
*
* @param string $table
* @return string
*/
public function failureDescription($table): string
{
return sprintf(
"table [%s] matches expected entries count of %s. Entries found: %s.\n",
$table, $this->expectedCount, $this->actualCount
);
}
/**
* Get a string representation of the object.
*
* @param int $options
* @return string
*/
public function toString($options = 0): string
{
return (new ReflectionClass($this))->name;
}
}

View File

@@ -697,7 +697,7 @@ class TestResponse implements ArrayAccess
*/
public function assertJsonCount(int $count, $key = null)
{
if ($key) {
if (! is_null($key)) {
PHPUnit::assertCount(
$count, data_get($this->json(), $key),
"Failed to assert that the response count matched the expected {$count}"
@@ -1020,7 +1020,7 @@ class TestResponse implements ArrayAccess
if (is_null($value)) {
PHPUnit::assertTrue(
$this->session()->getOldInput($key),
$this->session()->hasOldInput($key),
"Session is missing expected key [{$key}]."
);
} elseif ($value instanceof Closure) {

View File

@@ -203,8 +203,7 @@ class ComponentTagCompiler
$parameters = $data->all();
}
return " @component('{$class}', [".$this->attributesToString($parameters, $escapeBound = false).'])
<?php $component->withName(\''.$component.'\'); ?>
return " @component('{$class}', '{$component}', [".$this->attributesToString($parameters, $escapeBound = false).'])
<?php $component->withAttributes(['.$this->attributesToString($attributes->all()).']); ?>';
}

View File

@@ -21,16 +21,16 @@ trait CompilesComponents
*/
protected function compileComponent($expression)
{
[$component, $data] = strpos($expression, ',') !== false
? array_map('trim', explode(',', trim($expression, '()'), 2))
: [trim($expression, '()'), ''];
[$component, $alias, $data] = strpos($expression, ',') !== false
? array_map('trim', explode(',', trim($expression, '()'), 3)) + ['', '', '']
: [trim($expression, '()'), '', ''];
$component = trim($component, '\'"');
$hash = static::newComponentHash($component);
if (Str::contains($component, ['::class', '\\'])) {
return static::compileClassComponentOpening($component, $data, $hash);
return static::compileClassComponentOpening($component, $alias, $data, $hash);
}
return "<?php \$__env->startComponent{$expression}; ?>";
@@ -53,15 +53,17 @@ trait CompilesComponents
* Compile a class component opening.
*
* @param string $component
* @param string $alias
* @param string $data
* @param string $hash
* @return string
*/
public static function compileClassComponentOpening(string $component, string $data, string $hash)
public static function compileClassComponentOpening(string $component, string $alias, string $data, string $hash)
{
return implode("\n", [
'<?php if (isset($component)) { $__componentOriginal'.$hash.' = $component; } ?>',
'<?php $component = $__env->getContainer()->make('.Str::finish($component, '::class').', '.($data ?: '[]').'); ?>',
'<?php $component->withName('.$alias.'); ?>',
'<?php if ($component->shouldRender()): ?>',
'<?php $__env->startComponent($component->resolveView(), $component->data()); ?>',
]);

View File

@@ -4,6 +4,12 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi
## [Unreleased][unreleased]
## [1.4.3] - 2020-05-04
### Fixed
- Fixed certain Unicode letters, numbers, and marks not being preserved when generating URL slugs (#467)
## [1.4.2] - 2020-04-24
### Fixed
@@ -295,7 +301,8 @@ No changes were made since 1.0.0-rc1.
- Removed `DelimiterStack::iterateByCharacters()` (use the new `processDelimiters()` method instead)
- Removed the protected `DelimiterStack::findMatchingOpener()` method
[unreleased]: https://github.com/thephpleague/commonmark/compare/1.4.2...HEAD
[unreleased]: https://github.com/thephpleague/commonmark/compare/1.4.3...HEAD
[1.4.3]: https://github.com/thephpleague/commonmark/compare/1.4.2...1.4.3
[1.4.2]: https://github.com/thephpleague/commonmark/compare/1.4.1...1.4.2
[1.4.1]: https://github.com/thephpleague/commonmark/compare/1.4.0...1.4.1
[1.4.0]: https://github.com/thephpleague/commonmark/compare/1.3.4...1.4.0

View File

@@ -24,7 +24,7 @@ class CommonMarkConverter extends Converter
*
* This might be a typical `x.y.z` version, or `x.y-dev`.
*/
public const VERSION = '1.4.2';
public const VERSION = '1.4.3';
/** @var EnvironmentInterface */
protected $environment;

View File

@@ -24,8 +24,8 @@ final class DefaultSlugGenerator implements SlugGeneratorInterface
$slug = \mb_strtolower($slug);
// Try replacing whitespace with a dash
$slug = \preg_replace('/\s+/u', '-', $slug) ?? $slug;
// Try removing non-alphanumeric and non-dash characters
$slug = \preg_replace('/[^\p{Lu}\p{Ll}\p{Lt}\p{Nd}\p{Nl}\-]/u', '', $slug) ?? $slug;
// Try removing characters other than letters, numbers, and marks.
$slug = \preg_replace('/[^\p{L}\p{Nd}\p{Nl}\p{M}-]+/u', '', $slug) ?? $slug;
return $slug;
}

View File

@@ -30,6 +30,7 @@
"require": {
"php": "^7.1.8 || ^8.0",
"ext-json": "*",
"symfony/polyfill-mbstring": "^1.0",
"symfony/translation": "^3.4 || ^4.0 || ^5.0"
},
"require-dev": {

View File

@@ -131,3 +131,9 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
<a href="https://opencollective.com/Carbon/sponsor/2/website" target="_blank"><img src="https://opencollective.com/Carbon/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/Carbon/sponsor/3/website" target="_blank"><img src="https://opencollective.com/Carbon/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/Carbon/sponsor/4/website" target="_blank"><img src="https://opencollective.com/Carbon/sponsor/4/avatar.svg"></a>
## Carbon for enterprise
Available as part of the Tidelift Subscription.
The maintainers of ``Carbon`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)

View File

@@ -1318,6 +1318,7 @@ class CarbonInterval extends DateInterval
$join = $default === '' ? '' : ' ';
$altNumbers = false;
$aUnit = false;
$minimumUnit = 's';
extract($this->getForHumansInitialVariables($syntax, $short));
if (is_null($syntax)) {
@@ -1381,7 +1382,7 @@ class CarbonInterval extends DateInterval
':optional-space' => $optionalSpace,
];
return [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations];
return [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit];
}
protected static function getRoundingMethodFromOptions(int $options): ?string
@@ -1477,6 +1478,7 @@ class CarbonInterval extends DateInterval
* echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 3, 'join' => true]) . "\n";
* echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['short' => true]) . "\n";
* echo CarbonInterval::fromString('1d 24h')->forHumans(['join' => ' or ']) . "\n";
* echo CarbonInterval::fromString('1d 24h')->forHumans(['minimumUnit' => 'hour']) . "\n";
* ```
*
* @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains:
@@ -1492,6 +1494,8 @@ class CarbonInterval extends DateInterval
* ` will be used instead of the glue for the last item
* ` - if $join is true, it will be guessed from the locale ('list' translation file entry)
* ` - if $join is missing, a space will be used as glue
* - 'minimumUnit' entry determines the smallest unit of time to display can be long or
* ` short form of the units, e.g. 'hour' or 'h' (default value: s)
* if int passed, it add modifiers:
* Possible values:
* - CarbonInterface::DIFF_ABSOLUTE no modifiers
@@ -1506,7 +1510,7 @@ class CarbonInterval extends DateInterval
*/
public function forHumans($syntax = null, $short = false, $parts = -1, $options = null)
{
[$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations] = $this->getForHumansParameters($syntax, $short, $parts, $options);
[$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit] = $this->getForHumansParameters($syntax, $short, $parts, $options);
$interval = [];
@@ -1554,13 +1558,14 @@ class CarbonInterval extends DateInterval
}
$diffIntervalArray = [
['value' => $intervalValues->years, 'unit' => 'year', 'unitShort' => 'y'],
['value' => $intervalValues->months, 'unit' => 'month', 'unitShort' => 'm'],
['value' => $intervalValues->weeks, 'unit' => 'week', 'unitShort' => 'w'],
['value' => $intervalValues->daysExcludeWeeks, 'unit' => 'day', 'unitShort' => 'd'],
['value' => $intervalValues->hours, 'unit' => 'hour', 'unitShort' => 'h'],
['value' => $intervalValues->minutes, 'unit' => 'minute', 'unitShort' => 'min'],
['value' => $intervalValues->seconds, 'unit' => 'second', 'unitShort' => 's'],
['value' => $intervalValues->years, 'unit' => 'year', 'unitShort' => 'y'],
['value' => $intervalValues->months, 'unit' => 'month', 'unitShort' => 'm'],
['value' => $intervalValues->weeks, 'unit' => 'week', 'unitShort' => 'w'],
['value' => $intervalValues->daysExcludeWeeks, 'unit' => 'day', 'unitShort' => 'd'],
['value' => $intervalValues->hours, 'unit' => 'hour', 'unitShort' => 'h'],
['value' => $intervalValues->minutes, 'unit' => 'minute', 'unitShort' => 'min'],
['value' => $intervalValues->seconds, 'unit' => 'second', 'unitShort' => 's'],
['value' => $intervalValues->milliseconds, 'unit' => 'millisecond', 'unitShort' => 'ms'],
];
$transChoice = function ($short, $unitData) use ($absolute, $handleDeclensions, $translator, $aUnit, $altNumbers, $interpolations) {
@@ -1587,6 +1592,7 @@ class CarbonInterval extends DateInterval
return $this->translate($unitData['unit'], $interpolations, $count, $translator, $altNumbers);
};
$fallbackUnit = ['second', 's'];
foreach ($diffIntervalArray as $diffIntervalData) {
if ($diffIntervalData['value'] > 0) {
$unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit'];
@@ -1600,6 +1606,13 @@ class CarbonInterval extends DateInterval
if (count($interval) >= $parts) {
break;
}
// break the loop after we have reached the minimum unit
if (in_array($minimumUnit, [$diffIntervalData['unit'], $diffIntervalData['unitShort']])) {
$fallbackUnit = [$diffIntervalData['unit'], $diffIntervalData['unitShort']];
break;
}
}
if (count($interval) === 0) {
@@ -1613,7 +1626,7 @@ class CarbonInterval extends DateInterval
}
$count = $options & CarbonInterface::NO_ZERO_DIFF ? 1 : 0;
$unit = $short ? 's' : 'second';
$unit = $fallbackUnit[$short ? 1 : 0];
$interval[] = $this->translate($unit, $interpolations, $count, $translator, $altNumbers);
}

View File

@@ -152,13 +152,9 @@ class CarbonTimeZone extends DateTimeZone
*/
public function toOffsetName(DateTimeInterface $date = null)
{
$minutes = floor($this->getOffset($date ?: Carbon::now($this)) / 60);
$hours = floor($minutes / 60);
$minutes = str_pad((string) (abs($minutes) % 60), 2, '0', STR_PAD_LEFT);
return ($hours < 0 ? '-' : '+').str_pad((string) abs($hours), 2, '0', STR_PAD_LEFT).":$minutes";
return static::getOffsetNameFromMinuteOffset(
$this->getOffset($date ?: Carbon::now($this)) / 60
);
}
/**
@@ -247,4 +243,46 @@ class CarbonTimeZone extends DateTimeZone
{
return static::instance($object);
}
/**
* Create a CarbonTimeZone from int/float hour offset.
*
* @param float $hourOffset number of hour of the timezone shift (can be decimal).
*
* @return false|static
*/
public static function createFromHourOffset(float $hourOffset)
{
return static::createFromMinuteOffset($hourOffset * Carbon::MINUTES_PER_HOUR);
}
/**
* Create a CarbonTimeZone from int/float minute offset.
*
* @param float $minuteOffset number of total minutes of the timezone shift.
*
* @return false|static
*/
public static function createFromMinuteOffset(float $minuteOffset)
{
return static::instance(static::getOffsetNameFromMinuteOffset($minuteOffset));
}
/**
* Convert a total minutes offset into a standardized timezone offset string.
*
* @param float $minutes number of total minutes of the timezone shift.
*
* @return string
*/
public static function getOffsetNameFromMinuteOffset(float $minutes): string
{
$minutes = round($minutes);
$unsignedMinutes = abs($minutes);
return ($minutes < 0 ? '-' : '+').
str_pad((string) floor($unsignedMinutes / 60), 2, '0', STR_PAD_LEFT).
':'.
str_pad((string) ($unsignedMinutes % 60), 2, '0', STR_PAD_LEFT);
}
}

View File

@@ -16,6 +16,7 @@
* - dennisoderwald
* - Timo
* - Karag2006
* - Pete Scopes (pdscopes)
*/
return [
'year' => ':count Jahr|:count Jahre',
@@ -39,6 +40,9 @@ return [
'second' => ':count Sekunde|:count Sekunden',
'a_second' => 'ein paar Sekunden|:count Sekunden',
's' => ':count Sek.',
'millisecond' => ':count Millisekunde|:count Millisekunde',
'a_millisecond' => 'eine Millisekunde|:count Millisekunde',
'ms' => ':countms',
'ago' => 'vor :time',
'from_now' => 'in :time',
'after' => ':time später',

View File

@@ -13,6 +13,7 @@
* Authors:
* - Milos Sakovic
* - Paul
* - Pete Scopes (pdscopes)
*/
return [
'year' => '{1}:count year|{0}:count years|]1,Inf[:count years',
@@ -36,6 +37,9 @@ return [
'second' => '{1}:count second|{0}:count seconds|]1,Inf[:count seconds',
'a_second' => '{1}a few seconds|{0}:count seconds|]1,Inf[:count seconds',
's' => ':counts',
'millisecond' => '{1}:count millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds',
'a_millisecond' => '{1}a millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds',
'ms' => ':countms',
'ago' => ':time ago',
'from_now' => ':time from now',
'after' => ':time after',

View File

@@ -25,6 +25,7 @@
* - Sebastian Thierer
* - quinterocesar
* - Daniel Commesse Liévanos (danielcommesse)
* - Pete Scopes (pdscopes)
*/
return [
'year' => ':count año|:count años',
@@ -48,6 +49,9 @@ return [
'second' => ':count segundo|:count segundos',
'a_second' => 'unos segundos|:count segundos',
's' => ':counts',
'millisecond' => ':count milisegundo|:count milisegundos',
'a_millisecond' => 'un milisegundo|:count milisegundos',
'ms' => ':countms',
'ago' => 'hace :time',
'from_now' => 'en :time',
'after' => ':time después',

View File

@@ -20,6 +20,7 @@
* - JD Isaacks
* - Sebastian Thierer
* - Fastfuel
* - Pete Scopes (pdscopes)
*/
return [
'year' => ':count an|:count ans',
@@ -43,6 +44,9 @@ return [
'second' => ':count seconde|:count secondes',
'a_second' => 'quelques secondes|:count secondes',
's' => ':count s',
'millisecond' => ':count milliseconde:count millisecondes',
'a_millisecond' => 'une milliseconde|:count millisecondes',
'ms' => ':countms',
'ago' => 'il y a :time',
'from_now' => 'dans :time',
'after' => ':time après',

View File

@@ -20,6 +20,7 @@
* - Francesco Marasco
* - Tizianoz93
* - Davide Casiraghi (davide-casiraghi)
* - Pete Scopes (pdscopes)
*/
return [
'year' => ':count anno|:count anni',
@@ -43,6 +44,9 @@ return [
'second' => ':count secondo|:count secondi',
'a_second' => 'alcuni secondi|:count secondi',
's' => ':count sec.',
'millisecond' => ':count millisecondo|:count millisecondi',
'a_millisecond' => 'un millisecondo|:count millisecondi',
'ms' => ':countms',
'ago' => ':time fa',
'from_now' => function ($time) {
return (preg_match('/^[0-9].+$/', $time) ? 'tra' : 'in')." $time";

View File

@@ -354,9 +354,9 @@ trait Comparison
*
* @example
* ```
* Carbon::parse('2018-07-25')->betweenExcluded('2018-07-14', '2018-08-01'); // true
* Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false
* Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // true
* Carbon::parse('2018-07-25')->betweenIncluded('2018-07-14', '2018-08-01'); // true
* Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false
* Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true
* ```
*
* @param \Carbon\Carbon|\DateTimeInterface|mixed $date1
@@ -396,8 +396,8 @@ trait Comparison
* ```
* Carbon::parse('2018-07-25')->isBetween('2018-07-14', '2018-08-01'); // true
* Carbon::parse('2018-07-25')->isBetween('2018-08-01', '2018-08-20'); // false
* Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // false
* Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', true); // true
* Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // true
* Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false
* ```
*
* @param \Carbon\Carbon|\DateTimeInterface|mixed $date1

View File

@@ -23,7 +23,7 @@ use InvalidArgumentException;
* Depends on the following methods:
*
* @method CarbonInterface copy()
* @method CarbonInterface startOfWeek()
* @method CarbonInterface startOfWeek(int $weekStartsAt = null)
*/
trait Rounding
{

View File

@@ -340,33 +340,11 @@ class Translator extends Translation\Translator
$completeLocaleChunks = preg_split('/[_.-]+/', $completeLocale);
$getScore = function ($language) use ($completeLocaleChunks) {
$chunks = preg_split('/[_.-]+/', $language);
$score = 0;
foreach ($completeLocaleChunks as $index => $chunk) {
if (!isset($chunks[$index])) {
$score++;
continue;
}
if (strtolower($chunks[$index]) === strtolower($chunk)) {
$score += 10;
}
}
return $score;
return static::compareChunkLists($completeLocaleChunks, preg_split('/[_.-]+/', $language));
};
usort($locales, function ($first, $second) use ($getScore) {
$first = $getScore($first);
$second = $getScore($second);
if ($first === $second) {
return 0;
}
return $first < $second ? 1 : -1;
return $getScore($second) <=> $getScore($first);
});
$locale = $locales[0];
@@ -403,4 +381,23 @@ class Translator extends Translation\Translator
'locale' => $this->getLocale(),
];
}
private static function compareChunkLists($referenceChunks, $chunks)
{
$score = 0;
foreach ($referenceChunks as $index => $chunk) {
if (!isset($chunks[$index])) {
$score++;
continue;
}
if (strtolower($chunks[$index]) === strtolower($chunk)) {
$score += 10;
}
}
return $score;
}
}

View File

@@ -16,7 +16,7 @@
}
],
"require": {
"php": "^5.5.9 || ^7.0",
"php": "^5.5.9 || ^7.0 || ^8.0",
"phpoption/phpoption": "^1.7.2",
"symfony/polyfill-ctype": "^1.9"
},
@@ -40,6 +40,10 @@
"ext-filter": "Required to use the boolean validator.",
"ext-pcre": "Required to use most of the library."
},
"config": {
"preferred-install": "dist",
"platform-check": false
},
"extra": {
"branch-alias": {
"dev-master": "4.1-dev"