Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.8] Handle database urls for database connections. #28308

Merged
merged 7 commits into from
Apr 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Illuminate/Database/DatabaseManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ protected function configuration($name)
throw new InvalidArgumentException("Database [{$name}] not configured.");
}

return $config;
$urlParser = new UrlParser;

return $urlParser->parseDatabaseConfigWithUrl($config);
}

/**
Expand Down
212 changes: 212 additions & 0 deletions src/Illuminate/Database/UrlParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
<?php

namespace Illuminate\Database;

use function array_map;
use function parse_str;
use function parse_url;
use function array_merge;
use function preg_replace;
use Illuminate\Support\Arr;
use InvalidArgumentException;

class UrlParser
{
/**
* The drivers aliases map.
*
* @var array
*/
protected static $driverAliases = [
'mssql' => 'sqlsrv',
'mysql2' => 'mysql', // Amazon RDS, for some weird reason
'postgres' => 'pgsql',
'postgresql' => 'pgsql',
'sqlite3' => 'sqlite',
];

/**
* The different components of parsed url.
*
* @var array
*/
protected $parsedUrl;

/**
* Get all of the current drivers aliases.
*
* @return array
*/
public static function getDriverAliases(): array
driesvints marked this conversation as resolved.
Show resolved Hide resolved
{
return static::$driverAliases;
}

/**
* Add the driver alias to the driver aliases array.
*
* @param string $alias
* @param string $driver
* @return void
*/
public static function addDriverAlias($alias, $driver)
{
static::$driverAliases[$alias] = $driver;
}

/**
* Transform the url string or config array with url key to a parsed classic config array.
*
driesvints marked this conversation as resolved.
Show resolved Hide resolved
* @param array|string $config
* @return array
*/
public function parseDatabaseConfigWithUrl($config): array
{
if (is_string($config)) {
$config = ['url' => $config];
}

$url = $config['url'] ?? null;
$config = Arr::except($config, 'url');

if (! $url) {
return $config;
}

$this->parsedUrl = $this->parseUrl($url);

return array_merge(
$config,
$this->getMainAttributes(),
$this->getOtherOptions()
);
}

/**
* Decode the string url, to an array of all of its components.
*
* @param string $url
* @return array
*/
protected function parseUrl($url): array
{
// sqlite3?:///... => sqlite3?://null/... or else the URL will be invalid
$url = preg_replace('#^(sqlite3?):///#', '$1://null/', $url);

$parsedUrl = parse_url($url);

if ($parsedUrl === false) {
throw new InvalidArgumentException('Malformed parameter "url".');
}

return $this->parseStringsToNativeTypes(array_map('rawurldecode', $parsedUrl));
}

/**
* Convert string casted values to there native types.
* Ex: 'false' => false, '42' => 42, 'foo' => 'foo'
*
* @param string $url
* @return mixed
*/
protected function parseStringsToNativeTypes($value)
{
if (is_array($value)) {
return array_map([$this, 'parseStringsToNativeTypes'], $value);
}

if (! is_string($value)) {
return $value;
}

$parsedValue = json_decode($value, true);

if (json_last_error() === JSON_ERROR_NONE) {
return $parsedValue;
}

return $value;
}

/**
* Return the main attributes of the database connection config from url.
*
* @return array
*/
protected function getMainAttributes(): array
{
return array_filter([
'driver' => $this->getDriver(),
'database' => $this->getDatabase(),
'host' => $this->getInUrl('host'),
'port' => $this->getInUrl('port'),
'username' => $this->getInUrl('user'),
'password' => $this->getInUrl('pass'),
], function ($value) {
return $value !== null;
});
}

/**
* Find connection driver from url.
*
* @return string|null
*/
protected function getDriver()
{
$alias = $this->getInUrl('scheme');

if (! $alias) {
return null;
}

return static::$driverAliases[$alias] ?? $alias;
}

/**
* Get a component of the parsed url.
*
* @param string $key
* @return string|null
*/
protected function getInUrl($key)
{
return $this->parsedUrl[$key] ?? null;
}

