breier/mykrorm

Provides less than minimal DB functionality with still some intuitive automated stuff

2.1.1 2020-05-04 09:47 UTC

This package is auto-updated.

Last update: 2024-04-04 18:18:56 UTC


README

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:

Model Example

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);
  }
}

Further Information

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.

Methods in MykrORM

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 before parent::__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 for fetchObject() PDO mode.
    (you have to declare public setters like setPropertyName($value));

abstract protected function getDSN(): string

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 ;)
  }
}

protected function getDBProperties(): ExtendedArray

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'
  }
}

protected function getConnection(): PDO

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');
  }
}

public function __get(string $propertyName)

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!

public function __set(string $name, $value): void

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'
    }
  }
}

final protected static function camelToSnake(string $string): string

[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
  }
}

final protected static function snakeToCamel(string $string): string

[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
  }
}

CRUD Methods

This methods are embedded in MykrORM but I rather list them here for better organization.

public function create(): void

Insert new row to the model table with current properties.

Code Example
$test = new Test();
$test->testName = 'what';
$test->create();

public function find($criteria): ExtendedArray

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)

public function update($criteria): void

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']);
}

public function delete(): void

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();
}

protected function getProperties(): ExtendedArray

Get database available properties in an associative array manner.

Code Example
$test = new Test();
$test->testName = 'soap';
print($test->getProperties()); // {"test_name":"soap"}

protected function bindIndexedParams(PDOStatement $statement, ExtendedArray $parameters): void

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();
  }
}

final protected function validateCriteria($criteria): bool

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
  }
}

protected function findPrimaryKey(): ExtendedArray

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"}
  }
}

Table Management

A little bit of magic that actually limits the DB structure to a very simple one.

protected function createTableIfNotExists(): void

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
    ...
  }
}