Laravel 5.6 updates

Travis config update

Removed HHVM script as Laravel no longer support HHVM after releasing 5.3
This commit is contained in:
Manish Verma
2018-08-06 20:08:55 +05:30
parent 126fbb0255
commit 1ac0f42a58
2464 changed files with 65239 additions and 46734 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -10,17 +10,17 @@
}
],
"require": {
"php": ">=5.6.4",
"facebook/webdriver": "~1.0",
"php": ">=7.1.0",
"facebook/webdriver": "~1.3",
"nesbot/carbon": "~1.20",
"illuminate/console": "~5.5",
"illuminate/support": "~5.5",
"symfony/console": "~3.2",
"symfony/process": "~3.2"
"illuminate/console": "~5.6",
"illuminate/support": "~5.6",
"symfony/console": "~4.0",
"symfony/process": "~4.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7",
"mockery/mockery": "^0.9.6"
"mockery/mockery": "~1.0",
"phpunit/phpunit": "~7.0"
},
"autoload": {
"psr-4": {
@@ -29,7 +29,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
"dev-master": "4.0-dev"
},
"laravel": {
"providers": [

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"

View File

@@ -7,6 +7,7 @@ use BadMethodCallException;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Facebook\WebDriver\WebDriverDimension;
use Facebook\WebDriver\Remote\WebDriverBrowserType;
class Browser
{
@@ -42,6 +43,16 @@ class Browser
*/
public static $storeConsoleLogAt;
/**
* The browsers that support retrieving logs.
*
* @var array
*/
public static $supportsRemoteLogs = [
WebDriverBrowserType::CHROME,
WebDriverBrowserType::PHANTOMJS,
];
/**
* Get the callback which resolves the default user to authenticate.
*
@@ -49,6 +60,13 @@ class Browser
*/
public static $userResolver;
/**
* The default wait time in seconds.
*
* @var int
*/
public static $waitSeconds = 5;
/**
* The RemoteWebDriver instance.
*
@@ -70,6 +88,13 @@ class Browser
*/
public $page;
/**
* The component object currently being viewed.
*
* @var mixed
*/
public $component;
/**
* Create a browser instance.
*
@@ -87,7 +112,7 @@ class Browser
/**
* Browse to the given URL.
*
* @param string $url
* @param string|Page $url
* @return $this
*/
public function visit($url)
@@ -229,13 +254,15 @@ class Browser
*/
public function storeConsoleLog($name)
{
$console = $this->driver->manage()->getLog('browser');
if (in_array($this->driver->getCapabilities()->getBrowserName(), static::$supportsRemoteLogs)) {
$console = $this->driver->manage()->getLog('browser');
if (! empty($console)) {
file_put_contents(
sprintf('%s/%s.log', rtrim(static::$storeConsoleLogAt, '/'), $name)
, json_encode($console, JSON_PRETTY_PRINT)
);
if (!empty($console)) {
file_put_contents(
sprintf('%s/%s.log', rtrim(static::$storeConsoleLogAt, '/'), $name)
, json_encode($console, JSON_PRETTY_PRINT)
);
}
}
return $this;
@@ -270,11 +297,33 @@ class Browser
$browser->on($this->page);
}
if ($selector instanceof Component) {
$browser->onComponent($selector, $this->resolver);
}
call_user_func($callback, $browser);
return $this;
}
public function onComponent($component, $parentResolver)
{
$this->component = $component;
// Here we will set the component elements on the resolver instance, which will allow
// the developer to access short-cuts for CSS selectors on the component which can
// allow for more expressive navigation and interaction with all the components.
$this->resolver->pageElements(
$component->elements() + $parentResolver->elements
);
$component->assert($this);
$this->resolver->prefix = $this->resolver->format(
$component->selector()
);
}
/**
* Ensure that jQuery is available on the page.
*
@@ -373,6 +422,14 @@ class Browser
return $this->macroCall($method, $parameters);
}
if ($this->component && method_exists($this->component, $method)) {
array_unshift($parameters, $this);
$this->component->{$method}(...$parameters);
return $this;
}
if ($this->page && method_exists($this->page, $method)) {
array_unshift($parameters, $this);

View File

@@ -0,0 +1,102 @@
<?php
namespace Laravel\Dusk\Chrome;
use RuntimeException;
use Illuminate\Support\Str;
use Symfony\Component\Process\Process;
class ChromeProcess
{
/**
* The path to the Chromedriver.
*
* @var String
*/
protected $driver;
/**
* Create a new ChromeProcess instance.
*
* @param string $driver
* @return void
*/
public function __construct($driver = null)
{
$this->driver = $driver;
if (! is_null($driver) && realpath($driver) === false) {
throw new RuntimeException("Invalid path to Chromedriver [{$driver}].");
}
}
/**
* Build the process to run Chromedriver.
*
* @return \Symfony\Component\Process\Process
*/
public function toProcess()
{
if ($this->driver) {
return $this->process();
}
if ($this->onWindows()) {
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-win.exe');
return $this->process();
}
$this->driver = $this->onMac()
? realpath(__DIR__.'/../../bin/chromedriver-mac')
: realpath(__DIR__.'/../../bin/chromedriver-linux');
return $this->process();
}
/**
* Build the Chromedriver with Symfony Process.
*
* @return \Symfony\Component\Process\Process
*/
protected function process()
{
return (new Process(
[realpath($this->driver)], null, $this->chromeEnvironment()
));
}
/**
* Get the Chromedriver environment variables.
*
* @return array
*/
protected function chromeEnvironment()
{
if ($this->onMac() || $this->onWindows()) {
return [];
}
return ['DISPLAY' => ':0'];
}
/**
* Determine if Dusk is running on Windows or Windows Subsystem for Linux.
*
* @return bool
*/
protected function onWindows()
{
return PHP_OS === 'WINNT' || Str::contains(php_uname(), 'Microsoft');
}
/**
* Determine if Dusk is running on Mac.
*
* @return bool
*/
protected function onMac()
{
return PHP_OS === 'Darwin';
}
}

View File

@@ -1,10 +1,6 @@
<?php
namespace Laravel\Dusk;
use RuntimeException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;
namespace Laravel\Dusk\Chrome;
trait SupportsChrome
{
@@ -55,23 +51,13 @@ trait SupportsChrome
/**
* Build the process to run the Chromedriver.
*
* @throws \RuntimeException if the driver file path doesn't exist.
*
* @return \Symfony\Component\Process\Process
* @throws \RuntimeException if the driver file path doesn't exist.
*/
protected static function buildChromeProcess()
{
$driver = static::$chromeDriver
?: realpath(__DIR__.'/../bin/chromedriver-'.static::driverSuffix());
if (realpath($driver) === false) {
throw new RuntimeException("Invalid path to Chromedriver [{$driver}].");
}
return (new ProcessBuilder())
->setPrefix(realpath($driver))
->getProcess()
->setEnv(static::chromeEnvironment());
return (new ChromeProcess(static::$chromeDriver))->toProcess();
}
/**
@@ -84,35 +70,4 @@ trait SupportsChrome
{
static::$chromeDriver = $path;
}
/**
* Get the Chromedriver environment variables.
*
* @return array
*/
protected static function chromeEnvironment()
{
if (PHP_OS === 'Darwin' || PHP_OS === 'WINNT') {
return [];
}
return ['DISPLAY' => ':0'];
}
/**
* Get the suffix for the Chromedriver binary.
*
* @return string
*/
protected static function driverSuffix()
{
switch (PHP_OS) {
case 'Darwin':
return 'mac';
case 'WINNT':
return 'win.exe';
default:
return 'linux';
}
}
}

44
vendor/laravel/dusk/src/Component.php vendored Normal file
View File

@@ -0,0 +1,44 @@
<?php
namespace Laravel\Dusk;
abstract class Component
{
/**
* Get the root selector associated with this component.
*
* @return string
*/
abstract public function selector();
/**
* Assert that the current page contains this component.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
//
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [];
}
/**
* Allow this class to be used in place of a selector string.
*
* @return string
*/
public function __toString()
{
return '';
}
}

View File

@@ -86,7 +86,7 @@ trait InteractsWithAuthentication
/**
* Assert that the user is authenticated as the given user.
*
* @param $user
* @param mixed $user
* @param string|null $guard
* @return $this
*/

View File

@@ -14,7 +14,7 @@ trait InteractsWithElements
* Get all of the elements matching the given selector.
*
* @param string $selector
* @return array
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
*/
public function elements($selector)
{
@@ -32,45 +32,18 @@ trait InteractsWithElements
return $this->resolver->find($selector);
}
/**
* Click the element at the given selector.
*
* @param string $selector
* @return $this
*/
public function click($selector)
{
$this->resolver->findOrFail($selector)->click();
return $this;
}
/**
* Right click the element at the given selector.
*
* @param string $selector
* @return $this
*/
public function rightClick($selector)
{
(new WebDriverActions($this->driver))->contextClick(
$this->resolver->findOrFail($selector)
)->perform();
return $this;
}
/**
* Click the link with the given text.
*
* @param string $link
* @param string $element
* @return $this
*/
public function clickLink($link)
public function clickLink($link, $element = "a")
{
$this->ensurejQueryIsAvailable();
$selector = addslashes(trim($this->resolver->format("a:contains({$link})")));
$selector = addslashes(trim($this->resolver->format("{$element}:contains({$link})")));
$this->driver->executeScript("jQuery.find(\"{$selector}\")[0].click();");
@@ -213,9 +186,7 @@ trait InteractsWithElements
if (is_null($value)) {
$options[array_rand($options)]->click();
}
else {
} else {
foreach ($options as $option) {
if ((string) $option->getAttribute('value') === (string) $value) {
$option->click();
@@ -418,6 +389,19 @@ trait InteractsWithElements
return $this;
}
/**
* Type the given value in an open JavaScript prompt dialog.
*
* @param string $value
* @return $this
*/
public function typeInDialog($value)
{
$this->driver->switchTo()->alert()->sendKeys($value);
return $this;
}
/**
* Dismiss a JavaScript dialog.
*

View File

@@ -2,8 +2,26 @@
namespace Laravel\Dusk\Concerns;
use Facebook\WebDriver\Interactions\WebDriverActions;
trait InteractsWithMouse
{
/**
* Move the mouse by offset X and Y.
*
* @param int $xOffset
* @param int $yOffset
* @return $this
*/
public function moveMouse($xOffset, $yOffset)
{
(new WebDriverActions($this->driver))->moveByOffset(
$xOffset, $yOffset
)->perform();
return $this;
}
/**
* Move the mouse over the given selector.
*
@@ -18,4 +36,76 @@ trait InteractsWithMouse
return $this;
}
/**
* Click the element at the given selector.
*
* @param string|null $selector
* @return $this
*/
public function click($selector = null)
{
if (is_null($selector)) {
(new WebDriverActions($this->driver))->click()->perform();
} else {
$this->resolver->findOrFail($selector)->click();
}
return $this;
}
/**
* Perform a mouse click and hold the mouse button down.
*
* @return $this
*/
public function clickAndHold()
{
(new WebDriverActions($this->driver))->clickAndHold()->perform();
return $this;
}
/**
* Perform a double click at the current mouse position.
*
* @return $this
*/
public function doubleClick()
{
(new WebDriverActions($this->driver))->doubleClick()->perform();
return $this;
}
/**
* Right click the element at the given selector.
*
* @param string|null $selector
* @return $this
*/
public function rightClick($selector = null)
{
if (is_null($selector)) {
(new WebDriverActions($this->driver))->contextClick()->perform();
} else {
(new WebDriverActions($this->driver))->contextClick(
$this->resolver->findOrFail($selector)
)->perform();
}
return $this;
}
/**
* Release the currenctly clicked mouse button.
*
* @return $this
*/
public function releaseMouse()
{
(new WebDriverActions($this->driver))->release()->perform();
return $this;
}
}

View File

@@ -4,6 +4,7 @@ namespace Laravel\Dusk\Concerns;
use Illuminate\Support\Str;
use PHPUnit\Framework\Assert as PHPUnit;
use Facebook\WebDriver\Remote\RemoteWebElement;
use Facebook\WebDriver\Exception\NoSuchElementException;
trait MakesAssertions
@@ -16,7 +17,10 @@ trait MakesAssertions
*/
public function assertTitle($title)
{
PHPUnit::assertEquals($title, $this->driver->getTitle());
PHPUnit::assertEquals(
$title, $this->driver->getTitle(),
"Expected title [{$title}] does not equal actual title [{$this->driver->getTitle()}]."
);
return $this;
}
@@ -30,7 +34,36 @@ trait MakesAssertions
public function assertTitleContains($title)
{
PHPUnit::assertTrue(
Str::contains($this->driver->getTitle(), $title)
Str::contains($this->driver->getTitle(), $title),
"Did not see expected value [{$title}] within title [{$this->driver->getTitle()}]."
);
return $this;
}
/**
* Assert that the current URL matches the given URL.
*
* @param string $url
* @return $this
*/
public function assertUrlIs($url)
{
$pattern = str_replace('\*', '.*', preg_quote($url, '/'));
$segments = parse_url($this->driver->getCurrentURL());
$currentUrl = sprintf(
'%s://%s%s%s',
$segments['scheme'],
$segments['host'],
array_get($segments, 'port', '') ? ':'.$segments['port'] : '',
array_get($segments, 'path', '')
);
PHPUnit::assertRegExp(
'/^'.$pattern.'$/u', $currentUrl,
"Actual URL [{$this->driver->getCurrentURL()}] does not equal expected URL [{$url}]."
);
return $this;
@@ -48,9 +81,12 @@ trait MakesAssertions
$pattern = str_replace('\*', '.*', $pattern);
PHPUnit::assertRegExp('/^'.$pattern.'/u', parse_url(
$this->driver->getCurrentURL()
)['path']);
$actualPath = parse_url($this->driver->getCurrentURL())['path'];
PHPUnit::assertRegExp(
'/^'.$pattern.'$/u', $actualPath,
"Actual path [{$actualPath}] does not equal expected path [{$path}]."
);
return $this;
}
@@ -63,9 +99,12 @@ trait MakesAssertions
*/
public function assertPathBeginsWith($path)
{
PHPUnit::assertStringStartsWith($path, parse_url(
$this->driver->getCurrentURL()
)['path']);
$actualPath = parse_url($this->driver->getCurrentURL())['path'];
PHPUnit::assertStringStartsWith(
$path, $actualPath,
"Actual path [{$actualPath}] does not begin with expected path [{$path}]."
);
return $this;
}
@@ -78,9 +117,68 @@ trait MakesAssertions
*/
public function assertPathIsNot($path)
{
PHPUnit::assertNotEquals($path, parse_url(
$this->driver->getCurrentURL()
)['path']);
$actualPath = parse_url($this->driver->getCurrentURL())['path'];
PHPUnit::assertNotEquals(
$path, $actualPath,
"Path [{$path}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL fragment matches the given pattern.
*
* @param string $fragment
* @return $this
*/
public function assertFragmentIs($fragment)
{
$pattern = preg_quote($fragment, '/');
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
PHPUnit::assertRegExp(
'/^'.str_replace('\*', '.*', $pattern).'$/u', $actualFragment,
"Actual fragment [{$actualFragment}] does not equal expected fragment [{$fragment}]."
);
return $this;
}
/**
* Assert that the current URL fragment begins with given fragment.
*
* @param string $fragment
* @return $this
*/
public function assertFragmentBeginsWith($fragment)
{
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
PHPUnit::assertStringStartsWith(
$fragment, $actualFragment,
"Actual fragment [$actualFragment] does not begin with expected fragment [$fragment]."
);
return $this;
}
/**
* Assert that the current URL fragment does not match the given fragment.
*
* @param string $fragment
* @return $this
*/
public function assertFragmentIsNot($fragment)
{
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
PHPUnit::assertNotEquals(
$fragment, $actualFragment,
"Fragment [{$fragment}] should not equal the actual value."
);
return $this;
}
@@ -149,7 +247,7 @@ trait MakesAssertions
* Assert that the given query string parameter is present.
*
* @param string $name
* @return $this
* @return array
*/
protected function assertHasQueryStringParameter($name)
{
@@ -186,6 +284,22 @@ trait MakesAssertions
return $this;
}
/**
* Assert that the given cookie is not present.
*
* @param string $name
* @return $this
*/
public function assertCookieMissing($name)
{
PHPUnit::assertTrue(
is_null($this->cookie($name)),
"Found unexpected cookie [{$name}]."
);
return $this;
}
/**
* Assert that an encrypted cookie has a given value.
*
@@ -347,7 +461,7 @@ trait MakesAssertions
if ($this->resolver->prefix) {
$message = "Saw unexpected link [{$link}] within [{$this->resolver->prefix}].";
} else {
$message = "Saw unexpected expected link [{$link}].";
$message = "Saw unexpected link [{$link}].";
}
PHPUnit::assertFalse(
@@ -387,7 +501,10 @@ JS;
*/
public function assertInputValue($field, $value)
{
PHPUnit::assertEquals($value, $this->inputValue($field));
PHPUnit::assertEquals(
$value, $this->inputValue($field),
"Expected value [{$value}] for the [{$field}] input does not equal the actual value [{$this->inputValue($field)}]."
);
return $this;
}
@@ -401,7 +518,10 @@ JS;
*/
public function assertInputValueIsNot($field, $value)
{
PHPUnit::assertNotEquals($value, $this->inputValue($field));
PHPUnit::assertNotEquals(
$value, $this->inputValue($field),
"Value [{$value}] for the [{$field}] input should not equal the actual value."
);
return $this;
}
@@ -466,7 +586,7 @@ JS;
* @param string $value
* @return $this
*/
function assertRadioSelected($field, $value)
public function assertRadioSelected($field, $value)
{
$element = $this->resolver->resolveForRadioSelection($field, $value);
@@ -540,9 +660,14 @@ JS;
*/
public function assertSelectHasOptions($field, array $values)
{
$options = $this->resolver->resolveSelectOptions($field, $values);
$options = collect($options)->unique(function (RemoteWebElement $option) {
return $option->getAttribute('value');
})->all();
PHPUnit::assertCount(
count($values),
$this->resolver->resolveSelectOptions($field, $values),
count($values), $options,
"Expected options [".implode(',', $values)."] for selection field [{$field}] to be available."
);
@@ -638,6 +763,24 @@ JS;
return $this;
}
/**
* Assert that the element with the given selector is present in the DOM.
*
* @param string $selector
* @return $this
*/
public function assertPresent($selector)
{
$fullSelector = $this->resolver->format($selector);
PHPUnit::assertTrue(
! is_null($this->resolver->find($selector)),
"Element [{$fullSelector}] is not present."
);
return $this;
}
/**
* Assert that the element with the given selector is not on the page.
*
@@ -667,10 +810,160 @@ JS;
*/
public function assertDialogOpened($message)
{
$actualMessage = $this->driver->switchTo()->alert()->getText();
PHPUnit::assertEquals(
$message, $this->driver->switchTo()->alert()->getText()
$message, $actualMessage,
"Expected dialog message [{$message}] does not equal actual message [{$actualMessage}]."
);
return $this;
}
/**
* Assert that the given field is enabled.
*
* @param string $field
* @return $this
*/
public function assertEnabled($field) {
$element = $this->resolver->resolveForField($field);
PHPUnit::assertTrue(
$element->isEnabled(),
"Expected element [{$field}] to be enabled, but it wasn't."
);
return $this;
}
/**
* Assert that the given field is disabled.
*
* @param string $field
* @return $this
*/
public function assertDisabled($field) {
$element = $this->resolver->resolveForField($field);
PHPUnit::assertFalse(
$element->isEnabled(),
"Expected element [{$field}] to be disabled, but it wasn't."
);
return $this;
}
/**
* Assert that the given field is focused.
*
* @param string $field
* @return $this
*/
public function assertFocused($field) {
$element = $this->resolver->resolveForField($field);
PHPUnit::assertTrue(
$this->driver->switchTo()->activeElement()->equals($element),
"Expected element [{$field}] to be focused, but it wasn't."
);
return $this;
}
/**
* Assert that the given field is not focused.
*
* @param string $field
* @return $this
*/
public function assertNotFocused($field) {
$element = $this->resolver->resolveForField($field);
PHPUnit::assertFalse(
$this->driver->switchTo()->activeElement()->equals($element),
"Expected element [{$field}] not to be focused, but it was."
);
return $this;
}
/**
* Assert that the Vue component's attribute at the given key has the given value.
*
* @param string $key
* @param string $value
* @param string|null $componentSelector
* @return $this
*/
public function assertVue($key, $value, $componentSelector = null)
{
PHPUnit::assertEquals($value, $this->vueAttribute($componentSelector, $key));
return $this;
}
/**
* Assert that the Vue component's attribute at the given key
* does not have the given value.
*
* @param string $key
* @param string $value
* @param string|null $componentSelector
* @return $this
*/
public function assertVueIsNot($key, $value, $componentSelector = null)
{
PHPUnit::assertNotEquals($value, $this->vueAttribute($componentSelector, $key));
return $this;
}
/**
* Assert that the Vue component's attribute at the given key
* is an array that contains the given value.
*
* @param string $key
* @param string $value
* @param string|null $componentSelector
* @return $this
*/
public function assertVueContains($key, $value, $componentSelector = null)
{
PHPUnit::assertContains($value, $this->vueAttribute($componentSelector, $key));
return $this;
}
/**
* Assert that the Vue component's attribute at the given key
* is an array that does not contain the given value.
*
* @param string $key
* @param string $value
* @param string|null $componentSelector
* @return $this
*/
public function assertVueDoesNotContain($key, $value, $componentSelector = null)
{
PHPUnit::assertNotContains($value, $this->vueAttribute($componentSelector, $key));
return $this;
}
/**
* Retrieve the value of the Vue component's attribute at the given key.
*
* @param string $componentSelector
* @param string $key
* @return mixed
*/
public function vueAttribute($componentSelector, $key)
{
$fullSelector = $this->resolver->format($componentSelector);
return $this->driver->executeScript(
"return document.querySelector('" . $fullSelector . "').__vue__." . $key
);
}
}

View File

@@ -0,0 +1,199 @@
<?php
namespace Laravel\Dusk\Concerns;
use Closure;
use Exception;
use Throwable;
use ReflectionFunction;
use Laravel\Dusk\Browser;
use Illuminate\Support\Collection;
trait ProvidesBrowser
{
/**
* All of the active browser instances.
*
* @var array
*/
protected static $browsers = [];
/**
* The callbacks that should be run on class tear down.
*
* @var array
*/
protected static $afterClassCallbacks = [];
/**
* Tear down the Dusk test case class.
*
* @afterClass
* @return void
*/
public static function tearDownDuskClass()
{
static::closeAll();
foreach (static::$afterClassCallbacks as $callback) {
$callback();
}
}
/**
* Register an "after class" tear down callback.
*
* @param \Closure $callback
* @return void
*/
public static function afterClass(Closure $callback)
{
static::$afterClassCallbacks[] = $callback;
}
/**
* Create a new browser instance.
*
* @param \Closure $callback
* @return \Laravel\Dusk\Browser|void
* @throws \Exception
* @throws \Throwable
*/
public function browse(Closure $callback)
{
$browsers = $this->createBrowsersFor($callback);
try {
$callback(...$browsers->all());
} catch (Exception $e) {
$this->captureFailuresFor($browsers);
throw $e;
} catch (Throwable $e) {
$this->captureFailuresFor($browsers);
throw $e;
} finally {
$this->storeConsoleLogsFor($browsers);
static::$browsers = $this->closeAllButPrimary($browsers);
}
}
/**
* Create the browser instances needed for the given callback.
*
* @param \Closure $callback
* @return array
*/
protected function createBrowsersFor(Closure $callback)
{
if (count(static::$browsers) === 0) {
static::$browsers = collect([$this->newBrowser($this->createWebDriver())]);
}
$additional = $this->browsersNeededFor($callback) - 1;
for ($i = 0; $i < $additional; $i++) {
static::$browsers->push($this->newBrowser($this->createWebDriver()));
}
return static::$browsers;
}
/**
* Create a new Browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @return \Laravel\Dusk\Browser
*/
protected function newBrowser($driver)
{
return new Browser($driver);
}
/**
* Get the number of browsers needed for a given callback.
*
* @param \Closure $callback
* @return int
*/
protected function browsersNeededFor(Closure $callback)
{
return (new ReflectionFunction($callback))->getNumberOfParameters();
}
/**
* Capture failure screenshots for each browser.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function captureFailuresFor($browsers)
{
$browsers->each(function ($browser, $key) {
$name = str_replace('\\', '_', get_class($this)).'_'.$this->getName(false);
$browser->screenshot('failure-'.$name.'-'.$key);
});
}
/**
* Store the console output for the given browsers.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function storeConsoleLogsFor($browsers)
{
$browsers->each(function ($browser, $key) {
$name = str_replace('\\', '_', get_class($this)).'_'.$this->getName(false);
$browser->storeConsoleLog($name.'-'.$key);
});
}
/**
* Close all of the browsers except the primary (first) one.
*
* @param \Illuminate\Support\Collection $browsers
* @return \Illuminate\Support\Collection
*/
protected function closeAllButPrimary($browsers)
{
$browsers->slice(1)->each->quit();
return $browsers->take(1);
}
/**
* Close all of the active browsers.
*
* @return void
*/
public static function closeAll()
{
Collection::make(static::$browsers)->each->quit();
static::$browsers = collect();
}
/**
* Create the remote web driver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function createWebDriver()
{
return retry(5, function () {
return $this->driver();
}, 50);
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
abstract protected function driver();
}

View File

@@ -8,6 +8,7 @@ use Carbon\Carbon;
use Illuminate\Support\Str;
use Facebook\WebDriver\Exception\TimeOutException;
use Facebook\WebDriver\Exception\NoSuchElementException;
use Facebook\WebDriver\WebDriverExpectedCondition;
trait WaitsForElements
{
@@ -19,7 +20,7 @@ trait WaitsForElements
* @param int $seconds
* @return $this
*/
public function whenAvailable($selector, Closure $callback, $seconds = 5)
public function whenAvailable($selector, Closure $callback, $seconds = null)
{
return $this->waitFor($selector, $seconds)->with($selector, $callback);
}
@@ -31,11 +32,11 @@ trait WaitsForElements
* @param int $seconds
* @return $this
*/
public function waitFor($selector, $seconds = 5)
public function waitFor($selector, $seconds = null)
{
return $this->waitUsing($seconds, 100, function () use ($selector) {
return $this->resolver->findOrFail($selector)->isDisplayed();
}, "Waited {$seconds} seconds for selector [{$selector}].");
}, "Waited %s seconds for selector [{$selector}].");
}
/**
@@ -45,7 +46,7 @@ trait WaitsForElements
* @param int $seconds
* @return $this
*/
public function waitUntilMissing($selector, $seconds = 5)
public function waitUntilMissing($selector, $seconds = null)
{
return $this->waitUsing($seconds, 100, function () use ($selector) {
try {
@@ -55,7 +56,7 @@ trait WaitsForElements
}
return $missing;
}, "Waited {$seconds} seconds for removal of selector [{$selector}].");
}, "Waited %s seconds for removal of selector [{$selector}].");
}
/**
@@ -65,11 +66,11 @@ trait WaitsForElements
* @param int $seconds
* @return $this
*/
public function waitForText($text, $seconds = 5)
public function waitForText($text, $seconds = null)
{
return $this->waitUsing($seconds, 100, function () use ($text) {
return Str::contains($this->resolver->findOrFail('')->getText(), $text);
}, "Waited {$seconds} seconds for text [{$text}].");
}, "Waited %s seconds for text [{$text}].");
}
/**
@@ -79,7 +80,7 @@ trait WaitsForElements
* @param int $seconds
* @return $this
*/
public function waitForLink($link, $seconds = 5)
public function waitForLink($link, $seconds = null)
{
return $this->waitUsing($seconds, 100, function () use ($link) {
return $this->seeLink($link);
@@ -93,11 +94,24 @@ trait WaitsForElements
* @param int $seconds
* @return $this
*/
public function waitForLocation($path, $seconds = 5)
public function waitForLocation($path, $seconds = null)
{
return $this->waitUntil("window.location.pathname == '{$path}'", $seconds);
}
/**
* Wait for the given location using a named route.
*
* @param string $route
* @param array $parameters
* @param int $seconds
* @return $this
*/
public function waitForRoute($route, $parameters = [], $seconds = null)
{
return $this->waitForLocation(route($route, $parameters), $seconds);
}
/**
* Wait until the given script returns true.
*
@@ -105,7 +119,7 @@ trait WaitsForElements
* @param int $seconds
* @return $this
*/
public function waitUntil($script, $seconds = 5)
public function waitUntil($script, $seconds = null)
{
if (! Str::startsWith($script, 'return ')) {
$script = 'return '.$script;
@@ -120,6 +134,23 @@ trait WaitsForElements
});
}
/**
* Wait for a JavaScript dialog to open.
*
* @param int $seconds
* @return $this
*/
public function waitForDialog($seconds = null)
{
$seconds = is_null($seconds) ? static::$waitSeconds : $seconds;
$this->driver->wait($seconds, 100)->until(
WebDriverExpectedCondition::alertIsPresent(), "Waited {$seconds} seconds for dialog."
);
return $this;
}
/**
* Wait for the current page to reload.
*
@@ -127,7 +158,7 @@ trait WaitsForElements
* @param int $seconds
* @return $this
*/
public function waitForReload($callback = null, $seconds = 5)
public function waitForReload($callback = null, $seconds = null)
{
$token = str_random();
@@ -139,7 +170,7 @@ trait WaitsForElements
return $this->waitUsing($seconds, 100, function () use ($token) {
return $this->driver->executeScript("return typeof window['{$token}'] === 'undefined';");
});
}, 'Waited %s seconds for page reload.');
}
/**
@@ -154,6 +185,8 @@ trait WaitsForElements
*/
public function waitUsing($seconds, $interval, Closure $callback, $message = null)
{
$seconds = is_null($seconds) ? static::$waitSeconds : $seconds;
$this->pause($interval);
$started = Carbon::now();
@@ -168,7 +201,10 @@ trait WaitsForElements
}
if ($started->lt(Carbon::now()->subSeconds($seconds))) {
throw new TimeOutException($message ?: "Waited {$seconds} seconds for callback.");
throw new TimeOutException($message
? sprintf($message, $seconds)
: "Waited {$seconds} seconds for callback."
);
}
$this->pause($interval);

View File

@@ -0,0 +1,73 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
class ComponentCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $signature = 'dusk:component {name : The name of the class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Dusk component class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Component';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/component.stub';
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
$name = str_replace_first($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Browser\Components';
}
/**
* Get the root namespace for the class.
*
* @return string
*/
protected function rootNamespace()
{
return 'Tests';
}
}

View File

@@ -3,9 +3,10 @@
namespace Laravel\Dusk\Console;
use Dotenv\Dotenv;
use Illuminate\Support\Str;
use Illuminate\Console\Command;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\ProcessBuilder;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;
class DuskCommand extends Command
@@ -15,7 +16,7 @@ class DuskCommand extends Command
*
* @var string
*/
protected $signature = 'dusk';
protected $signature = 'dusk {--without-tty : Disable output to TTY}';
/**
* The console command description.
@@ -54,17 +55,15 @@ class DuskCommand extends Command
$this->purgeConsoleLogs();
$options = array_slice($_SERVER['argv'], 2);
$options = array_slice($_SERVER['argv'], $this->option('without-tty') ? 3 : 2);
return $this->withDuskEnvironment(function () use ($options) {
$process = (new ProcessBuilder())
->setTimeout(null)
->setPrefix($this->binary())
->setArguments($this->phpunitArguments($options))
->getProcess();
$process = (new Process(array_merge(
$this->binary(), $this->phpunitArguments($options)
)))->setTimeout(null);
try {
$process->setTty(true);
$process->setTty(! $this->option('without-tty'));
} catch (RuntimeException $e) {
$this->output->writeln('Warning: '.$e->getMessage());
}
@@ -88,10 +87,15 @@ class DuskCommand extends Command
/**
* Get the array of arguments for running PHPUnit.
*
* @param array $options
* @return array
*/
protected function phpunitArguments($options)
{
$options = array_values(array_filter($options, function ($option) {
return ! Str::startsWith($option, '--env=');
}));
return array_merge(['-c', base_path('phpunit.dusk.xml')], $options);
}

View File

@@ -41,6 +41,10 @@ class InstallCommand extends Command
mkdir(base_path('tests/Browser/Pages'), 0755, true);
}
if (! is_dir(base_path('tests/Browser/Components'))) {
mkdir(base_path('tests/Browser/Components'), 0755, true);
}
if (! is_dir(base_path('tests/Browser/screenshots'))) {
$this->createScreenshotsDirectory();
}
@@ -49,14 +53,14 @@ class InstallCommand extends Command
$this->createConsoleDirectory();
}
$subs = [
$stubs = [
'ExampleTest.stub' => base_path('tests/Browser/ExampleTest.php'),
'HomePage.stub' => base_path('tests/Browser/Pages/HomePage.php'),
'DuskTestCase.stub' => base_path('tests/DuskTestCase.php'),
'Page.stub' => base_path('tests/Browser/Pages/Page.php'),
];
foreach ($subs as $stub => $file) {
foreach ($stubs as $stub => $file) {
if (! is_file($file)) {
copy(__DIR__.'/../../stubs/'.$stub, $file);
}

View File

@@ -0,0 +1,42 @@
<?php
namespace DummyNamespace;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Component as BaseComponent;
class DummyClass extends BaseComponent
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '#selector';
}
/**
* Assert that the browser page contains the component.
*
* @param Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertVisible($this->selector());
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [
'@element' => '#selector',
];
}
}

View File

@@ -3,9 +3,8 @@
namespace DummyNamespace;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Page as BasePage;
class DummyClass extends BasePage
class DummyClass extends Page
{
/**
* Get the URL for the page.

View File

@@ -49,6 +49,7 @@ class DuskServiceProvider extends ServiceProvider
Console\DuskCommand::class,
Console\MakeCommand::class,
Console\PageCommand::class,
Console\ComponentCommand::class,
]);
}
}

View File

@@ -111,7 +111,7 @@ class ElementResolver
*
* @param string $field
* @param array $values
* @return array
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
*/
public function resolveSelectOptions($field, array $values)
{
@@ -122,7 +122,7 @@ class ElementResolver
return [];
}
return array_filter($options, function($option) use ($values) {
return array_filter($options, function ($option) use ($values) {
return in_array($option->getAttribute('value'), $values);
});
}
@@ -192,6 +192,24 @@ class ElementResolver
]);
}
/**
* Resolve the element for a given "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function resolveForField($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
$field, "input[name='{$field}']", "textarea[name='{$field}']",
"select[name='{$field}']", "button[name='{$field}']"
]);
}
/**
* Resolve the element for a given button.
*
@@ -337,7 +355,7 @@ class ElementResolver
* Find the elements by the given selector or return an empty array.
*
* @param string $selector
* @return array
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
*/
public function all($selector)
{
@@ -360,7 +378,7 @@ class ElementResolver
*/
public function format($selector)
{
$sortedElements = collect($this->elements)->sortByDesc(function($element, $key){
$sortedElements = collect($this->elements)->sortByDesc(function ($element, $key) {
return strlen($key);
})->toArray();

View File

@@ -31,19 +31,16 @@ class UserController
*
* @param string $userId
* @param string $guard
* @return Response
*/
public function login($userId, $guard = null)
{
$model = $this->modelForGuard(
$guard = $guard ?: config('auth.defaults.guard')
);
$guard = $guard ?: config('auth.defaults.guard');
if (str_contains($userId, '@')) {
$user = (new $model)->where('email', $userId)->first();
} else {
$user = (new $model)->find($userId);
}
$provider = Auth::guard($guard)->getProvider();
$user = str_contains($userId, '@')
? $provider->retrieveByCredentials(['email' => $userId])
: $provider->retrieveById($userId);
Auth::guard($guard)->login($user);
}
@@ -52,7 +49,6 @@ class UserController
* Log the user out of the application.
*
* @param string $guard
* @return Response
*/
public function logout($guard = null)
{

View File

@@ -2,32 +2,16 @@
namespace Laravel\Dusk;
use Closure;
use Exception;
use Throwable;
use ReflectionFunction;
use Illuminate\Support\Collection;
use Laravel\Dusk\Chrome\SupportsChrome;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Illuminate\Foundation\Testing\TestCase as FoundationTestCase;
abstract class TestCase extends FoundationTestCase
{
use SupportsChrome;
/**
* All of the active browser instances.
*
* @var array
*/
protected static $browsers = [];
/**
* The callbacks that should be run on class tear down.
*
* @var array
*/
protected static $afterClassCallbacks = [];
use Concerns\ProvidesBrowser,
SupportsChrome;
/**
* Register the base URL with Dusk.
@@ -49,167 +33,6 @@ abstract class TestCase extends FoundationTestCase
};
}
/**
* Tear down the Dusk test case class.
*
* @afterClass
* @return void
*/
public static function tearDownDuskClass()
{
static::closeAll();
foreach (static::$afterClassCallbacks as $callback) {
$callback();
}
}
/**
* Register an "after class" tear down callback.
*
* @param \Closure $callback
* @return void
*/
public static function afterClass(Closure $callback)
{
static::$afterClassCallbacks[] = $callback;
}
/**
* Create a new browser instance.
*
* @param \Closure $callback
* @return \Laravel\Dusk\Browser|void
* @throws \Exception
* @throws \Throwable
*/
public function browse(Closure $callback)
{
$browsers = $this->createBrowsersFor($callback);
try {
$callback(...$browsers->all());
} catch (Exception $e) {
$this->captureFailuresFor($browsers);
throw $e;
} catch (Throwable $e) {
$this->captureFailuresFor($browsers);
throw $e;
} finally {
$this->storeConsoleLogsFor($browsers);
static::$browsers = $this->closeAllButPrimary($browsers);
}
}
/**
* Create the browser instances needed for the given callback.
*
* @param \Closure $callback
* @return array
*/
protected function createBrowsersFor(Closure $callback)
{
if (count(static::$browsers) === 0) {
static::$browsers = collect([$this->newBrowser($this->createWebDriver())]);
}
$additional = $this->browsersNeededFor($callback) - 1;
for ($i = 0; $i < $additional; $i++) {
static::$browsers->push($this->newBrowser($this->createWebDriver()));
}
return static::$browsers;
}
/**
* Create a new Browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @return \Laravel\Dusk\Browser
*/
protected function newBrowser($driver)
{
return new Browser($driver);
}
/**
* Get the number of browsers needed for a given callback.
*
* @param \Closure $callback
* @return int
*/
protected function browsersNeededFor(Closure $callback)
{
return (new ReflectionFunction($callback))->getNumberOfParameters();
}
/**
* Capture failure screenshots for each browser.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function captureFailuresFor($browsers)
{
$browsers->each(function ($browser, $key) {
$browser->screenshot('failure-'.$this->getName().'-'.$key);
});
}
/**
* Store the console output for the given browsers.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function storeConsoleLogsFor($browsers)
{
$browsers->each(function ($browser, $key) {
$browser->storeConsoleLog($this->getName().'-'.$key);
});
}
/**
* Close all of the browsers except the primary (first) one.
*
* @param \Illuminate\Support\Collection $browsers
* @return \Illuminate\Support\Collection
*/
protected function closeAllButPrimary($browsers)
{
$browsers->slice(1)->each->quit();
return $browsers->take(1);
}
/**
* Close all of the active browsers.
*
* @return void
*/
public static function closeAll()
{
Collection::make(static::$browsers)->each->quit();
static::$browsers = collect();
}
/**
* Create the remote web driver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function createWebDriver()
{
return retry(5, function () {
return $this->driver();
}, 50);
}
/**
* Create the RemoteWebDriver instance.
*
@@ -225,7 +48,7 @@ abstract class TestCase extends FoundationTestCase
/**
* Determine the application's base URL.
*
* @var string
* @return string
*/
protected function baseUrl()
{
@@ -233,9 +56,9 @@ abstract class TestCase extends FoundationTestCase
}
/**
* Get a callback that returns the default user to authenticate.
* Return the default user to authenticate.
*
* @return \Closure
* @return \App\User|int|null
* @throws \Exception
*/
protected function user()

View File

@@ -15,28 +15,28 @@
}
],
"require": {
"php": ">=7.0",
"php": "^7.1.3",
"ext-mbstring": "*",
"ext-openssl": "*",
"doctrine/inflector": "~1.1",
"dragonmantank/cron-expression": "~2.0",
"erusev/parsedown": "~1.7",
"league/flysystem": "^1.0.8",
"monolog/monolog": "~1.12",
"mtdowling/cron-expression": "~1.0",
"nesbot/carbon": "^1.24.1",
"nesbot/carbon": "1.25.*",
"psr/container": "~1.0",
"psr/simple-cache": "^1.0",
"ramsey/uuid": "~3.0",
"ramsey/uuid": "^3.7",
"swiftmailer/swiftmailer": "~6.0",
"symfony/console": "~3.3",
"symfony/debug": "~3.3",
"symfony/finder": "~3.3",
"symfony/http-foundation": "~3.3",
"symfony/http-kernel": "~3.3",
"symfony/process": "~3.3",
"symfony/routing": "~3.3",
"symfony/var-dumper": "~3.3",
"tijsverkoyen/css-to-inline-styles": "~2.2",
"symfony/console": "~4.0",
"symfony/debug": "~4.0",
"symfony/finder": "~4.0",
"symfony/http-foundation": "~4.0",
"symfony/http-kernel": "~4.0",
"symfony/process": "~4.0",
"symfony/routing": "~4.0",
"symfony/var-dumper": "~4.0",
"tijsverkoyen/css-to-inline-styles": "^2.2.1",
"vlucas/phpdotenv": "~2.2"
},
"replace": {
@@ -67,20 +67,24 @@
"illuminate/support": "self.version",
"illuminate/translation": "self.version",
"illuminate/validation": "self.version",
"illuminate/view": "self.version",
"illuminate/view": "self.version"
},
"conflict": {
"tightenco/collect": "<5.5.33"
},
"require-dev": {
"aws/aws-sdk-php": "~3.0",
"doctrine/dbal": "~2.5",
"doctrine/dbal": "~2.6",
"filp/whoops": "^2.1.4",
"league/flysystem-cached-adapter": "~1.0",
"mockery/mockery": "~1.0",
"orchestra/testbench-core": "3.5.*",
"moontoast/math": "^1.1",
"orchestra/testbench-core": "3.6.*",
"pda/pheanstalk": "~3.0",
"phpunit/phpunit": "~6.0",
"phpunit/phpunit": "~7.0",
"predis/predis": "^1.1.1",
"symfony/css-selector": "~3.3",
"symfony/dom-crawler": "~3.3"
"symfony/css-selector": "~4.0",
"symfony/dom-crawler": "~4.0"
},
"autoload": {
"files": [
@@ -101,26 +105,27 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"suggest": {
"ext-pcntl": "Required to use all features of the queue worker.",
"ext-posix": "Required to use all features of the queue worker.",
"aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).",
"doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).",
"doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.6).",
"fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).",
"guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).",
"laravel/tinker": "Required to use the tinker console command (~1.0).",
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).",
"league/flysystem-cached-adapter": "Required to use the Flysystem cache (~1.0).",
"league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).",
"league/flysystem-cached-adapter": "Required to use Flysystem caching (~1.0).",
"league/flysystem-sftp": "Required to use the Flysystem SFTP driver (~1.0).",
"nexmo/client": "Required to use the Nexmo transport (~1.0).",
"pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).",
"predis/predis": "Required to use the redis cache and queue drivers (~1.0).",
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~3.0).",
"symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.3).",
"symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.3).",
"symfony/css-selector": "Required to use some of the crawler integration testing tools (~4.0).",
"symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~4.0).",
"symfony/psr-http-message-bridge": "Required to psr7 bridging features (~1.0)."
},
"config": {

View File

@@ -30,7 +30,7 @@ If you're not in the mood to read, [Laracasts](https://laracasts.com) contains o
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](http://laravel.com/docs/contributions).
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
@@ -42,4 +42,4 @@ If you discover a security vulnerability within Laravel, please send an e-mail t
## License
The Laravel framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@@ -122,7 +122,7 @@ class Gate implements GateContract
*
* @param string $name
* @param string $class
* @param array $abilities
* @param array|null $abilities
* @return $this
*/
public function resource($name, $class, array $abilities = null)

View File

@@ -35,7 +35,7 @@ class Response
/**
* Get the string representation of the message.
*
* @return string
* @return string|null
*/
public function __toString()
{

View File

@@ -94,7 +94,7 @@ class AuthManager implements FactoryContract
return $this->{$driverMethod}($name, $config);
}
throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
throw new InvalidArgumentException("Auth driver [{$config['driver']}] for guard [{$name}] is not defined.");
}
/**

View File

@@ -2,61 +2,63 @@
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Login</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('login') }}">
{{ csrf_field() }}
<div class="card-body">
<form method="POST" action="{{ route('login') }}" aria-label="{{ __('Login') }}">
@csrf
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="form-group row">
<label for="email" class="col-sm-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required autofocus>
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="help-block">
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
<label for="password" class="col-md-4 control-label">Password</label>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<div class="checkbox">
<label>
<input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> Remember Me
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-8 col-md-offset-4">
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
Login
{{ __('Login') }}
</button>
<a class="btn btn-link" href="{{ route('password.request') }}">
Forgot Your Password?
{{ __('Forgot Your Password?') }}
</a>
</div>
</div>

View File

@@ -2,39 +2,39 @@
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Reset Password</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="panel-body">
<div class="card-body">
@if (session('status'))
<div class="alert alert-success">
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<form class="form-horizontal" method="POST" action="{{ route('password.email') }}">
{{ csrf_field() }}
<form method="POST" action="{{ route('password.email') }}" aria-label="{{ __('Reset Password') }}">
@csrf
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="help-block">
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
Send Password Reset Link
{{ __('Send Password Reset Link') }}
</button>
</div>
</div>

View File

@@ -2,62 +2,57 @@
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Reset Password</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('password.request') }}">
{{ csrf_field() }}
<div class="card-body">
<form method="POST" action="{{ route('password.request') }}" aria-label="{{ __('Reset Password') }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ $email or old('email') }}" required autofocus>
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email ?? old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="help-block">
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
<label for="password" class="col-md-4 control-label">Password</label>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('password_confirmation') ? ' has-error' : '' }}">
<label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
@if ($errors->has('password_confirmation'))
<span class="help-block">
<strong>{{ $errors->first('password_confirmation') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
Reset Password
{{ __('Reset Password') }}
</button>
</div>
</div>

View File

@@ -2,69 +2,69 @@
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Register</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Register') }}</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('register') }}">
{{ csrf_field() }}
<div class="card-body">
<form method="POST" action="{{ route('register') }}" aria-label="{{ __('Register') }}">
@csrf
<div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
<label for="name" class="col-md-4 control-label">Name</label>
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autofocus>
<input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus>
@if ($errors->has('name'))
<span class="help-block">
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('name') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Address</label>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="help-block">
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
<label for="password" class="col-md-4 control-label">Password</label>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>
@if ($errors->has('password'))
<span class="help-block">
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
Register
{{ __('Register') }}
</button>
</div>
</div>

View File

@@ -2,14 +2,14 @@
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Dashboard</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Dashboard</div>
<div class="panel-body">
<div class="card-body">
@if (session('status'))
<div class="alert alert-success">
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -10,60 +10,60 @@
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
<nav class="navbar navbar-default navbar-static-top">
<nav class="navbar navbar-expand-md navbar-light navbar-laravel">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Collapsed Hamburger -->
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse" aria-expanded="false">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Branding Image -->
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
</div>
<div class="collapse navbar-collapse" id="app-navbar-collapse">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="nav navbar-nav">
&nbsp;
<ul class="navbar-nav mr-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="nav navbar-nav navbar-right">
<ul class="navbar-nav ml-auto">
<!-- Authentication Links -->
@guest
<li><a href="{{ route('login') }}">Login</a></li>
<li><a href="{{ route('register') }}">Register</a></li>
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@else
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true" v-pre>
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<a href="{{ route('logout') }}"
onclick="event.preventDefault();
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
Logout
</a>
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
{{ csrf_field() }}
</form>
</li>
</ul>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
</ul>
@@ -71,10 +71,9 @@
</div>
</nav>
@yield('content')
<main class="py-4">
@yield('content')
</main>
</div>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

View File

@@ -4,6 +4,7 @@ namespace Illuminate\Auth;
use Illuminate\Support\Str;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
@@ -110,7 +111,13 @@ class DatabaseUserProvider implements UserProvider
$query = $this->conn->table($this->table);
foreach ($credentials as $key => $value) {
if (! Str::contains($key, 'password')) {
if (Str::contains($key, 'password')) {
continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}

View File

@@ -4,6 +4,7 @@ namespace Illuminate\Auth;
use Illuminate\Support\Str;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
@@ -113,7 +114,13 @@ class EloquentUserProvider implements UserProvider
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value) {
if (! Str::contains($key, 'password')) {
if (Str::contains($key, 'password')) {
continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}

View File

@@ -40,6 +40,16 @@ trait GuardHelpers
throw new AuthenticationException;
}
/**
* Determine if the guard has a user instance.
*
* @return bool
*/
public function hasUser()
{
return ! is_null($this->user);
}
/**
* Determine if the current user is authenticated.
*

View File

@@ -42,7 +42,7 @@ class Authorize
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $ability
* @param array|null $models
* @param array|null ...$models
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Auth\Notifications;
use Illuminate\Support\Facades\Lang;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
@@ -14,6 +15,13 @@ class ResetPassword extends Notification
*/
public $token;
/**
* The callback that should be used to build the mail message.
*
* @var \Closure|null
*/
public static $toMailCallback;
/**
* Create a notification instance.
*
@@ -44,9 +52,25 @@ class ResetPassword extends Notification
*/
public function toMail($notifiable)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this->token);
}
return (new MailMessage)
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', url(config('app.url').route('password.reset', $this->token, false)))
->line('If you did not request a password reset, no further action is required.');
->subject(Lang::getFromJson('Reset Password Notification'))
->line(Lang::getFromJson('You are receiving this email because we received a password reset request for your account.'))
->action(Lang::getFromJson('Reset Password'), url(config('app.url').route('password.reset', $this->token, false)))
->line(Lang::getFromJson('If you did not request a password reset, no further action is required.'));
}
/**
* Set a callback that should be used when building the notification mail message.
*
* @param \Closure $callback
* @return void
*/
public static function toMailUsing($callback)
{
static::$toMailCallback = $callback;
}
}

