diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php
index 191eefedc891..93be53b2fdc9 100644
--- a/src/Illuminate/Database/DetectsLostConnections.php
+++ b/src/Illuminate/Database/DetectsLostConnections.php
@@ -52,6 +52,8 @@ protected function causedByLostConnection(Throwable $e)
'Temporary failure in name resolution',
'SSL: Broken pipe',
'SQLSTATE[08S01]: Communication link failure',
+ 'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host',
+ 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host',
]);
}
}
diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php
index ca84d06cdbc1..a8f2fb87842c 100755
--- a/src/Illuminate/Database/Eloquent/Builder.php
+++ b/src/Illuminate/Database/Eloquent/Builder.php
@@ -1608,6 +1608,16 @@ protected static function registerMixin($mixin, $replace)
}
}
+ /**
+ * Clone the Eloquent query builder.
+ *
+ * @return static
+ */
+ public function clone()
+ {
+ return clone $this;
+ }
+
/**
* Force a clone of the underlying query builder when cloning.
*
diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
index 459b14c73399..7d660905e393 100644
--- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
+++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
@@ -1322,6 +1322,16 @@ public function getAttributes()
return $this->attributes;
}
+ /**
+ * Get all of the current attributes on the model for an insert operation.
+ *
+ * @return array
+ */
+ protected function getAttributesForInsert()
+ {
+ return $this->getAttributes();
+ }
+
/**
* Set the array of model attributes. No checking is done.
*
diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php
index 62fcd058e258..512d59eae10e 100644
--- a/src/Illuminate/Database/Eloquent/Model.php
+++ b/src/Illuminate/Database/Eloquent/Model.php
@@ -3,7 +3,6 @@
namespace Illuminate\Database\Eloquent;
use ArrayAccess;
-use Exception;
use Illuminate\Contracts\Queue\QueueableCollection;
use Illuminate\Contracts\Queue\QueueableEntity;
use Illuminate\Contracts\Routing\UrlRoutable;
@@ -20,6 +19,7 @@
use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use JsonSerializable;
+use LogicException;
abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
@@ -1011,7 +1011,7 @@ protected function performInsert(Builder $query)
// If the model has an incrementing key, we can use the "insertGetId" method on
// the query builder, which will give us back the final inserted ID for this
// table from the database. Not all tables have to be incrementing though.
- $attributes = $this->getAttributes();
+ $attributes = $this->getAttributesForInsert();
if ($this->getIncrementing()) {
$this->insertAndSetId($query, $attributes);
@@ -1097,14 +1097,14 @@ public static function destroy($ids)
*
* @return bool|null
*
- * @throws \Exception
+ * @throws \LogicException
*/
public function delete()
{
$this->mergeAttributesFromClassCasts();
if (is_null($this->getKeyName())) {
- throw new Exception('No primary key defined on model.');
+ throw new LogicException('No primary key defined on model.');
}
// If the model doesn't exist, there is nothing to delete so we'll just return
diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php
index 093e41e78a43..35b4bb92e92a 100755
--- a/src/Illuminate/Database/Migrations/Migrator.php
+++ b/src/Illuminate/Database/Migrations/Migrator.php
@@ -13,6 +13,7 @@
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
+use ReflectionClass;
use Symfony\Component\Console\Output\OutputInterface;
class Migrator
@@ -185,9 +186,9 @@ protected function runUp($file, $batch, $pretend)
// First we will resolve a "real" instance of the migration class from this
// migration file name. Once we have the instances we can run the actual
// command such as "up" or "down", or we can just simulate the action.
- $migration = $this->resolve(
- $name = $this->getMigrationName($file)
- );
+ $migration = $this->resolvePath($file);
+
+ $name = $this->getMigrationName($file);
if ($pretend) {
return $this->pretendToRun($migration, 'up');
@@ -348,9 +349,9 @@ protected function runDown($file, $migration, $pretend)
// First we will get the file name of the migration so we can resolve out an
// instance of the migration. Once we get an instance we can either run a
// pretend execution of the migration or we can run the real migration.
- $instance = $this->resolve(
- $name = $this->getMigrationName($file)
- );
+ $instance = $this->resolvePath($file);
+
+ $name = $this->getMigrationName($file);
$this->note("Rolling back: {$name}");
@@ -413,6 +414,12 @@ protected function pretendToRun($migration, $method)
foreach ($this->getQueries($migration, $method) as $query) {
$name = get_class($migration);
+ $reflectionClass = new ReflectionClass($migration);
+
+ if ($reflectionClass->isAnonymous()) {
+ $name = $this->getMigrationName($reflectionClass->getFileName());
+ }
+
$this->note("{$name}: {$query['query']}");
}
}
@@ -448,11 +455,39 @@ protected function getQueries($migration, $method)
*/
public function resolve($file)
{
- $class = Str::studly(implode('_', array_slice(explode('_', $file), 4)));
+ $class = $this->getMigrationClass($file);
return new $class;
}
+ /**
+ * Resolve a migration instance from a migration path.
+ *
+ * @param string $path
+ * @return object
+ */
+ protected function resolvePath(string $path)
+ {
+ $class = $this->getMigrationClass($this->getMigrationName($path));
+
+ if (class_exists($class)) {
+ return new $class;
+ }
+
+ return $this->files->getRequire($path);
+ }
+
+ /**
+ * Generate a migration class name based on the migration file name.
+ *
+ * @param string $migrationName
+ * @return string
+ */
+ protected function getMigrationClass(string $migrationName): string
+ {
+ return Str::studly(implode('_', array_slice(explode('_', $migrationName), 4)));
+ }
+
/**
* Get all of the migration files in a given path.
*
diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php
index 710d0a3a568c..befc8a8cfa33 100755
--- a/src/Illuminate/Database/Query/Builder.php
+++ b/src/Illuminate/Database/Query/Builder.php
@@ -244,7 +244,7 @@ public function select($columns = ['*'])
/**
* Add a subselect expression to the query.
*
- * @param \Closure|\Illuminate\Database\Query\Builder|string $query
+ * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query
* @param string $as
* @return $this
*
diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php
index 622659995410..e2b968ab6bd4 100755
--- a/src/Illuminate/Database/Schema/Blueprint.php
+++ b/src/Illuminate/Database/Schema/Blueprint.php
@@ -677,6 +677,17 @@ public function string($column, $length = null)
return $this->addColumn('string', $column, compact('length'));
}
+ /**
+ * Create a new tiny text column on the table.
+ *
+ * @param string $column
+ * @return \Illuminate\Database\Schema\ColumnDefinition
+ */
+ public function tinyText($column)
+ {
+ return $this->addColumn('tinyText', $column);
+ }
+
/**
* Create a new text column on the table.
*
diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php
index dd53b9fe9bed..37df3337b040 100755
--- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php
+++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php
@@ -490,6 +490,17 @@ protected function typeString(Fluent $column)
return "varchar({$column->length})";
}
+ /**
+ * Create the column definition for a tiny text type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeTinyText(Fluent $column)
+ {
+ return 'tinytext';
+ }
+
/**
* Create the column definition for a text type.
*
diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php
index adaf21f90e4c..fb7005b09df3 100755
--- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php
+++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php
@@ -472,6 +472,17 @@ protected function typeString(Fluent $column)
return "varchar({$column->length})";
}
+ /**
+ * Create the column definition for a tiny text type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeTinyText(Fluent $column)
+ {
+ return 'varchar(255)';
+ }
+
/**
* Create the column definition for a text type.
*
diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php
index 556d749e23b2..b7e406f578ef 100755
--- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php
+++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php
@@ -432,6 +432,17 @@ protected function typeString(Fluent $column)
return 'varchar';
}
+ /**
+ * Create the column definition for a tiny text type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeTinyText(Fluent $column)
+ {
+ return 'text';
+ }
+
/**
* Create the column definition for a text type.
*
diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php
index c3fc442e2368..b147628ec2f3 100755
--- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php
+++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php
@@ -417,6 +417,17 @@ protected function typeString(Fluent $column)
return "nvarchar({$column->length})";
}
+ /**
+ * Create the column definition for a tiny text type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeTinyText(Fluent $column)
+ {
+ return 'nvarchar(255)';
+ }
+
/**
* Create the column definition for a text type.
*
diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php
index f7d1716456d0..b9a9eecbbca9 100755
--- a/src/Illuminate/Foundation/Application.php
+++ b/src/Illuminate/Foundation/Application.php
@@ -33,7 +33,7 @@ class Application extends Container implements ApplicationContract, CachesConfig
*
* @var string
*/
- const VERSION = '8.36.2';
+ const VERSION = '8.37.0';
/**
* The base path for the Laravel installation.
diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithViews.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithViews.php
index 574009a68f95..faa6c64a367e 100644
--- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithViews.php
+++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithViews.php
@@ -2,6 +2,7 @@
namespace Illuminate\Foundation\Testing\Concerns;
+use Closure;
use Illuminate\Support\Facades\View as ViewFacade;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Str;
@@ -58,6 +59,10 @@ protected function component(string $componentClass, array $data = [])
$view = $component->resolveView();
+ if ($view instanceof Closure) {
+ $view = $view($data);
+ }
+
return $view instanceof View
? new TestView($view->with($component->data()))
: new TestView(view($view, $component->data()));
diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php
index 4db3e1fa98a0..6fc069d390b7 100644
--- a/src/Illuminate/Http/Client/Factory.php
+++ b/src/Illuminate/Http/Client/Factory.php
@@ -34,6 +34,8 @@
* @method \Illuminate\Http\Client\PendingRequest withoutVerifying()
* @method \Illuminate\Http\Client\PendingRequest dump()
* @method \Illuminate\Http\Client\PendingRequest dd()
+ * @method \Illuminate\Http\Client\PendingRequest async()
+ * @method \Illuminate\Http\Client\Pool pool()
* @method \Illuminate\Http\Client\Response delete(string $url, array $data = [])
* @method \Illuminate\Http\Client\Response get(string $url, array $query = [])
* @method \Illuminate\Http\Client\Response head(string $url, array $query = [])
diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php
index 5801bc0b70f0..f0790c5a54a3 100644
--- a/src/Illuminate/Http/Client/PendingRequest.php
+++ b/src/Illuminate/Http/Client/PendingRequest.php
@@ -5,10 +5,13 @@
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ConnectException;
+use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\HandlerStack;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
+use Psr\Http\Message\MessageInterface;
use Symfony\Component\VarDumper\VarDumper;
class PendingRequest
@@ -22,6 +25,13 @@ class PendingRequest
*/
protected $factory;
+ /**
+ * The Guzzle client instance.
+ *
+ * @var \GuzzleHttp\Client
+ */
+ protected $client;
+
/**
* The base URL for the request.
*
@@ -106,6 +116,20 @@ class PendingRequest
*/
protected $middleware;
+ /**
+ * Whether the requests should be asynchronous.
+ *
+ * @var bool
+ */
+ protected $async = false;
+
+ /**
+ * The pending request promise.
+ *
+ * @var \GuzzleHttp\Promise\PromiseInterface
+ */
+ protected $promise;
+
/**
* Create a new HTTP Client instance.
*
@@ -571,6 +595,27 @@ public function delete($url, $data = [])
]);
}
+ /**
+ * Send a pool of asynchronous requests concurrently.
+ *
+ * @param callable $callback
+ * @return array
+ */
+ public function pool(callable $callback)
+ {
+ $results = [];
+
+ $requests = tap(new Pool($this->factory), $callback)->getRequests();
+
+ foreach ($requests as $key => $item) {
+ $results[$key] = $item instanceof static ? $item->getPromise()->wait() : $item->wait();
+ }
+
+ ksort($results);
+
+ return $results;
+ }
+
/**
* Send the request to the given URL.
*
@@ -601,18 +646,14 @@ public function send(string $method, string $url, array $options = [])
[$this->pendingBody, $this->pendingFiles] = [null, []];
+ if ($this->async) {
+ return $this->makePromise($method, $url, $options);
+ }
+
return retry($this->tries ?? 1, function () use ($method, $url, $options) {
try {
- $laravelData = $this->parseRequestData($method, $url, $options);
-
- return tap(new Response($this->buildClient()->request($method, $url, $this->mergeOptions([
- 'laravel_data' => $laravelData,
- 'on_stats' => function ($transferStats) {
- $this->transferStats = $transferStats;
- },
- ], $options))), function ($response) {
- $response->cookies = $this->cookies;
- $response->transferStats = $this->transferStats;
+ return tap(new Response($this->sendRequest($method, $url, $options)), function ($response) {
+ $this->populateResponse($response);
if ($this->tries > 1 && ! $response->successful()) {
$response->throw();
@@ -637,6 +678,49 @@ protected function parseMultipartBodyFormat(array $data)
})->values()->all();
}
+ /**
+ * Send an asynchronous request to the given URL.
+ *
+ * @param string $method
+ * @param string $url
+ * @param array $options
+ * @return \GuzzleHttp\Promise\PromiseInterface
+ */
+ protected function makePromise(string $method, string $url, array $options = [])
+ {
+ return $this->promise = $this->sendRequest($method, $url, $options)
+ ->then(function (MessageInterface $message) {
+ return $this->populateResponse(new Response($message));
+ })
+ ->otherwise(function (TransferException $e) {
+ return $e instanceof RequestException ? $this->populateResponse(new Response($e->getResponse())) : $e;
+ });
+ }
+
+ /**
+ * Send a request either synchronously or asynchronously.
+ *
+ * @param string $method
+ * @param string $url
+ * @param array $options
+ * @return \Psr\Http\Message\MessageInterface|\GuzzleHttp\Promise\PromiseInterface
+ *
+ * @throws \Exception
+ */
+ protected function sendRequest(string $method, string $url, array $options = [])
+ {
+ $clientMethod = $this->async ? 'requestAsync' : 'request';
+
+ $laravelData = $this->parseRequestData($method, $url, $options);
+
+ return $this->buildClient()->$clientMethod($method, $url, $this->mergeOptions([
+ 'laravel_data' => $laravelData,
+ 'on_stats' => function ($transferStats) {
+ $this->transferStats = $transferStats;
+ },
+ ], $options));
+ }
+
/**
* Get the request data as an array so that we can attach it to the request for convenient assertions.
*
@@ -664,6 +748,21 @@ protected function parseRequestData($method, $url, array $options)
return $laravelData;
}
+ /**
+ * Populate the given response with additional data.
+ *
+ * @param \Illuminate\Http\Client\Response $response
+ * @return \Illuminate\Http\Client\Response
+ */
+ protected function populateResponse(Response $response)
+ {
+ $response->cookies = $this->cookies;
+
+ $response->transferStats = $this->transferStats;
+
+ return $response;
+ }
+
/**
* Build the Guzzle client.
*
@@ -671,7 +770,7 @@ protected function parseRequestData($method, $url, array $options)
*/
public function buildClient()
{
- return new Client([
+ return $this->client = $this->client ?: new Client([
'handler' => $this->buildHandlerStack(),
'cookies' => true,
]);
@@ -826,4 +925,40 @@ public function stub($callback)
return $this;
}
+
+ /**
+ * Toggle asynchronicity in requests.
+ *
+ * @param bool $async
+ * @return $this
+ */
+ public function async(bool $async = true)
+ {
+ $this->async = $async;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the pending request promise.
+ *
+ * @return \GuzzleHttp\Promise\PromiseInterface|null
+ */
+ public function getPromise()
+ {
+ return $this->promise;
+ }
+
+ /**
+ * Set the client instance.
+ *
+ * @param \GuzzleHttp\Client $client
+ * @return $this
+ */
+ public function setClient(Client $client)
+ {
+ $this->client = $client;
+
+ return $this;
+ }
}
diff --git a/src/Illuminate/Http/Client/Pool.php b/src/Illuminate/Http/Client/Pool.php
new file mode 100644
index 000000000000..15002b28e92e
--- /dev/null
+++ b/src/Illuminate/Http/Client/Pool.php
@@ -0,0 +1,83 @@
+factory = $factory ?: new Factory();
+
+ $this->client = $this->factory->buildClient();
+ }
+
+ /**
+ * Add a request to the pool with a key.
+ *
+ * @param string $key
+ * @return \Illuminate\Http\Client\PendingRequest
+ */
+ public function as(string $key)
+ {
+ return $this->pool[$key] = $this->asyncRequest();
+ }
+
+ /**
+ * Retrieve a new async pending request.
+ *
+ * @return \Illuminate\Http\Client\PendingRequest
+ */
+ protected function asyncRequest()
+ {
+ return $this->factory->setClient($this->client)->async();
+ }
+
+ /**
+ * Retrieve the requests in the pool.
+ *
+ * @return array
+ */
+ public function getRequests()
+ {
+ return $this->pool;
+ }
+
+ /**
+ * Add a request to the pool with a numeric index.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @return \Illuminate\Http\Client\PendingRequest
+ */
+ public function __call($method, $parameters)
+ {
+ return $this->pool[] = $this->asyncRequest()->$method(...$parameters);
+ }
+}
diff --git a/src/Illuminate/Pagination/LengthAwarePaginator.php b/src/Illuminate/Pagination/LengthAwarePaginator.php
index 3e5adad0a316..d1c6cc711fb5 100644
--- a/src/Illuminate/Pagination/LengthAwarePaginator.php
+++ b/src/Illuminate/Pagination/LengthAwarePaginator.php
@@ -99,7 +99,7 @@ public function render($view = null, $data = [])
*
* @return \Illuminate\Support\Collection
*/
- protected function linkCollection()
+ public function linkCollection()
{
return collect($this->elements())->flatMap(function ($item) {
if (! is_array($item)) {
diff --git a/src/Illuminate/Queue/Console/RetryCommand.php b/src/Illuminate/Queue/Console/RetryCommand.php
index 2f651b60d098..212883fecdcb 100644
--- a/src/Illuminate/Queue/Console/RetryCommand.php
+++ b/src/Illuminate/Queue/Console/RetryCommand.php
@@ -18,6 +18,7 @@ class RetryCommand extends Command
*/
protected $signature = 'queue:retry
{id?* : The ID of the failed job or "all" to retry all jobs}
+ {--queue= : Retry all of the failed jobs for the specified queue}
{--range=* : Range of job IDs (numeric) to be retried}';
/**
@@ -62,6 +63,10 @@ protected function getJobIds()
return Arr::pluck($this->laravel['queue.failer']->all(), 'id');
}
+ if ($queue = $this->option('queue')) {
+ return $this->getJobIdsByQueue($queue);
+ }
+
if ($ranges = (array) $this->option('range')) {
$ids = array_merge($ids, $this->getJobIdsByRanges($ranges));
}
@@ -69,6 +74,26 @@ protected function getJobIds()
return array_values(array_filter(array_unique($ids)));
}
+ /**
+ * Get the job IDs by queue, if applicable.
+ *
+ * @param string $queue
+ * @return array
+ */
+ protected function getJobIdsByQueue($queue)
+ {
+ $ids = collect($this->laravel['queue.failer']->all())
+ ->where('queue', $queue)
+ ->pluck('id')
+ ->toArray();
+
+ if (count($ids) === 0) {
+ $this->error("Unable to find failed jobs for queue [{$queue}].");
+ }
+
+ return $ids;
+ }
+
/**
* Get the job IDs ranges, if applicable.
*
diff --git a/src/Illuminate/Session/Store.php b/src/Illuminate/Session/Store.php
index ff2a3b966a20..151e8b6330b5 100755
--- a/src/Illuminate/Session/Store.php
+++ b/src/Illuminate/Session/Store.php
@@ -193,6 +193,17 @@ public function exists($key)
});
}
+ /**
+ * Determine if the given key is missing from the session data.
+ *
+ * @param string|array $key
+ * @return bool
+ */
+ public function missing($key)
+ {
+ return ! $this->exists($key);
+ }
+
/**
* Checks if a key is present and not null.
*
diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php
index 426d574789c5..51763b6caef0 100644
--- a/src/Illuminate/Support/Facades/Http.php
+++ b/src/Illuminate/Support/Facades/Http.php
@@ -32,6 +32,8 @@
* @method static \Illuminate\Http\Client\PendingRequest withoutVerifying()
* @method static \Illuminate\Http\Client\PendingRequest dump()
* @method static \Illuminate\Http\Client\PendingRequest dd()
+ * @method static \Illuminate\Http\Client\PendingRequest async()
+ * @method static \Illuminate\Http\Client\Pool pool()
* @method static \Illuminate\Http\Client\Response delete(string $url, array $data = [])
* @method static \Illuminate\Http\Client\Response get(string $url, array $query = [])
* @method static \Illuminate\Http\Client\Response head(string $url, array $query = [])
diff --git a/src/Illuminate/View/Engines/EngineResolver.php b/src/Illuminate/View/Engines/EngineResolver.php
index d0edb7367df6..6a5b80026342 100755
--- a/src/Illuminate/View/Engines/EngineResolver.php
+++ b/src/Illuminate/View/Engines/EngineResolver.php
@@ -57,4 +57,15 @@ public function resolve($engine)
throw new InvalidArgumentException("Engine [{$engine}] not found.");
}
+
+ /**
+ * Remove a resolved engine.
+ *
+ * @param string $engine
+ * @return void
+ */
+ public function forget($engine)
+ {
+ unset($this->resolved[$engine]);
+ }
}
diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php
index 0809f8e0fe98..682545ff9b98 100755
--- a/tests/Database/DatabaseEloquentBuilderTest.php
+++ b/tests/Database/DatabaseEloquentBuilderTest.php
@@ -1497,6 +1497,18 @@ public function testWithCastsMethod()
$builder->withCasts(['foo' => 'bar']);
}
+ public function testClone()
+ {
+ $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class));
+ $builder = new Builder($query);
+ $builder->select('*')->from('users');
+ $clone = $builder->clone()->where('email', 'foo');
+
+ $this->assertNotSame($builder, $clone);
+ $this->assertSame('select * from "users"', $builder->toSql());
+ $this->assertSame('select * from "users" where "email" = ?', $clone->toSql());
+ }
+
protected function mockConnectionForModel($model, $database)
{
$grammarClass = 'Illuminate\Database\Query\Grammars\\'.$database.'Grammar';
diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php
index 94303415e8f4..5247df35c3ba 100755
--- a/tests/Database/DatabaseSchemaBlueprintTest.php
+++ b/tests/Database/DatabaseSchemaBlueprintTest.php
@@ -291,4 +291,62 @@ public function testGenerateRelationshipColumnWithUuidModel()
'alter table `posts` add `eloquent_model_uuid_stub_id` char(36) not null',
], $blueprint->toSql($connection, new MySqlGrammar));
}
+
+ public function testTinyTextColumn()
+ {
+ $base = new Blueprint('posts', function ($table) {
+ $table->tinyText('note');
+ });
+
+ $connection = m::mock(Connection::class);
+
+ $blueprint = clone $base;
+ $this->assertEquals([
+ 'alter table `posts` add `note` tinytext not null',
+ ], $blueprint->toSql($connection, new MySqlGrammar));
+
+ $blueprint = clone $base;
+ $this->assertEquals([
+ 'alter table "posts" add column "note" text not null',
+ ], $blueprint->toSql($connection, new SQLiteGrammar));
+
+ $blueprint = clone $base;
+ $this->assertEquals([
+ 'alter table "posts" add column "note" varchar(255) not null',
+ ], $blueprint->toSql($connection, new PostgresGrammar));
+
+ $blueprint = clone $base;
+ $this->assertEquals([
+ 'alter table "posts" add "note" nvarchar(255) not null',
+ ], $blueprint->toSql($connection, new SqlServerGrammar));
+ }
+
+ public function testTinyTextNullableColumn()
+ {
+ $base = new Blueprint('posts', function ($table) {
+ $table->tinyText('note')->nullable();
+ });
+
+ $connection = m::mock(Connection::class);
+
+ $blueprint = clone $base;
+ $this->assertEquals([
+ 'alter table `posts` add `note` tinytext null',
+ ], $blueprint->toSql($connection, new MySqlGrammar));
+
+ $blueprint = clone $base;
+ $this->assertEquals([
+ 'alter table "posts" add column "note" text',
+ ], $blueprint->toSql($connection, new SQLiteGrammar));
+
+ $blueprint = clone $base;
+ $this->assertEquals([
+ 'alter table "posts" add column "note" varchar(255) null',
+ ], $blueprint->toSql($connection, new PostgresGrammar));
+
+ $blueprint = clone $base;
+ $this->assertEquals([
+ 'alter table "posts" add "note" nvarchar(255) null',
+ ], $blueprint->toSql($connection, new SqlServerGrammar));
+ }
}
diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php
index 12d7a1806c21..84709fa3ba66 100644
--- a/tests/Http/HttpClientTest.php
+++ b/tests/Http/HttpClientTest.php
@@ -2,8 +2,11 @@
namespace Illuminate\Tests\Http;
+use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response as Psr7Response;
use Illuminate\Http\Client\Factory;
+use Illuminate\Http\Client\PendingRequest;
+use Illuminate\Http\Client\Pool;
use Illuminate\Http\Client\Request;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\Response;
@@ -833,4 +836,70 @@ public function testResponseSequenceIsMacroable()
$this->assertSame('yes!', $this->factory->fakeSequence()->customMethod());
}
+
+ public function testRequestsCanBeAsync()
+ {
+ $request = new PendingRequest($this->factory);
+
+ $promise = $request->async()->get('http://foo.com');
+
+ $this->assertInstanceOf(PromiseInterface::class, $promise);
+
+ $this->assertSame($promise, $request->getPromise());
+ }
+
+ public function testClientCanBeSet()
+ {
+ $client = $this->factory->buildClient();
+
+ $request = new PendingRequest($this->factory);
+
+ $this->assertNotSame($client, $request->buildClient());
+
+ $request->setClient($client);
+
+ $this->assertSame($client, $request->buildClient());
+ }
+
+ public function testMultipleRequestsAreSentInThePool()
+ {
+ $this->factory->fake([
+ '200.com' => $this->factory::response('', 200),
+ '400.com' => $this->factory::response('', 400),
+ '500.com' => $this->factory::response('', 500),
+ ]);
+
+ $responses = $this->factory->pool(function (Pool $pool) {
+ return [
+ $pool->get('200.com'),
+ $pool->get('400.com'),
+ $pool->get('500.com'),
+ ];
+ });
+
+ $this->assertSame(200, $responses[0]->status());
+ $this->assertSame(400, $responses[1]->status());
+ $this->assertSame(500, $responses[2]->status());
+ }
+
+ public function testMultipleRequestsAreSentInThePoolWithKeys()
+ {
+ $this->factory->fake([
+ '200.com' => $this->factory::response('', 200),
+ '400.com' => $this->factory::response('', 400),
+ '500.com' => $this->factory::response('', 500),
+ ]);
+
+ $responses = $this->factory->pool(function (Pool $pool) {
+ return [
+ $pool->as('test200')->get('200.com'),
+ $pool->as('test400')->get('400.com'),
+ $pool->as('test500')->get('500.com'),
+ ];
+ });
+
+ $this->assertSame(200, $responses['test200']->status());
+ $this->assertSame(400, $responses['test400']->status());
+ $this->assertSame(500, $responses['test500']->status());
+ }
}
diff --git a/tests/Integration/Migration/MigratorTest.php b/tests/Integration/Migration/MigratorTest.php
index 50ee6f3cbd6b..08dd9c97862f 100644
--- a/tests/Integration/Migration/MigratorTest.php
+++ b/tests/Integration/Migration/MigratorTest.php
@@ -2,11 +2,29 @@
namespace Illuminate\Tests\Integration\Migration;
+use Illuminate\Support\Facades\DB;
+use Mockery;
+use Mockery\Mock;
use Orchestra\Testbench\TestCase;
-use PDOException;
+use Symfony\Component\Console\Output\OutputInterface;
class MigratorTest extends TestCase
{
+ /**
+ * @var Mock
+ */
+ private $output;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->output = Mockery::mock(OutputInterface::class);
+ $this->subject = $this->app->make('migrator');
+ $this->subject->setOutput($this->output);
+ $this->subject->getRepository()->createRepository();
+ }
+
protected function getEnvironmentSetUp($app)
{
$app['config']->set('app.debug', 'true');
@@ -19,25 +37,55 @@ protected function getEnvironmentSetUp($app)
]);
}
- public function testDontDisplayOutputWhenOutputObjectIsNotAvailable()
+ public function testMigrate()
+ {
+ $this->expectOutput('Migrating: 2014_10_12_000000_create_people_table');
+ $this->expectOutput(Mockery::pattern('#Migrated: 2014_10_12_000000_create_people_table (.*)#'));
+ $this->expectOutput('Migrating: 2015_10_04_000000_modify_people_table');
+ $this->expectOutput(Mockery::pattern('#Migrated: 2015_10_04_000000_modify_people_table (.*)#'));
+ $this->expectOutput('Migrating: 2016_10_04_000000_modify_people_table');
+ $this->expectOutput(Mockery::pattern('#Migrated: 2016_10_04_000000_modify_people_table (.*)#'));
+
+ $this->subject->run([__DIR__.'/fixtures']);
+
+ self::assertTrue(DB::getSchemaBuilder()->hasTable('people'));
+ self::assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'first_name'));
+ self::assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'last_name'));
+ }
+
+ public function testRollback()
{
- $migrator = $this->app->make('migrator');
+ $this->getConnection()->statement('CREATE TABLE people(id INT, first_name VARCHAR, last_name VARCHAR);');
+ $this->subject->getRepository()->log('2014_10_12_000000_create_people_table', 1);
+ $this->subject->getRepository()->log('2015_10_04_000000_modify_people_table', 1);
+ $this->subject->getRepository()->log('2016_10_04_000000_modify_people_table', 1);
- $migrator->getRepository()->createRepository();
+ $this->expectOutput('Rolling back: 2016_10_04_000000_modify_people_table');
+ $this->expectOutput(Mockery::pattern('#Rolled back: 2016_10_04_000000_modify_people_table (.*)#'));
+ $this->expectOutput('Rolling back: 2015_10_04_000000_modify_people_table');
+ $this->expectOutput(Mockery::pattern('#Rolled back: 2015_10_04_000000_modify_people_table (.*)#'));
+ $this->expectOutput('Rolling back: 2014_10_12_000000_create_people_table');
+ $this->expectOutput(Mockery::pattern('#Rolled back: 2014_10_12_000000_create_people_table (.*)#'));
- $migrator->run([__DIR__.'/fixtures']);
+ $this->subject->rollback([__DIR__.'/fixtures']);
- $this->assertTrue($this->tableExists('people'));
+ self::assertFalse(DB::getSchemaBuilder()->hasTable('people'));
}
- private function tableExists($table): bool
+ public function testPretendMigrate()
{
- try {
- $this->app->make('db')->select("SELECT COUNT(*) FROM $table");
- } catch (PDOException $e) {
- return false;
- }
+ $this->expectOutput('CreatePeopleTable: create table "people" ("id" integer not null primary key autoincrement, "name" varchar not null, "email" varchar not null, "password" varchar not null, "remember_token" varchar, "created_at" datetime, "updated_at" datetime)');
+ $this->expectOutput('CreatePeopleTable: create unique index "people_email_unique" on "people" ("email")');
+ $this->expectOutput('2015_10_04_000000_modify_people_table: alter table "people" add column "first_name" varchar');
+ $this->expectOutput('2016_10_04_000000_modify_people_table: alter table "people" add column "last_name" varchar');
+
+ $this->subject->run([__DIR__.'/fixtures'], ['pretend' => true]);
- return true;
+ self::assertFalse(DB::getSchemaBuilder()->hasTable('people'));
+ }
+
+ private function expectOutput($argument): void
+ {
+ $this->output->shouldReceive('writeln')->once()->with($argument);
}
}
diff --git a/tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php b/tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php
new file mode 100644
index 000000000000..88ac706cd12a
--- /dev/null
+++ b/tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php
@@ -0,0 +1,31 @@
+string('first_name')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('people', function (Blueprint $table) {
+ $table->dropColumn('first_name');
+ });
+ }
+};
diff --git a/tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php b/tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php
new file mode 100644
index 000000000000..6492b6d7f55a
--- /dev/null
+++ b/tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php
@@ -0,0 +1,31 @@
+string('last_name')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('people', function (Blueprint $table) {
+ $table->dropColumn('last_name');
+ });
+ }
+};
diff --git a/tests/Session/SessionStoreTest.php b/tests/Session/SessionStoreTest.php
index 5871cc657a85..188bdce4d89f 100644
--- a/tests/Session/SessionStoreTest.php
+++ b/tests/Session/SessionStoreTest.php
@@ -452,6 +452,22 @@ public function testKeyExists()
$this->assertFalse($session->exists(['hulk.two']));
}
+ public function testKeyMissing()
+ {
+ $session = $this->getSession();
+ $session->put('foo', 'bar');
+ $this->assertFalse($session->missing('foo'));
+ $session->put('baz', null);
+ $session->put('hulk', ['one' => true]);
+ $this->assertFalse($session->has('baz'));
+ $this->assertFalse($session->missing('baz'));
+ $this->assertTrue($session->missing('bogus'));
+ $this->assertFalse($session->missing(['foo', 'baz']));
+ $this->assertTrue($session->missing(['foo', 'baz', 'bogus']));
+ $this->assertFalse($session->missing(['hulk.one']));
+ $this->assertTrue($session->missing(['hulk.two']));
+ }
+
public function testRememberMethodCallsPutAndReturnsDefault()
{
$session = $this->getSession();