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 5 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
2 changes: 1 addition & 1 deletion src/Illuminate/Database/DatabaseManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ protected function configuration($name)
throw new InvalidArgumentException("Database [{$name}] not configured.");
}

return $config;
return $this->app->make(UrlParser::class)->parseDatabaseConfigWithUrl($config);
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
152 changes: 152 additions & 0 deletions src/Illuminate/Database/UrlParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?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;

class UrlParser
{
private static $driverAliases = [
driesvints marked this conversation as resolved.
Show resolved Hide resolved
'mssql' => 'sqlsrv',
'mysql2' => 'mysql', // Amazon RDS, for some weird reason
'postgres' => 'pgsql',
'postgresql' => 'pgsql',
'sqlite3' => 'sqlite',
];

/**
* @var array
*/
private $parsedUrl;
mathieutu marked this conversation as resolved.
Show resolved Hide resolved

public static function getDriverAliases(): array
driesvints marked this conversation as resolved.
Show resolved Hide resolved
{
return self::$driverAliases;
}

public static function addDriverAlias(string $alias, string $driver)
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
{
self::$driverAliases[$alias] = $driver;
}

/**
* @param array|string $config
*
driesvints marked this conversation as resolved.
Show resolved Hide resolved
* @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()
);
}

private function parseUrl(string $url): array
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
{
// 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".');
driesvints marked this conversation as resolved.
Show resolved Hide resolved
}

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

private function parseStringsToNativeTypes($value)
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
{
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;
}

private function getMainAttributes(): array
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
{
return array_filter([
'driver' => $this->getDriver(),
'database' => $this->getNormalizedPath(),
'host' => $this->getInUrl('host'),
'port' => $this->getInUrl('port'),
'username' => $this->getInUrl('user'),
'password' => $this->getInUrl('pass'),
], function ($value) {
return $value !== null;
});
}

private function getDriver(): ?string
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
{
$alias = $this->getInUrl('scheme');

if (! $alias) {
return null;
}

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

private function getInUrl(string $key): ?string
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
{
return $this->parsedUrl[$key] ?? null;
}

private function getNormalizedPath(): ?string
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
{
$path = $this->getInUrl('path');

if (! $path) {
return null;
}

return substr($path, 1);
}

private function getOtherOptions(): array
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
{
$queryString = $this->getInUrl('query');

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

$query = [];

parse_str($queryString, $query);

return $this->parseStringsToNativeTypes($query);
}
}
57 changes: 46 additions & 11 deletions tests/Database/DatabaseConnectionFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

class DatabaseConnectionFactoryTest extends TestCase
{
/** @var DB */
mathieutu marked this conversation as resolved.
Show resolved Hide resolved
protected $db;

protected function setUp(): void
Expand All @@ -24,6 +25,10 @@ protected function setUp(): void
'database' => ':memory:',
]);

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

$this->db->addConnection([
'driver' => 'sqlite',
'read' => [
Expand All @@ -44,15 +49,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 +101,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 +142,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