View File

@@ -39,7 +39,7 @@ class PasswordBrokerManager implements FactoryContract
/**
* Attempt to get the broker from the local cache.
*
* @param string $name
* @param string|null $name
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker($name = null)

View File

@@ -4,6 +4,7 @@ namespace Illuminate\Auth;
use RuntimeException;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\Auth\UserProvider;
@@ -90,7 +91,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
* @param string $name
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Illuminate\Contracts\Session\Session $session
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \Symfony\Component\HttpFoundation\Request|null $request
* @return void
*/
public function __construct($name,
@@ -533,6 +534,26 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth
$this->provider->updateRememberToken($user, $token);
}
/**
* Invalidate other sessions for the current user.
*
* The application must be using the AuthenticateSession middleware.
*
* @param string $password
* @param string $attribute
* @return null|bool
*/
public function logoutOtherDevices($password, $attribute = 'password')
{
if (! $this->user()) {
return;
}
return tap($this->user()->forceFill([
$attribute => Hash::make($password),
]))->save();
}
/**
* Register an authentication attempt event listener.
*

View File

@@ -36,14 +36,16 @@ class TokenGuard implements Guard
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Illuminate\Http\Request $request
* @param string $inputKey
* @param string $storageKey
* @return void
*/
public function __construct(UserProvider $provider, Request $request)
public function __construct(UserProvider $provider, Request $request, $inputKey = 'api_token', $storageKey = 'api_token')
{
$this->request = $request;
$this->provider = $provider;
$this->inputKey = 'api_token';
$this->storageKey = 'api_token';
$this->inputKey = $inputKey;
$this->storageKey = $storageKey;
}
/**

View File

@@ -14,11 +14,11 @@
}
],
"require": {
"php": ">=7.0",
"illuminate/contracts": "5.5.*",
"illuminate/http": "5.5.*",
"illuminate/queue": "5.5.*",
"illuminate/support": "5.5.*"
"php": "^7.1.3",
"illuminate/contracts": "5.6.*",
"illuminate/http": "5.6.*",
"illuminate/queue": "5.6.*",
"illuminate/support": "5.6.*"
},
"autoload": {
"psr-4": {
@@ -27,13 +27,13 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"suggest": {
"illuminate/console": "Required to use the auth:clear-resets command (5.5.*).",
"illuminate/queue": "Required to fire login / logout events (5.5.*).",
"illuminate/session": "Required to use the session based guard (5.5.*)."
"illuminate/console": "Required to use the auth:clear-resets command (5.6.*).",
"illuminate/queue": "Required to fire login / logout events (5.6.*).",
"illuminate/session": "Required to use the session based guard (5.6.*)."
},
"config": {
"sort-packages": true

View File

@@ -65,7 +65,10 @@ class BroadcastManager implements FactoryContract
$attributes = $attributes ?: ['middleware' => ['web']];
$this->app['router']->group($attributes, function ($router) {
$router->post('/broadcasting/auth', '\\'.BroadcastController::class.'@authenticate');
$router->match(
['get', 'post'], '/broadcasting/auth',
'\\'.BroadcastController::class.'@authenticate'
);
});
}
@@ -140,7 +143,7 @@ class BroadcastManager implements FactoryContract
/**
* Get a driver instance.
*
* @param string $name
* @param string|null $name
* @return mixed
*/
public function driver($name = null)

