Skip to content

Commit

Permalink
Merge branch 'features/database_url' of https://github.com/mathieutu/…
Browse files Browse the repository at this point in the history
…framework into mathieutu-features/database_url
  • Loading branch information
taylorotwell committed Apr 30, 2019
2 parents 66d028a + 1b568d0 commit 00f1776
Show file tree
Hide file tree
Showing 4 changed files with 599 additions and 12 deletions.
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
{
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.
*
* @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

0 comments on commit 00f1776

Please sign in to comment.