/**
* Find connection database from url.
*
* @return string|null
*/
protected function getDatabase()
{
$path = $this->getInUrl('path');

if (! $path) {
return null;
}

return substr($path, 1);
}

/**
* Return all the options added to the url with query params.
*
* @return array
*/
protected function getOtherOptions(): array
{
$queryString = $this->getInUrl('query');

if (! $queryString) {
return [];
}

$query = [];

parse_str($queryString, $query);

return $this->parseStringsToNativeTypes($query);
}
}
56 changes: 45 additions & 11 deletions tests/Database/DatabaseConnectionFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ protected function setUp(): void
'database' => ':memory:',
]);

$this->db->addConnection([
'url' => 'sqlite:///:memory:',
], 'url');

$this->db->addConnection([
'driver' => 'sqlite',
'read' => [
Expand All @@ -44,15 +48,47 @@ protected function tearDown(): void

public function testConnectionCanBeCreated()
{
$this->assertInstanceOf(PDO::class, $this->db->connection()->getPdo());
$this->assertInstanceOf(PDO::class, $this->db->connection()->getReadPdo());
$this->assertInstanceOf(PDO::class, $this->db->connection('read_write')->getPdo());
$this->assertInstanceOf(PDO::class, $this->db->connection('read_write')->getReadPdo());
$this->assertInstanceOf(PDO::class, $this->db->getConnection()->getPdo());
$this->assertInstanceOf(PDO::class, $this->db->getConnection()->getReadPdo());
$this->assertInstanceOf(PDO::class, $this->db->getConnection('read_write')->getPdo());
$this->assertInstanceOf(PDO::class, $this->db->getConnection('read_write')->getReadPdo());
$this->assertInstanceOf(PDO::class, $this->db->getConnection('url')->getPdo());
$this->assertInstanceOf(PDO::class, $this->db->getConnection('url')->getReadPdo());
}

public function testConnectionFromUrlHasProperConfig()
{
$this->db->addConnection([
'url' => 'mysql://root:pass@db/local?strict=true',
'unix_socket' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => false,
'engine' => null,
], 'url-config');

$this->assertEquals([
'name' => 'url-config',
'driver' => 'mysql',
'database' => 'local',
'host' => 'db',
'username' => 'root',
'password' => 'pass',
'unix_socket' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
], $this->db->getConnection('url-config')->getConfig());
}

public function testSingleConnectionNotCreatedUntilNeeded()
{
$connection = $this->db->connection();
$connection = $this->db->getConnection();
$pdo = new ReflectionProperty(get_class($connection), 'pdo');
$pdo->setAccessible(true);
$readPdo = new ReflectionProperty(get_class($connection), 'readPdo');
Expand All @@ -64,7 +100,7 @@ public function testSingleConnectionNotCreatedUntilNeeded()

public function testReadWriteConnectionsNotCreatedUntilNeeded()
{
$connection = $this->db->connection('read_write');
$connection = $this->db->getConnection('read_write');
$pdo = new ReflectionProperty(get_class($connection), 'pdo');
$pdo->setAccessible(true);
$readPdo = new ReflectionProperty(get_class($connection), 'readPdo');
Expand Down Expand Up @@ -105,13 +141,11 @@ public function testCustomConnectorsCanBeResolvedViaContainer()
public function testSqliteForeignKeyConstraints()
{
$this->db->addConnection([
'driver' => 'sqlite',
'database' => ':memory:',
'foreign_key_constraints' => true,
'url' => 'sqlite:///:memory:?foreign_key_constraints=true',
], 'constraints_set');

$this->assertEquals(0, $this->db->connection()->select('PRAGMA foreign_keys')[0]->foreign_keys);
$this->assertEquals(0, $this->db->getConnection()->select('PRAGMA foreign_keys')[0]->foreign_keys);

$this->assertEquals(1, $this->db->connection('constraints_set')->select('PRAGMA foreign_keys')[0]->foreign_keys);
$this->assertEquals(1, $this->db->getConnection('constraints_set')->select('PRAGMA foreign_keys')[0]->foreign_keys);
}
}
Loading