View File

@@ -2,6 +2,8 @@
namespace Illuminate\Broadcasting\Broadcasters;
use Exception;
use ReflectionClass;
use ReflectionFunction;
use Illuminate\Support\Str;
use Illuminate\Container\Container;
@@ -30,10 +32,10 @@ abstract class Broadcaster implements BroadcasterContract
* Register a channel authenticator.
*
* @param string $channel
* @param callable $callback
* @param callable|string $callback
* @return $this
*/
public function channel($channel, callable $callback)
public function channel($channel, $callback)
{
$this->channels[$channel] = $callback;
@@ -57,7 +59,9 @@ abstract class Broadcaster implements BroadcasterContract
$parameters = $this->extractAuthParameters($pattern, $channel, $callback);
if ($result = $callback($request->user(), ...$parameters)) {
$handler = $this->normalizeChannelHandlerToCallable($callback);
if ($result = $handler($request->user(), ...$parameters)) {
return $this->validAuthenticationResponse($request, $result);
}
}
@@ -70,12 +74,12 @@ abstract class Broadcaster implements BroadcasterContract
*
* @param string $pattern
* @param string $channel
* @param callable $callback
* @param callable|string $callback
* @return array
*/
protected function extractAuthParameters($pattern, $channel, $callback)
{
$callbackParameters = (new ReflectionFunction($callback))->getParameters();
$callbackParameters = $this->extractParameters($callback);
return collect($this->extractChannelKeys($pattern, $channel))->reject(function ($value, $key) {
return is_numeric($key);
@@ -84,6 +88,42 @@ abstract class Broadcaster implements BroadcasterContract
})->values()->all();
}
/**
* Extracts the parameters out of what the user passed to handle the channel authentication.
*
* @param callable|string $callback
* @return \ReflectionParameter[]
* @throws \Exception
*/
protected function extractParameters($callback)
{
if (is_callable($callback)) {
return (new ReflectionFunction($callback))->getParameters();
} elseif (is_string($callback)) {
return $this->extractParametersFromClass($callback);
}
throw new Exception('Given channel handler is an unknown type.');
}
/**
* Extracts the parameters out of a class channel's "join" method.
*
* @param string $callback
* @return \ReflectionParameter[]
* @throws \Exception
*/
protected function extractParametersFromClass($callback)
{
$reflection = new ReflectionClass($callback);
if (! $reflection->hasMethod('join')) {
throw new Exception('Class based channel must define a "join" method.');
}
return $reflection->getMethod('join')->getParameters();
}
/**
* Extract the channel keys from the incoming channel name.
*
@@ -201,4 +241,19 @@ abstract class Broadcaster implements BroadcasterContract
return $this->bindingRegistrar;
}
/**
* Normalize the given callback into a callable.
*
* @param mixed $callback
* @return callable|\Closure
*/
protected function normalizeChannelHandlerToCallable($callback)
{
return is_callable($callback) ? $callback : function (...$args) use ($callback) {
return Container::getInstance()
->make($callback)
->join(...$args);
};
}
}

