-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
430 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?php | ||
namespace Anorm; | ||
|
||
class Anorm | ||
{ | ||
|
||
private static $connections = array(); | ||
|
||
/** | ||
* @var callable Function that returns a SQL type definition for creating a column | ||
* function($fieldName, $sampleData) { | ||
* return TableMaker::columnDefinition($fieldName, $sampleData); | ||
* }; | ||
*/ | ||
public static $columnFn = '\Anorm\TableMaker::columnDefinition'; | ||
|
||
/** | ||
* Creates a new Anorm connection named $name connected to $dsn. | ||
* @param string $name Name of this connection for later use. | ||
* @param string $dsn PDO DSN string to establish the connection. | ||
* @return Anorm | ||
* @see connect | ||
*/ | ||
public static function connect($name, $dsn, $user, $password) | ||
{ | ||
if (!\array_key_exists($name, self::$connections)) { | ||
self::$connections[$name] = new Anorm($dsn, $user, $password); | ||
} | ||
return self::$connections[$name]; | ||
} | ||
|
||
/** | ||
* Returns the Anorm connection of the given $name. | ||
* Note that the connection must have been previously opened | ||
* with a call to connect. | ||
* @param string $name Name of the connection to use. | ||
* @return Anorm | ||
* @see connect | ||
*/ | ||
public static function use($name) | ||
{ | ||
if (!\array_key_exists($name, self::$connections)) { | ||
throw new \Exception("Anorm: Connection '$name' doesn't exist. Call Anorm::connection first."); | ||
} | ||
return self::$connections[$name]; | ||
} | ||
|
||
/** @var PDO The connection */ | ||
public $pdo; | ||
|
||
private function __construct($dsn, $user, $password) | ||
{ | ||
$this->pdo = new \PDO($dsn, $user, $password); | ||
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
namespace Anorm; | ||
|
||
class TableMaker | ||
{ | ||
|
||
public static function fix(\Exception $exception, DataMapper $mapper, $model = null) | ||
{ | ||
// TODO We could create this from an Anorm factory / container | ||
$maker = new TableMaker($exception, $mapper, $model); | ||
return $maker->_fix(); | ||
} | ||
|
||
/** @var \PDOException The exception that requires the database schema to be fixed. */ | ||
public $exception; | ||
|
||
/** @var DataMapper The DataMapper */ | ||
private $mapper; | ||
|
||
/** @var Model An optional model instance */ | ||
private $model; | ||
|
||
public function __construct(\Exception $exception, DataMapper $mapper, $model) | ||
{ | ||
$this->exception = $exception; | ||
$this->mapper = $mapper; | ||
$this->model = $model; | ||
} | ||
|
||
private function _fix() | ||
{ | ||
switch ($this->exception->getCode()) { | ||
case '42S02': // table not found | ||
$this->createTable(); | ||
break; | ||
case '42S22': // column not found | ||
$this->createColumn(); | ||
break; | ||
} | ||
} | ||
|
||
private function createTable() | ||
{ | ||
// Regex the message to get the name of the table | ||
$matches = array(); | ||
if (!\preg_match("/'([^\.']*)\.([^\.']*)'/", $this->exception->getMessage(), $matches)) { | ||
throw new \Exception('Anorm: Could not parse PDOException', 0, $this->exception); | ||
} | ||
$tableName = $matches[2]; | ||
// Create the table with an auto increment id as primary key. | ||
// Review: Should we also try and create all the columns we can now, | ||
// or wait until possibly later when we might have better data | ||
// to hint the type? | ||
// Current design choice is to wait until later even if it means | ||
// a highly iterative, multiple exception approach on the common | ||
// first write case. | ||
$sql = "CREATE TABLE $tableName( | ||
id INT(11) AUTO_INCREMENT PRIMARY KEY | ||
)"; | ||
$this->mapper->pdo->query($sql); | ||
} | ||
|
||
private function createColumn() | ||
{ | ||
// Regex the message to get the name of the table | ||
$matches = array(); | ||
if (!\preg_match("/column '([^\.']*)'/", $this->exception->getMessage(), $matches)) { | ||
throw new \Exception('Anorm: Could not parse PDOException', 0, $this->exception); | ||
} | ||
$columnName = $matches[1]; | ||
// Add the column. | ||
// TODO Have a go at figuring out the type if the model is available. | ||
$sampleData = null; | ||
if ($this->model) { | ||
// See if we can reverse map the | ||
$invertMap = array_flip($this->mapper->map); | ||
$property = $invertMap[$columnName]; | ||
$sampleData = $this->model->$property; | ||
} | ||
$columnFn = Anorm::$columnFn; // Redundant, but can't do this Anorm::$columnFn(...) | ||
$columnDefinition = $columnFn($columnName, $sampleData); | ||
$sql = "ALTER TABLE " . $this->mapper->table . " ADD $columnName $columnDefinition"; | ||
$this->mapper->pdo->query($sql); | ||
} | ||
|
||
public static function columnDefinition($columnName, $sampleData) | ||
{ | ||
if ($sampleData) { | ||
if (\is_numeric($sampleData)) { | ||
if (\is_integer($sampleData)) { | ||
return "INT(11) NULL"; | ||
} | ||
if (\is_float($sampleData)) { | ||
return "DOUBLE NULL"; | ||
} | ||
} | ||
if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $sampleData) === 1) { | ||
return "DATETIME NULL"; | ||
} | ||
if (strlen($sampleData) > 256) { | ||
return "TEXT"; | ||
} | ||
if (strlen($sampleData) > 128) { | ||
return "VARCHAR(256)"; | ||
} | ||
} | ||
return 'VARCHAR(128)'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<?php | ||
|
||
require_once(__DIR__ . '/../../vendor/autoload.php'); | ||
|
||
use PHPUnit\Framework\TestCase; | ||
|
||
use Anorm\Anorm; | ||
|
||
class AnormTest extends TestCase | ||
{ | ||
public function testConnctionAndUse_OK() | ||
{ | ||
$anorm1 = Anorm::connect('testname', 'mysql:host=localhost;dbname=anorm_test', 'travis', ''); | ||
$this->assertInstanceOf('Anorm\Anorm', $anorm1); | ||
|
||
$anorm2 = Anorm::use('testname'); | ||
$this->assertInstanceOf('Anorm\Anorm', $anorm2); | ||
$this->assertEquals($anorm1, $anorm2); | ||
} | ||
|
||
/** | ||
* @expectedException \Exception | ||
* @expectedExceptionMessage Anorm: Connection 'bogusname' doesn't exist. Call Anorm::connection first. | ||
*/ | ||
public function testUse_NotConnected_Fails() | ||
{ | ||
$result = Anorm::use('bogusname'); | ||
} | ||
|
||
/** | ||
* @expectedException \PDOException | ||
* @expectedExceptionMessage SQLSTATE[HY000] [1045] Access denied for user 'bogus'@'localhost' (using password: NO) | ||
*/ | ||
public function testConnction_Bogus_Fails() | ||
{ | ||
$result = Anorm::connect('bogusname', 'mysql:host=localhost;dbname=bogus', 'bogus', ''); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
<?php | ||
|
||
require_once(__DIR__ . '/../../vendor/autoload.php'); | ||
|
||
use PHPUnit\Framework\TestCase; | ||
|
||
use Anorm\DataMapper; | ||
use Anorm\Model; | ||
|
||
class NotYetModel extends Model { | ||
public function __construct(\PDO $pdo) | ||
{ | ||
parent::__construct($pdo, DataMapper::createByClass($pdo, $this)); | ||
$this->_mapper->mode = DataMapper::MODE_DYNAMIC; | ||
} | ||
|
||
public function countRows() | ||
{ | ||
$result = $this->_mapper->query('SELECT * FROM `not_yet`'); | ||
return $result->rowCount(); | ||
} | ||
|
||
public $id; | ||
public $name; | ||
public $dtc; | ||
} | ||
|
||
class DataMapperDynamicTest extends TestCase | ||
{ | ||
/** @var \PDO */ | ||
private $pdo; | ||
|
||
public function __construct() | ||
{ | ||
parent::__construct(); | ||
$this->pdo = new \PDO('mysql:host=localhost;dbname=anorm_test', 'travis', ''); | ||
} | ||
|
||
public function setUp() | ||
{ | ||
$this->pdo->query('DROP TABLE IF EXISTS `not_yet`'); | ||
} | ||
|
||
public function testFindOne_OK() | ||
{ | ||
/** @var NotYetModel */ | ||
$model = DataMapper::find('NotYetModel', $this->pdo) | ||
->where("`name`='Name 1'") | ||
->one(); | ||
$this->assertTrue(true); // Just testing that we haven't yet thrown. | ||
} | ||
|
||
public function testCrud_OK() | ||
{ | ||
$model0 = new NotYetModel($this->pdo); | ||
$this->assertEquals($this->pdo, $model0->_mapper->pdo); | ||
// Count current rows | ||
$n0 = $model0->countRows(); | ||
$this->assertEquals(0, $n0); | ||
|
||
// Create | ||
$model0->name = 'bob'; | ||
$model0->dtc = '2018-11-25 00:00:00'; | ||
$this->assertNull($model0->id); | ||
$model0->write(); | ||
$this->assertNotNull($model0->id); | ||
|
||
// Count current rows (n+1) | ||
$n1 = $model0->countRows(); | ||
$this->assertEquals($n0 + 1, $n1); | ||
|
||
// Read (data present) | ||
$model1 = new NotYetModel($this->pdo); | ||
$model1->read($model0->id); | ||
$this->assertEquals($model0->name, $model1->name); | ||
$this->assertEquals($model0->dtc, $model1->dtc); | ||
|
||
// Update | ||
$model1->name = 'fred'; | ||
$model1->write(); | ||
|
||
// Read (data changed) | ||
$model2 = new NotYetModel($this->pdo); | ||
$model2->read($model1->id); | ||
$this->assertEquals($model1->name, $model2->name); | ||
|
||
// Delete | ||
$model0->_mapper->delete($model0->id); | ||
|
||
// Count current rows (n) | ||
$n2 = $model0->countRows(); | ||
$this->assertEquals($n0, $n2); | ||
|
||
} | ||
|
||
|
||
|
||
|
||
} |
Oops, something went wrong.