This library started with the idea of providing less than minimal DB functionality with still some intuitive automated stuff.
In order to handle data models and its properties in a clean code manner (readable and maintainable) I developed this library on top of PDO. Please enjoy (use at your own risk XD).
* You can find all the methods from PDO and their documentation at php.net/manual/class.pdo.
Table of Contents:
This is a code example of a model that extends MykrORM
<?php
class Session extends \Breier\MykrORM
{
protected $token;
protected $email;
protected $startTime;
protected function getDSN(): string
{
return 'pgsql:host=localhost;port=5432;dbname=test;user=test;password=1234';
}
public function __construct()
{
parent::__construct();
$this->dbProperties = [
'token' => 'CHAR(64) PRIMARY KEY',
'email' => 'VARCHAR(64) NOT NULL',
'start_time' => 'TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP',
];
}
public function setToken(string $value = ''): string
{
if (strlen($value) === 64) {
return $this->token = $value;
}
$secret = 'my-app-hash-secret-123';
$this->token = hash('sha256', "{$secret}-{$this->email}-" . microtime(true));
return $this->token;
}
public function setEmail(string $value): string
{
$value = filter_var($value, FILTER_VALIDATE_EMAIL);
return $this->email = $value;
}
public function setStartTime($value): DateTime
{
if ($value instanceof DateTime) {
return $this->startTime = $value;
}
return $this->startTime = new \DateTime($value);
}
}
The first time you try to "Create" an entry of that model, the table will be created in the database.
If you update the model adding more columns they will be added on creation as well.
While Create, Update and Delete deal with the current instance,
"Read" (find
) returns an ExtendedArray of instances of the Model.
Properties listed in $this->dbProperties should be declared with protected visibility. MykrORM will provide automatic getters and setters for them.
This is the abstract class that implements the ORM and it's also the base for every model you wish to create.
- The model class that extends from it has to implement
getDSN()
that returns a DSN string to PDO connection; - It sets these default PDO options:
PDO::ATTR_ERRMODE
->PDO::ERRMODE_EXCEPTION
PDO::ATTR_DEFAULT_FETCH_MODE
->PDO::FETCH_NAMED
- It sets the DB table name based on the model class name that extended from it.
But you can override it by setting$this->dbTableName
beforeparent::__construct()
; - If you use parameters for the extended
__construct
you also need to set$this->dbConstructorArgs
as an array containing the parameters' values in it; - It provides automatic getters via
__get
for related DB properties; - It maps setters via
__set
forfetchObject()
PDO mode.
(you have to declare public setters likesetPropertyName($value)
);
Returns the DSN for PDO connection (has to be implemented by the extending class).
Code Example
class Test extends MykrORM
{
protected function getDSN(): string
{
return 'pgsql:host=localhost;port=5432;dbname=test;user=test;password=1234';
// return 'sqlite:messaging.sqlite3'; // local option ;)
}
}
Get DB Properties (ensure it is an ExtendedArray instance)
Code Example
class Test extends MykrORM
{
public function test(): void
{
$this->getDBProperties()->keys()->join('/'); // 'token/email/start_time'
}
}
Gets the stored PDO object with a valid connection.
Code Example
class Test extends MykrORM
{
public function test(): void
{
$this->getConnection()->query('SELECT * FROM test');
}
}
Provides automatic getters for DB properties.
Code Example
class Test extends MykrORM
{
protected $test = 1234;
protected $other = "no-getter";
public __construct()
{
$this->dbProperties = [
'test' => 'INT NOT NULL PRIMARY KEY',
];
}
}
print((new Test())->test); // 1234
print((new Test())->other); // throws DBException property is not DB property!
Maps setters automatically for fetchObject()
PDO mode.
Code Example
class Test extends MykrORM
{
protected $testName = 'test';
public __construct()
{
$this->dbProperties = [
'test_name' => 'CHAR(4) NOT NULL PRIMARY KEY',
];
}
public setTestName(string $value): string
{
$this->testName = $value;
}
public test(): void
{
$preparedStatement = $this->getConnection()->prepare("SELECT * FROM {$this->dbTableName}");
$preparedStatement->execute();
$likeThis = $preparedStatement->fetchObject(static::class);
if (!empty($likeThis)) {
print($likeThis->testName); // works because __set mapped 'test_name' to 'setTestName'
}
}
}
[static]
Converts Camel-Case to Snake-Case (from Property to DB).
Code Example
class Test extends MykrORM
{
public function test(): void
{
print(self::camelToSnake('anotherTestName')); // another_test_name
}
}
[static]
Converts Snake-Case to Camel-Case (from DB to Property).
Code Example
class Test extends MykrORM
{
public function test(): void
{
printt(self::snakeToCamel('test_name')); // TestName
}
}
This methods are embedded in MykrORM but I rather list them here for better organization.
Insert new row to the model table with current properties.
Code Example
$test = new Test();
$test->testName = 'what';
$test->create();
Get all rows of the model table that matches $criteria (returns an ExtendedArray with model instances).
Code Example
$testModel = new Test();
$test = $testModel->find(['test_name' => 'what']); // ExtendedArray
$test->first()->element(); // Test Model instance (or null)
$test->next()->element(); // Test Model instance of second row (or null)
Update a row of the model table that matches $criteria.
* It internally uses find
to get the original object.
Code Example
$test = (new Test())->find(['test_name' => 'what']);
if ($test->count()) {
$testModel = $test->first()->element();
$testModel->testName = 'soap';
$testModel->update(['test_name' => 'what']);
}
Delete a row of the model table with current properties.
Code Example
$test = (new Test())->find(['test_name' => 'soap']);
if ($test->count()) {
$testModel = $test->first()->element();
$testModel->delete();
}
Get database available properties in an associative array manner.
Code Example
$test = new Test();
$test->testName = 'soap';
print($test->getProperties()); // {"test_name":"soap"}
Binds parameters to indexed (?) placeholders in the prepared statement.
* Specially useful to detect booleans and nulls and bind them properly.
Code Example
class Test extends MykrORM
{
protected $testName = 'test';
public __construct()
{
$this->dbProperties = [
'test_name' => 'CHAR(4) NOT NULL PRIMARY KEY',
];
}
public setTestName(string $value): string
{
$this->testName = $value;
}
public testUpdate(): void
{
$query = "UPDATE {$this->dbTableName} SET test_name = ? WHERE test_name = ?";
$parameters = new ExtendedArray(['test_name' => 'newValue', 0 => 'oldValue']);
$preparedStatement = $this->getConnection()->prepare($query);
$this->bindIndexedParams($preparedStatement, $parameters);
$preparedStatement->execute();
}
}
Make sure that any key in the criteria matches "dbProperties".
Code Example
class Test extends MykrORM
{
public function test(): void
{
$this->validateCriteria([]); // true
$this->validateCriteria(['test_name' => null]); // true
$this->validateCriteria(['test_name' => 'soap']); // true
$this->validateCriteria(['test_non_existent' => 'soap']); // Throws DBException
}
}
Get DB property set as 'PRIMARY KEY' (or the first index if not found).
Code Example
class Test extends MykrORM
{
public __construct()
{
$this->dbProperties = [
'test_name' => 'CHAR(4) NOT NULL PRIMARY KEY',
];
}
public function test(): void
{
print($this->findPrimaryKey()); // {"as_db_field":"test_name","asProperty":"testName"}
}
}
A little bit of magic that actually limits the DB structure to a very simple one.
Checks if a table exists for the current extending model.
If it doesn't, it creates it.
If it does, it further checks for alterations to alter it.
Code Example
class Test extends MykrORM
{
...
public function test(): void
{
$this->dbTableName = 'different_test';
$this->createTableIfNotExists(); // creates new table with same DB properties
...
}
}