View File

@@ -62,25 +62,34 @@ class PusherBroadcaster extends Broadcaster
{
if (Str::startsWith($request->channel_name, 'private')) {
return $this->decodePusherResponse(
$this->pusher->socket_auth($request->channel_name, $request->socket_id)
$request, $this->pusher->socket_auth($request->channel_name, $request->socket_id)
);
}
return $this->decodePusherResponse(
$request,
$this->pusher->presence_auth(
$request->channel_name, $request->socket_id, $request->user()->getAuthIdentifier(), $result)
$request->channel_name, $request->socket_id,
$request->user()->getAuthIdentifier(), $result
)
);
}
/**
* Decode the given Pusher response.
*
* @param \Illuminate\Http\Request $request
* @param mixed $response
* @return array
*/
protected function decodePusherResponse($response)
protected function decodePusherResponse($request, $response)
{
return json_decode($response, true);
if (! $request->input('callback', false)) {
return json_decode($response, true);
}
return response()->json(json_decode($response, true))
->withCallback($request->callback);
}
/**

View File

@@ -27,7 +27,7 @@ class RedisBroadcaster extends Broadcaster
* Create a new broadcaster instance.
*
* @param \Illuminate\Contracts\Redis\Factory $redis
* @param string $connection
* @param string|null $connection
* @return void
*/
public function __construct(Redis $redis, $connection = null)

View File

@@ -14,12 +14,12 @@
}
],
"require": {
"php": ">=7.0",
"php": "^7.1.3",
"psr/log": "~1.0",
"illuminate/bus": "5.5.*",
"illuminate/contracts": "5.5.*",
"illuminate/queue": "5.5.*",
"illuminate/support": "5.5.*"
"illuminate/bus": "5.6.*",
"illuminate/contracts": "5.6.*",
"illuminate/queue": "5.6.*",
"illuminate/support": "5.6.*"
},
"autoload": {
"psr-4": {
@@ -28,7 +28,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"suggest": {

View File

@@ -14,10 +14,10 @@
}
],
"require": {
"php": ">=7.0",
"illuminate/contracts": "5.5.*",
"illuminate/pipeline": "5.5.*",
"illuminate/support": "5.5.*"
"php": "^7.1.3",
"illuminate/contracts": "5.6.*",
"illuminate/pipeline": "5.6.*",
"illuminate/support": "5.6.*"
},
"autoload": {
"psr-4": {
@@ -26,7 +26,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"config": {

View File

@@ -23,9 +23,7 @@ class ArrayStore extends TaggableStore implements Store
*/
public function get($key)
{
if (array_key_exists($key, $this->storage)) {
return $this->storage[$key];
}
return $this->storage[$key] ?? null;
}
/**

View File

@@ -61,7 +61,7 @@ class CacheManager implements FactoryContract
/**
* Get a cache driver instance.
*
* @param string $driver
* @param string|null $driver
* @return mixed
*/
public function driver($driver = null)

View File

@@ -72,7 +72,7 @@ class ClearCommand extends Command
'cache:cleared', [$this->argument('store'), $this->tags()]
);
$this->info('Cache cleared successfully.');
$this->info('Application cache cleared!');
}
/**
@@ -82,7 +82,11 @@ class ClearCommand extends Command
*/
public function flushFacades()
{
foreach ($this->files->files(storage_path('framework/cache')) as $file) {
if (! $this->files->exists($storagePath = storage_path('framework/cache'))) {
return;
}
foreach ($this->files->files($storagePath) as $file) {
if (preg_match('/facade-.*\.php$/', $file)) {
$this->files->delete($file);
}

View File

@@ -91,7 +91,7 @@ class FileStore implements Store
$raw = $this->getPayload($key);
return tap(((int) $raw['data']) + $value, function ($newValue) use ($key, $raw) {
$this->put($key, $newValue, $raw['time']);
$this->put($key, $newValue, $raw['time'] ?? 0);
});
}

View File

@@ -47,7 +47,7 @@ class MemcachedConnector
{
$memcached = $this->createMemcachedInstance($connectionId);
if (count($credentials) == 2) {
if (count($credentials) === 2) {
$this->setCredentials($memcached, $credentials);
}

View File

@@ -32,10 +32,9 @@ class RateLimiter
*
* @param string $key
* @param int $maxAttempts
* @param float|int $decayMinutes
* @return bool
*/
public function tooManyAttempts($key, $maxAttempts, $decayMinutes = 1)
public function tooManyAttempts($key, $maxAttempts)
{
if ($this->attempts($key) >= $maxAttempts) {
if ($this->cache->has($key.':timer')) {

View File

@@ -22,7 +22,7 @@ class RedisTaggedCache extends TaggedCache
*
* @param string $key
* @param mixed $value
* @param \DateTime|float|int $minutes
* @param \DateTime|float|int|null $minutes
* @return void
*/
public function put($key, $value, $minutes = null)
@@ -32,6 +32,34 @@ class RedisTaggedCache extends TaggedCache
parent::put($key, $value, $minutes);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function increment($key, $value = 1)
{
$this->pushStandardKeys($this->tags->getNamespace(), $key);
parent::increment($key, $value);
}
/**
* Decrement the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function decrement($key, $value = 1)
{
$this->pushStandardKeys($this->tags->getNamespace(), $key);
parent::decrement($key, $value);
}
/**
* Store an item in the cache indefinitely.
*

View File

@@ -182,7 +182,7 @@ class Repository implements CacheContract, ArrayAccess
*
* @param string $key
* @param mixed $value
* @param \DateTimeInterface|\DateInterval|float|int $minutes
* @param \DateTimeInterface|\DateInterval|float|int|null $minutes
* @return void
*/
public function put($key, $value, $minutes = null)
@@ -419,7 +419,7 @@ class Repository implements CacheContract, ArrayAccess
throw new BadMethodCallException('This cache store does not support tagging.');
}
$cache = $this->store->tags($names);
$cache = $this->store->tags(is_array($names) ? $names : func_get_args());
if (! is_null($this->events)) {
$cache->setEventDispatcher($this->events);

View File

@@ -42,7 +42,7 @@ class TaggedCache extends Repository
}
/**
* Increment the value of an item in the cache.
* Decrement the value of an item in the cache.
*
* @param string $key
* @param mixed $value

View File

@@ -14,9 +14,9 @@
}
],
"require": {
"php": ">=7.0",
"illuminate/contracts": "5.5.*",
"illuminate/support": "5.5.*"
"php": "^7.1.3",
"illuminate/contracts": "5.6.*",
"illuminate/support": "5.6.*"
},
"autoload": {
"psr-4": {
@@ -25,13 +25,13 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"suggest": {
"illuminate/database": "Required to use the database cache driver (5.5.*).",
"illuminate/filesystem": "Required to use the file cache driver (5.5.*).",
"illuminate/redis": "Required to use the redis cache driver (5.5.*)."
"illuminate/database": "Required to use the database cache driver (5.6.*).",
"illuminate/filesystem": "Required to use the file cache driver (5.6.*).",
"illuminate/redis": "Required to use the redis cache driver (5.6.*)."
},
"config": {
"sort-packages": true

View File

@@ -14,9 +14,9 @@
}
],
"require": {
"php": ">=7.0",
"illuminate/contracts": "5.5.*",
"illuminate/support": "5.5.*"
"php": "^7.1.3",
"illuminate/contracts": "5.6.*",
"illuminate/support": "5.6.*"
},
"autoload": {
"psr-4": {
@@ -25,7 +25,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"config": {

View File

@@ -16,6 +16,7 @@ use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Illuminate\Contracts\Console\Application as ApplicationContract;
class Application extends SymfonyApplication implements ApplicationContract
@@ -163,18 +164,28 @@ class Application extends SymfonyApplication implements ApplicationContract
*
* @param string $command
* @param array $parameters
* @param \Symfony\Component\Console\Output\OutputInterface $outputBuffer
* @param \Symfony\Component\Console\Output\OutputInterface|null $outputBuffer
* @return int
*
* @throws \Symfony\Component\Console\Exception\CommandNotFoundException
*/
public function call($command, array $parameters = [], $outputBuffer = null)
{
$parameters = collect($parameters)->prepend($command);
if (is_subclass_of($command, SymfonyCommand::class)) {
$command = $this->laravel->make($command)->getName();
}
if (! $this->has($command)) {
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $command));
}
array_unshift($parameters, $command);
$this->lastOutput = $outputBuffer ?: new BufferedOutput;
$this->setCatchExceptions(false);
$result = $this->run(new ArrayInput($parameters->toArray()), $this->lastOutput);
$result = $this->run(new ArrayInput($parameters), $this->lastOutput);
$this->setCatchExceptions(true);
@@ -188,7 +199,9 @@ class Application extends SymfonyApplication implements ApplicationContract
*/
public function output()
{
return $this->lastOutput ? $this->lastOutput->fetch() : '';
return $this->lastOutput && method_exists($this->lastOutput, 'fetch')
? $this->lastOutput->fetch()
: '';
}
/**

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Console;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Support\Arrayable;
use Symfony\Component\Console\Helper\Table;
@@ -245,7 +246,7 @@ class Command extends SymfonyCommand
* Get the value of a command argument.
*
* @param string|null $key
* @return string|array
* @return string|array|null
*/
public function argument($key = null)
{
@@ -280,8 +281,8 @@ class Command extends SymfonyCommand
/**
* Get the value of a command option.
*
* @param string $key
* @return string|array
* @param string|null $key
* @return string|array|null
*/
public function option($key = null)
{
@@ -318,7 +319,7 @@ class Command extends SymfonyCommand
* Prompt the user for input.
*
* @param string $question
* @param string $default
* @param string|null $default
* @return string
*/
public function ask($question, $default = null)
@@ -331,7 +332,7 @@ class Command extends SymfonyCommand
*
* @param string $question
* @param array $choices
* @param string $default
* @param string|null $default
* @return string
*/
public function anticipate($question, array $choices, $default = null)
@@ -344,7 +345,7 @@ class Command extends SymfonyCommand
*
* @param string $question
* @param array $choices
* @param string $default
* @param string|null $default
* @return string
*/
public function askWithCompletion($question, array $choices, $default = null)
@@ -377,9 +378,9 @@ class Command extends SymfonyCommand
*
* @param string $question
* @param array $choices
* @param string $default
* @param mixed $attempts
* @param bool $multiple
* @param string|null $default
* @param mixed|null $attempts
* @param bool|null $multiple
* @return string
*/
public function choice($question, array $choices, $default = null, $attempts = null, $multiple = null)
@@ -506,9 +507,11 @@ class Command extends SymfonyCommand
*/
public function alert($string)
{
$this->comment(str_repeat('*', strlen($string) + 12));
$length = Str::length(strip_tags($string)) + 12;
$this->comment(str_repeat('*', $length));
$this->comment('* '.$string.' *');
$this->comment(str_repeat('*', strlen($string) + 12));
$this->comment(str_repeat('*', $length));
$this->output->newLine();
}
@@ -527,7 +530,7 @@ class Command extends SymfonyCommand
/**
* Get the verbosity level in terms of Symfony's OutputInterface level.
*
* @param string|int $level
* @param string|int|null $level
* @return int
*/
protected function parseVerbosity($level = null)

View File

@@ -56,7 +56,9 @@ abstract class GeneratorCommand extends Command
// First we will check to see if the class already exists. If it does, we don't want
// to create the class and overwrite the user's code. So, we will bail out so the
// code is untouched. Otherwise, we will continue generating this class' files.
if ((! $this->hasOption('force') || ! $this->option('force')) && $this->alreadyExists($this->getNameInput())) {
if ((! $this->hasOption('force') ||
! $this->option('force')) &&
$this->alreadyExists($this->getNameInput())) {
$this->error($this->type.' already exists!');
return false;

View File

@@ -0,0 +1,81 @@
<?php
namespace Illuminate\Console\Scheduling;
use Illuminate\Contracts\Cache\Factory as Cache;
class CacheEventMutex implements EventMutex
{
/**
* The cache repository implementation.
*
* @var \Illuminate\Contracts\Cache\Factory
*/
public $cache;
/**
* The cache store that should be used.
*
* @var string|null
*/
public $store;
/**
* Create a new overlapping strategy.
*
* @param \Illuminate\Contracts\Cache\Factory $cache
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
/**
* Attempt to obtain an event mutex for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return bool
*/
public function create(Event $event)
{
return $this->cache->store($this->store)->add(
$event->mutexName(), true, $event->expiresAt
);
}
/**
* Determine if an event mutex exists for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return bool
*/
public function exists(Event $event)
{
return $this->cache->store($this->store)->has($event->mutexName());
}
/**
* Clear the event mutex for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return void
*/
public function forget(Event $event)
{
$this->cache->store($this->store)->forget($event->mutexName());
}
/**
* Specify the cache store that should be used.
*
* @param string $store
* @return $this
*/
public function useStore($store)
{
$this->store = $store;
return $this;
}
}

View File

@@ -1,61 +0,0 @@
<?php
namespace Illuminate\Console\Scheduling;
use Illuminate\Contracts\Cache\Repository as Cache;
class CacheMutex implements Mutex
{
/**
* The cache repository implementation.
*
* @var \Illuminate\Contracts\Cache\Repository
*/
public $cache;
/**
* Create a new overlapping strategy.
*
* @param \Illuminate\Contracts\Cache\Repository $cache
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
/**
* Attempt to obtain a mutex for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return bool
*/
public function create(Event $event)
{
return $this->cache->add(
$event->mutexName(), true, $event->expiresAt
);
}
/**
* Determine if a mutex exists for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return bool
*/
public function exists(Event $event)
{
return $this->cache->has($event->mutexName());
}
/**
* Clear the mutex for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return void
*/
public function forget(Event $event)
{
$this->cache->forget($event->mutexName());
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Illuminate\Console\Scheduling;
use DateTimeInterface;
use Illuminate\Contracts\Cache\Factory as Cache;
class CacheSchedulingMutex implements SchedulingMutex
{
/**
* The cache factory implementation.
*
* @var \Illuminate\Contracts\Cache\Factory
*/
public $cache;
/**
* The cache store that should be used.
*
* @var string|null
*/
public $store;
/**
* Create a new scheduling strategy.
*
* @param \Illuminate\Contracts\Cache\Factory $cache
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
/**
* Attempt to obtain a scheduling mutex for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @param \DateTimeInterface $time
* @return bool
*/
public function create(Event $event, DateTimeInterface $time)
{
return $this->cache->store($this->store)->add(
$event->mutexName().$time->format('Hi'), true, 60
);
}
/**
* Determine if a scheduling mutex exists for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @param \DateTimeInterface $time
* @return bool
*/
public function exists(Event $event, DateTimeInterface $time)
{
return $this->cache->store($this->store)->has(
$event->mutexName().$time->format('Hi')
);
}
/**
* Specify the cache store that should be used.
*
* @param string $store
* @return $this
*/
public function useStore($store)
{
$this->store = $store;
return $this;
}
}

View File

@@ -25,14 +25,14 @@ class CallbackEvent extends Event
/**
* Create a new event instance.
*
* @param \Illuminate\Console\Scheduling\Mutex $mutex
* @param \Illuminate\Console\Scheduling\EventMutex $mutex
* @param string $callback
* @param array $parameters
* @return void
*
* @throws \InvalidArgumentException
*/
public function __construct(Mutex $mutex, $callback, array $parameters = [])
public function __construct(EventMutex $mutex, $callback, array $parameters = [])
{
if (! is_string($callback) && ! is_callable($callback)) {
throw new InvalidArgumentException(
@@ -98,6 +98,8 @@ class CallbackEvent extends Event
*
* @param int $expiresAt
* @return $this
*
* @throws \LogicException
*/
public function withoutOverlapping($expiresAt = 1440)
{
@@ -116,6 +118,26 @@ class CallbackEvent extends Event
});
}
/**
* Allow the event to only run on one server for each cron expression.
*
* @return $this
*
* @throws \LogicException
*/
public function onOneServer()
{
if (! isset($this->description)) {
throw new LogicException(
"A scheduled event name is required to only run on one server. Use the 'name' method before 'onOneServer'."
);
}
$this->onOneServer = true;
return $this;
}
/**
* Get the mutex name for the scheduled command.
*

View File

@@ -4,6 +4,7 @@ namespace Illuminate\Console\Scheduling;
use Closure;
use Cron\CronExpression;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use GuzzleHttp\Client as HttpClient;
use Illuminate\Contracts\Mail\Mailer;
@@ -27,7 +28,7 @@ class Event
*
* @var string
*/
public $expression = '* * * * * *';
public $expression = '* * * * *';
/**
* The timezone the date should be evaluated on.
@@ -64,6 +65,13 @@ class Event
*/
public $withoutOverlapping = false;
/**
* Indicates if the command should only be allowed to run on one server for each cron expression.
*
* @var bool
*/
public $onOneServer = false;
/**
* The amount of time the mutex should be valid.
*
@@ -128,20 +136,20 @@ class Event
public $description;
/**
* The mutex implementation.
* The event mutex implementation.
*
* @var \Illuminate\Console\Scheduling\Mutex
* @var \Illuminate\Console\Scheduling\EventMutex
*/
public $mutex;
/**
* Create a new event instance.
*
* @param \Illuminate\Console\Scheduling\Mutex $mutex
* @param \Illuminate\Console\Scheduling\EventMutex $mutex
* @param string $command
* @return void
*/
public function __construct(Mutex $mutex, $command)
public function __construct(EventMutex $mutex, $command)
{
$this->mutex = $mutex;
$this->command = $command;
@@ -370,7 +378,7 @@ class Event
{
$this->ensureOutputIsBeingCapturedForEmail();
$addresses = is_array($addresses) ? $addresses : [$addresses];
$addresses = Arr::wrap($addresses);
return $this->then(function (Mailer $mailer) use ($addresses, $onlyIfOutputExists) {
$this->emailOutput($mailer, $addresses, $onlyIfOutputExists);
@@ -450,6 +458,18 @@ class Event
});
}
/**
* Register a callback to ping a given URL before the job runs if the given condition is true.
*
* @param bool $value
* @param string $url
* @return $this
*/
public function pingBeforeIf($value, $url)
{
return $value ? $this->pingBefore($url) : $this;
}
/**
* Register a callback to ping a given URL after the job runs.
*
@@ -463,6 +483,18 @@ class Event
});
}
/**
* Register a callback to ping a given URL after the job runs if the given condition is true.
*
* @param bool $value
* @param string $url
* @return $this
*/
public function thenPingIf($value, $url)
{
return $value ? $this->thenPing($url) : $this;
}
/**
* State that the command should run in background.
*
@@ -532,6 +564,18 @@ class Event
});
}
/**
* Allow the event to only run on one server for each cron expression.
*
* @return $this
*/
public function onOneServer()
{
$this->onOneServer = true;
return $this;
}
/**
* Register a callback to further filter the schedule.
*
@@ -663,12 +707,12 @@ class Event
}
/**
* Set the mutex implementation to be used.
* Set the event mutex implementation to be used.
*
* @param \Illuminate\Console\Scheduling\Mutex $mutex
* @param \Illuminate\Console\Scheduling\EventMutex $mutex
* @return $this
*/
public function preventOverlapsUsing(Mutex $mutex)
public function preventOverlapsUsing(EventMutex $mutex)
{
$this->mutex = $mutex;

View File

@@ -2,10 +2,10 @@
namespace Illuminate\Console\Scheduling;
interface Mutex
interface EventMutex
{
/**
* Attempt to obtain a mutex for the given event.
* Attempt to obtain an event mutex for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return bool
@@ -13,7 +13,7 @@ interface Mutex
public function create(Event $event);
/**
* Determine if a mutex exists for the given event.
* Determine if an event mutex exists for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return bool
@@ -21,7 +21,7 @@ interface Mutex
public function exists(Event $event);
/**
* Clear the mutex for the given event.
* Clear the event mutex for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return void

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Console\Scheduling;
use DateTimeInterface;
use Illuminate\Console\Application;
use Illuminate\Container\Container;
use Illuminate\Support\ProcessUtils;
@@ -17,11 +18,18 @@ class Schedule
protected $events = [];
/**
* The mutex implementation.
* The event mutex implementation.
*
* @var \Illuminate\Console\Scheduling\Mutex
* @var \Illuminate\Console\Scheduling\EventMutex
*/
protected $mutex;
protected $eventMutex;
/**
* The scheduling mutex implementation.
*
* @var \Illuminate\Console\Scheduling\SchedulingMutex
*/
protected $schedulingMutex;
/**
* Create a new schedule instance.
@@ -32,9 +40,13 @@ class Schedule
{
$container = Container::getInstance();
$this->mutex = $container->bound(Mutex::class)
? $container->make(Mutex::class)
: $container->make(CacheMutex::class);
$this->eventMutex = $container->bound(EventMutex::class)
? $container->make(EventMutex::class)
: $container->make(CacheEventMutex::class);
$this->schedulingMutex = $container->bound(SchedulingMutex::class)
? $container->make(SchedulingMutex::class)
: $container->make(CacheSchedulingMutex::class);
}
/**
@@ -47,7 +59,7 @@ class Schedule
public function call($callback, array $parameters = [])
{
$this->events[] = $event = new CallbackEvent(
$this->mutex, $callback, $parameters
$this->eventMutex, $callback, $parameters
);
return $event;
@@ -104,7 +116,7 @@ class Schedule
$command .= ' '.$this->compileParameters($parameters);
}
$this->events[] = $event = new Event($this->mutex, $command);
$this->events[] = $event = new Event($this->eventMutex, $command);
return $event;
}
@@ -130,6 +142,18 @@ class Schedule
})->implode(' ');
}
/**
* Determine if the server is allowed to run this event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @param \DateTimeInterface $time
* @return bool
*/
public function serverShouldRun(Event $event, DateTimeInterface $time)
{
return $this->schedulingMutex->create($event, $time);
}
/**
* Get all of the events on the schedule that are due.
*
@@ -150,4 +174,23 @@ class Schedule
{
return $this->events;
}
/**
* Specify the cache store that should be used to store mutexes.
*
* @param string $store
* @return $this
*/
public function useCache($store)
{
if ($this->eventMutex instanceof CacheEventMutex) {
$this->eventMutex->useStore($store);
}
if ($this->schedulingMutex instanceof CacheSchedulingMutex) {
$this->schedulingMutex->useStore($store);
}
return $this;
}
}

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Console\Scheduling;
use Illuminate\Support\Carbon;
use Illuminate\Console\Command;
class ScheduleRunCommand extends Command
@@ -27,6 +28,20 @@ class ScheduleRunCommand extends Command
*/
protected $schedule;
/**
* The 24 hour timestamp this scheduler command started running.
*
* @var \Illuminate\Support\Carbon;
*/
protected $startedAt;
/**
* Check if any events ran.
*
* @var bool
*/
protected $eventsRan = false;
/**
* Create a new command instance.
*
@@ -37,6 +52,8 @@ class ScheduleRunCommand extends Command
{
$this->schedule = $schedule;
$this->startedAt = Carbon::now();
parent::__construct();
}
@@ -47,22 +64,52 @@ class ScheduleRunCommand extends Command
*/
public function handle()
{
$eventsRan = false;
foreach ($this->schedule->dueEvents($this->laravel) as $event) {
if (! $event->filtersPass($this->laravel)) {
continue;
}
$this->line('<info>Running scheduled command:</info> '.$event->getSummaryForDisplay());
if ($event->onOneServer) {
$this->runSingleServerEvent($event);
} else {
$this->runEvent($event);
}
$event->run($this->laravel);
$eventsRan = true;
$this->eventsRan = true;
}
if (! $eventsRan) {
if (! $this->eventsRan) {
$this->info('No scheduled commands are ready to run.');
}
}
/**
* Run the given single server event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return void
*/
protected function runSingleServerEvent($event)
{
if ($this->schedule->serverShouldRun($event, $this->startedAt)) {
$this->runEvent($event);
} else {
$this->line('<info>Skipping command (has already run on another server):</info> '.$event->getSummaryForDisplay());
}
}
/**
* Run the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @return void
*/
protected function runEvent($event)
{
$this->line('<info>Running scheduled command:</info> '.$event->getSummaryForDisplay());
$event->run($this->laravel);
$this->eventsRan = true;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Illuminate\Console\Scheduling;
use DateTimeInterface;
interface SchedulingMutex
{
/**
* Attempt to obtain a scheduling mutex for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @param \DateTimeInterface $time
* @return bool
*/
public function create(Event $event, DateTimeInterface $time);
/**
* Determine if a scheduling mutex exists for the given event.
*
* @param \Illuminate\Console\Scheduling\Event $event
* @param \DateTimeInterface $time
* @return bool
*/
public function exists(Event $event, DateTimeInterface $time);
}

View File

@@ -14,10 +14,10 @@
}
],
"require": {
"php": ">=7.0",
"illuminate/contracts": "5.5.*",
"illuminate/support": "5.5.*",
"symfony/console": "~3.3"
"php": "^7.1.3",
"illuminate/contracts": "5.6.*",
"illuminate/support": "5.6.*",
"symfony/console": "~4.0"
},
"autoload": {
"psr-4": {
@@ -26,13 +26,13 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"suggest": {
"dragonmantank/cron-expression": "Required to use scheduling component (~2.0).",
"guzzlehttp/guzzle": "Required to use the ping methods on schedules (~6.0).",
"mtdowling/cron-expression": "Required to use scheduling component (~1.0).",
"symfony/process": "Required to use scheduling component (~3.3)."
"symfony/process": "Required to use scheduling component (~4.0)."
},
"config": {
"sort-packages": true

View File

@@ -152,6 +152,10 @@ class BoundMethod
$dependencies[] = $parameters[$parameter->name];
unset($parameters[$parameter->name]);
} elseif ($parameter->getClass() && array_key_exists($parameter->getClass()->name, $parameters)) {
$dependencies[] = $parameters[$parameter->getClass()->name];
unset($parameters[$parameter->getClass()->name]);
} elseif ($parameter->getClass()) {
$dependencies[] = $container->make($parameter->getClass()->name);
} elseif ($parameter->isDefaultValueAvailable()) {

View File

@@ -271,13 +271,28 @@ class Container implements ArrayAccess, ContainerContract
/**
* Bind a callback to resolve with Container::call.
*
* @param string $method
* @param array|string $method
* @param \Closure $callback
* @return void
*/
public function bindMethod($method, $callback)
{
$this->methodBindings[$method] = $callback;
$this->methodBindings[$this->parseBindMethod($method)] = $callback;
}
/**
* Get the method to be bound in class@method format.
*
* @param array|string $method
* @return string
*/
protected function parseBindMethod($method)
{
if (is_array($method)) {
return $method[0].'@'.$method[1];
}
return $method;
}
/**
@@ -1166,7 +1181,7 @@ class Container implements ArrayAccess, ContainerContract
* Set the shared instance of the container.
*
* @param \Illuminate\Contracts\Container\Container|null $container
* @return static
* @return \Illuminate\Contracts\Container\Container|static
*/
public static function setInstance(ContainerContract $container = null)
{

View File

@@ -14,8 +14,8 @@
}
],
"require": {
"php": ">=7.0",
"illuminate/contracts": "5.5.*",
"php": "^7.1.3",
"illuminate/contracts": "5.6.*",
"psr/container": "~1.0"
},
"autoload": {
@@ -25,7 +25,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"config": {

View File

@@ -7,7 +7,7 @@ interface Factory
/**
* Get a broadcaster implementation by name.
*
* @param string $name
* @param string|null $name
* @return void
*/
public function connection($name = null);

View File

@@ -2,14 +2,12 @@
namespace Illuminate\Contracts\Broadcasting;
use Illuminate\Broadcasting\Channel;
interface ShouldBroadcast
{
/**
* Get the channels the event should broadcast on.
*
* @return Channel|Channel[]
* @return \Illuminate\Broadcasting\Channel|\Illuminate\Broadcasting\Channel[]
*/
public function broadcastOn();
}

View File

@@ -21,6 +21,22 @@ interface Dispatcher
*/
public function dispatchNow($command, $handler = null);
/**
* Determine if the given command has a handler.
*
* @param mixed $command
* @return bool
*/
public function hasCommandHandler($command);
/**
* Retrieve the handler for a command.
*
* @param mixed $command
* @return bool|mixed
*/
public function getCommandHandler($command);
/**
* Set the pipes commands should be piped through before dispatching.
*
@@ -28,4 +44,12 @@ interface Dispatcher
* @return $this
*/
public function pipeThrough(array $pipes);
/**
* Map a command to a handler.
*
* @param array $map
* @return $this
*/
public function map(array $map);
}

View File

@@ -5,13 +5,14 @@ namespace Illuminate\Contracts\Console;
interface Application
{
/**
* Call a console application command.
* Run an Artisan console command by name.
*
* @param string $command
* @param array $parameters
* @param \Symfony\Component\Console\Output\OutputInterface|null $outputBuffer
* @return int
*/
public function call($command, array $parameters = []);
public function call($command, array $parameters = [], $outputBuffer = null);
/**
* Get the output from the last command.

View File

@@ -8,7 +8,7 @@ interface Kernel
* Handle an incoming console command.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param \Symfony\Component\Console\Output\OutputInterface|null $output
* @return int
*/
public function handle($input, $output = null);
@@ -18,9 +18,10 @@ interface Kernel
*
* @param string $command
* @param array $parameters
* @param \Symfony\Component\Console\Output\OutputInterface|null $outputBuffer
* @return int
*/
public function call($command, array $parameters = []);
public function call($command, array $parameters = [], $outputBuffer = null);
/**
* Queue an Artisan console command by name.

View File

@@ -14,9 +14,11 @@ interface Factory
* @param string $domain
* @param bool $secure
* @param bool $httpOnly
* @param bool $raw
* @param string|null $sameSite
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true);
public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null);
/**
* Create a cookie that lasts "forever" (five years).
@@ -27,9 +29,11 @@ interface Factory
* @param string $domain
* @param bool $secure
* @param bool $httpOnly
* @param bool $raw
* @param string|null $sameSite
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function forever($name, $value, $path = null, $domain = null, $secure = false, $httpOnly = true);
public function forever($name, $value, $path = null, $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null);
/**
* Expire the given cookie.

View File

@@ -20,6 +20,13 @@ class ModelIdentifier
*/
public $id;
/**
* The relationships loaded on the model.
*
* @var array
*/
public $relations;
/**
* The connection name of the model.
*
@@ -32,13 +39,15 @@ class ModelIdentifier
*
* @param string $class
* @param mixed $id
* @param array $relations
* @param mixed $connection
* @return void
*/
public function __construct($class, $id, $connection)
public function __construct($class, $id, array $relations, $connection)
{
$this->id = $id;
$this->class = $class;
$this->relations = $relations;
$this->connection = $connection;
}
}

View File

@@ -28,12 +28,19 @@ interface Application extends Container
public function environment();
/**
* Determine if we are running in the console.
* Determine if the application is running in the console.
*
* @return bool
*/
public function runningInConsole();
/**
* Determine if the application is running unit tests.
*
* @return bool
*/
public function runningUnitTests();
/**
* Determine if the application is currently down for maintenance.
*

View File

@@ -4,6 +4,14 @@ namespace Illuminate\Contracts\Hashing;
interface Hasher
{
/**
* Get information about the given hashed value.
*
* @param string $hashedValue
* @return array
*/
public function info($hashedValue);
/**
* Hash the given value.
*

View File

@@ -1,98 +0,0 @@
<?php
namespace Illuminate\Contracts\Logging;
interface Log
{
/**
* Log an alert message to the logs.
*
* @param string $message
* @param array $context
* @return void
*/
public function alert($message, array $context = []);
/**
* Log a critical message to the logs.
*
* @param string $message
* @param array $context
* @return void
*/
public function critical($message, array $context = []);
/**
* Log an error message to the logs.
*
* @param string $message
* @param array $context
* @return void
*/
public function error($message, array $context = []);
/**
* Log a warning message to the logs.
*
* @param string $message
* @param array $context
* @return void
*/
public function warning($message, array $context = []);
/**
* Log a notice to the logs.
*
* @param string $message
* @param array $context
* @return void
*/
public function notice($message, array $context = []);
/**
* Log an informational message to the logs.
*
* @param string $message
* @param array $context
* @return void
*/
public function info($message, array $context = []);
/**
* Log a debug message to the logs.
*
* @param string $message
* @param array $context
* @return void
*/
public function debug($message, array $context = []);
/**
* Log a message to the logs.
*
* @param string $level
* @param string $message
* @param array $context
* @return void
*/
public function log($level, $message, array $context = []);
/**
* Register a file log handler.
*
* @param string $path
* @param string $level
* @return void
*/
public function useFiles($path, $level = 'debug');
/**
* Register a daily file log handler.
*
* @param string $path
* @param int $days
* @param string $level
* @return void
*/
public function useDailyFiles($path, $days = 0, $level = 'debug');
}

View File

@@ -7,7 +7,7 @@ interface MailQueue
/**
* Queue a new e-mail message for sending.
*
* @param string|array|MailableContract $view
* @param string|array|\Illuminate\Contracts\Mail\Mailable $view
* @param string $queue
* @return mixed
*/
@@ -17,7 +17,7 @@ interface MailQueue
* Queue a new e-mail message for sending after (n) seconds.
*
* @param \DateTimeInterface|\DateInterval|int $delay
* @param string|array|MailableContract $view
* @param string|array|\Illuminate\Contracts\Mail\Mailable $view
* @param string $queue
* @return mixed
*/

View File

@@ -2,6 +2,8 @@
namespace Illuminate\Contracts\Mail;
use Illuminate\Contracts\Mail\Mailable as MailableContract;
interface Mailer
{
/**

View File

@@ -4,6 +4,20 @@ namespace Illuminate\Contracts\Queue;
interface Job
{
/**
* Get the job identifier.
*
* @return string
*/
public function getJobId();
/**
* Get the decoded body of the job.
*
* @return array
*/
public function payload();
/**
* Fire the job.
*

View File

@@ -18,6 +18,13 @@ interface QueueableCollection
*/
public function getQueueableIds();
/**
* Get the relationships of the entities being queued.
*
* @return array
*/
public function getQueueableRelations();
/**
* Get the connection of the entities being queued.
*

View File

@@ -11,6 +11,13 @@ interface QueueableEntity
*/
public function getQueueableId();
/**
* Get the relationships for the entity.
*
* @return array
*/
public function getQueueableRelations();
/**
* Get the connection of the entity.
*

View File

@@ -0,0 +1,35 @@
<?php
namespace Illuminate\Contracts\Redis;
use Closure;
interface Connection
{
/**
* Subscribe to a set of given channels for messages.
*
* @param array|string $channels
* @param \Closure $callback
* @return void
*/
public function subscribe($channels, Closure $callback);
/**
* Subscribe to a set of given channels with wildcards.
*
* @param array|string $channels
* @param \Closure $callback
* @return void
*/
public function psubscribe($channels, Closure $callback);
/**
* Run a command against the Redis database.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function command($method, array $parameters = []);
}

View File

@@ -58,11 +58,22 @@ interface ResponseFactory
*/
public function stream($callback, $status = 200, array $headers = []);
/**
* Return a new streamed response as a file download from the application.
*
* @param \Closure $callback
* @param string|null $name
* @param array $headers
* @param string|null $disposition
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
public function streamDownload($callback, $name = null, array $headers = [], $disposition = 'attachment');
/**
* Create a new file download response.
*
* @param \SplFileInfo|string $file
* @param string $name
* @param string|null $name
* @param array $headers
* @param string|null $disposition
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse

View File

@@ -9,5 +9,5 @@ interface ValidatesWhenResolved
*
* @return void
*/
public function validate();
public function validateResolved();
}

View File

@@ -14,7 +14,7 @@
}
],
"require": {
"php": ">=7.0",
"php": "^7.1.3",
"psr/container": "~1.0",
"psr/simple-cache": "~1.0"
},
@@ -25,7 +25,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"config": {

View File

@@ -54,13 +54,13 @@ class CookieJar implements JarContract
* @param int $minutes
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool|null $secure
* @param bool $httpOnly
* @param bool $raw
* @param string|null $sameSite
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null)
public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null)
{
list($path, $domain, $secure, $sameSite) = $this->getPathAndDomain($path, $domain, $secure, $sameSite);
@@ -76,13 +76,13 @@ class CookieJar implements JarContract
* @param string $value
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool|null $secure
* @param bool $httpOnly
* @param bool $raw
* @param string|null $sameSite
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function forever($name, $value, $path = null, $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null)
public function forever($name, $value, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null)
{
return $this->make($name, $value, 2628000, $path, $domain, $secure, $httpOnly, $raw, $sameSite);
}
@@ -154,15 +154,15 @@ class CookieJar implements JarContract
/**
* Get the path and domain, or the default values.
*
* @param string $path
* @param string $domain
* @param bool $secure
* @param string $sameSite
* @param string $path
* @param string $domain
* @param bool|null $secure
* @param string $sameSite
* @return array
*/
protected function getPathAndDomain($path, $domain, $secure = false, $sameSite = null)
protected function getPathAndDomain($path, $domain, $secure = null, $sameSite = null)
{
return [$path ?: $this->path, $domain ?: $this->domain, $secure ?: $this->secure, $sameSite ?: $this->sameSite];
return [$path ?: $this->path, $domain ?: $this->domain, is_bool($secure) ? $secure : $this->secure, $sameSite ?: $this->sameSite];
}
/**

View File

@@ -39,12 +39,12 @@ class EncryptCookies
/**
* Disable encryption for the given cookie name(s).
*
* @param string|array $cookieName
* @param string|array $name
* @return void
*/
public function disableFor($cookieName)
public function disableFor($name)
{
$this->except = array_merge($this->except, (array) $cookieName);
$this->except = array_merge($this->except, (array) $name);
}
/**
@@ -52,7 +52,7 @@ class EncryptCookies
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle($request, Closure $next)
{
@@ -67,13 +67,13 @@ class EncryptCookies
*/
protected function decrypt(Request $request)
{
foreach ($request->cookies as $key => $c) {
foreach ($request->cookies as $key => $cookie) {
if ($this->isDisabled($key)) {
continue;
}
try {
$request->cookies->set($key, $this->decryptCookie($c));
$request->cookies->set($key, $this->decryptCookie($cookie));
} catch (DecryptException $e) {
$request->cookies->set($key, null);
}
@@ -138,16 +138,16 @@ class EncryptCookies
/**
* Duplicate a cookie with a new value.
*
* @param \Symfony\Component\HttpFoundation\Cookie $c
* @param \Symfony\Component\HttpFoundation\Cookie $cookie
* @param mixed $value
* @return \Symfony\Component\HttpFoundation\Cookie
*/
protected function duplicate(Cookie $c, $value)
protected function duplicate(Cookie $cookie, $value)
{
return new Cookie(
$c->getName(), $value, $c->getExpiresTime(), $c->getPath(),
$c->getDomain(), $c->isSecure(), $c->isHttpOnly(), $c->isRaw(),
$c->getSameSite()
$cookie->getName(), $value, $cookie->getExpiresTime(),
$cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(),
$cookie->isHttpOnly(), $cookie->isRaw(), $cookie->getSameSite()
);
}

View File

@@ -14,11 +14,11 @@
}
],
"require": {
"php": ">=7.0",
"illuminate/contracts": "5.5.*",
"illuminate/support": "5.5.*",
"symfony/http-foundation": "~3.3",
"symfony/http-kernel": "~3.3"
"php": "^7.1.3",
"illuminate/contracts": "5.6.*",
"illuminate/support": "5.6.*",
"symfony/http-foundation": "~4.0",
"symfony/http-kernel": "~4.0"
},
"autoload": {
"psr-4": {
@@ -27,7 +27,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.5-dev"
"dev-master": "5.6-dev"
}
},
"config": {

View File

@@ -129,7 +129,7 @@ trait ManagesTransactions
/**
* Handle an exception from a transaction beginning.
*
* @param \Exception $e
* @param \Throwable $e
* @return void
*
* @throws \Exception

Some files were not shown because too many files have changed in this diff Show More