Festi ObjectDB

Festi ObjectDB is a database abstraction layer for the Festi Framework, designed to provide a unified interface for working with different database engines. It simplifies database interactions by supporting multiple adapters such as PDO, Cassandra, HandlerSocket, and WPDB. This allows developers to switch between databases without modifying application logic.

Installation

To install Festi ObjectDB using Composer, add the following to your composer.json file:

{
    "repositories": [
        { "type": "composer", "url": "https://packages.festi.io/" }
    ],

    "require": {
       "festi-team/festi-framework-database": "dev-develop"
    }
}

Usage

PDO

$db = new PDO(
    $GLOBALS['config']['db']['dsn'],
    $GLOBALS['config']['db']['user'],
    $GLOBALS['config']['db']['pass']
);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); 
$db->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_EMPTY_STRING); 
$db->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); 

$res = $db->query('SET NAMES utf8');

if (!$res) {
    throw new Exception('Database connection error');
}

$db = DataAccessObject::factory($db);

Wordpress

$db = DataAccessObject::factory($GLOBALS['wpdb']);

Cassandra

$cluster = Cassandra::cluster()->
                    withContactPoints('IP')->
                    withCredentials("UserName", "Password")->
                    build();

$session  = $cluster->connect('KeySpace');  

$db = DataAccessObject::factory($session);

$sql = "SELECT * FROM sensors_data";
$result = $db->getCol($sql);

ORM

/**
 * Class CandidateValuesObject
 * @table(name:"candidates")
 */
class CandidateValuesObject extends ValuesObject
{
    /**
     * @primaryKey
     * @column
     * @return int|null
     * @throws SystemException
     */
    public function getID(): ?int
    {
        return $this->get('id');
    } // end getID

    /**
     * @param string $name
     * @column(name:"lastname")
     * @null
     */
    public function setLastName(string $name): void
    {
        $this->set('last_name', $name);
    } // end setLastName

    /**
     * @oneToMany(foreignKey:"id_candidate", id:"id", entity:"CandidateEducationValuesObject")
     * @param CandidateEducationValuesObject[] $items
     */
    public function setEducations(array $items): void
    {
        $this->_educations = $items;
    } // end setEducations
}

ORM PHP8

#[Table("scraper_actions")]
class ActionValuesObject
{
    private ?int $id;
    private string $caption;
    private array $items = array();

    #[Column]
    #[PrimaryKey]
    public function setID(?int $id): void
    {
        $this->id = $id;
    }

    public function getID(): ?int
    {
        return $this->id;
    }

    #[Column("title")]
    #[Nullable]
    public function setCaption(string $caption): void
    {
        $this->caption = $caption;
    }

    public function getCaption(): string
    {
        return $this->caption;
    }

    #[OneToMany("id", "id_action", ActionItem::class)]
    public function setItems(array $items): void
    {
        $this->items = $items;
    }

    public function getItems(): array
    {
        return $this->items;
    }
}

#[Table("scraper_action_items")]
class ActionItem
{
    private ?int $id;
    private string $caption;
    private int $actionId;

    #[Column]
    #[PrimaryKey]
    public function setID(?int $id): void
    {
        $this->id = $id;
    }

    public function getID(): ?int
    {
        return $this->id;
    }

    #[Column("title")]
    #[Nullable]
    public function setCaption(string $caption): void
    {
        $this->caption = $caption;
    }

    public function getCaption(): string
    {
        return $this->caption;
    }

    public function setActionID(int $id): void
    {
        $this->actionId = $id;
    }

    public function getActionID(): int
    {
        return $this->actionId;
    }
}
$action = new ActionValuesObject();

$action->setID(null);
$action->setCaption("Test");

$manager = EntityManager::createInstance($connection, new AttributesEntityParser());

$manager->sync($action);

Driver

A driver can function as an adapter for database-specific APIs, such as those required for creating tables, executing queries, and utilizing syntax specific to the database being used.

Create Table

This code is intended as a proof-of-concept and should not be used in production environments. It can be used for unit testing purposes, and provides a safe way to create a table in a database with support for different database engines.

$table = new Table();
$table->setName('test_table');

$primaryKey = new Column('id');
$primaryKey->setPrimaryKey(true);

$captionColumn = new Column('caption');
$captionColumn->setNullable(true);

$table->addColumn($primaryKey);
$table->addColumn($captionColumn);

$query = $driver->createTableQuery($table);

DataAccessObject

In most cases, you will need to create a DataAccessObject that follows the Repository Pattern. This helps encapsulate database access logic in a structured way.

Example implementation:

class ScraperObject extends DataAccessObject
{
    const ACTIONS_TABLE_NAME = "scraper_actions";

    public function changeActions(array $values, array $search): int
    {
        return $this->update(static::ACTIONS_TABLE_NAME, $values, $search);
    }

    public function removeActions(array $search): void
    {
        $this->delete(static::ACTIONS_TABLE_NAME, $search);
    }

    public function getByID(int $id): ?ActionValuesObject
    {
        $search = ['id' => $id];
        $sql = "SELECT * FROM ".static::ACTIONS_TABLE_NAME;

        $data = $this->select($sql, $search, null, static::FETCH_ROW);
        if (!$data) {
            return null;
        }
        return new ActionValuesObject($data);
    }
}

You can create an instance of the ScraperObject using:

$scraperObject = DataAccessObject::create(ScraperObject::class);
Alternatively, if a database connection or path is needed:
$scraperObject = DataAccessObject::getInstance(ScraperObject::class, $connection, $path);

Handling Errors

Festi ObjectDB provides exception handling through DatabaseException:

try {
    $db->query("INVALID SQL QUERY");
} catch (DatabaseException $e) {
    echo "Database error: " . $e->getMessage();
}

Creating Conditions

Festi ObjectDB uses associative arrays to build database query conditions dynamically. This approach is faster, more readable, and easier to maintain than traditional ORM-based approaches.

Why Use This Approach?

  • Performance – Directly maps to SQL queries without unnecessary abstraction layers.
  • Flexibility – Enables complex condition structures without requiring ORM-based query builders.
  • Faster Development – Reduces boilerplate code, making query writing faster.
  • Easier Maintenance – Keeps conditions structured and readable, reducing debugging time.
  • Reduced Time to Market – Allows for quick database queries without needing to learn ORM syntax.

Basic Condition Example

$search = array(
    'area' => $area
);

This generates a simple condition: WHERE area = :area.

Using Comparison Operators

$search = array(
    'id&>' => $lastID,
    'id_author&<' => $this->core->user->getID()
);

This translates to:

WHERE id > :lastID AND id_author < :userID

Using IN Conditions

$search = array(
    'id&IN' => $authorIDs
);

This translates to:

WHERE id IN (:authorIDs)

Using IS NULL/IS NOT NULL Conditions

$search = array(
    'is_initiator&IS' => null
);
````

This translates to:

`WHERE is_initiator IS NULL`


```php
$search = array(
    'is_initiator&IS NOT' => null
);
````

This translates to:

`WHERE is_initiator IS NOT NULL`

Also, you can use `'NULL'` as value to check for NULL:
```php
$search = array(
    'is_initiator&IS' => 'NULL',
    // OR
    'is_initiator&IS NOT' => 'NULL',
);

``php

Using OR Conditions

$search = array(
    ContentsObject::CONTENTS_TABLE_NAME.'.status' => ContentValuesObject::STATUS_ACTIVE,
    'sql_or' => array(
        array('types.id_type&IN' => $roleIDs),
        array('types.id_type&IS' => null)
    )
);

This translates to:

WHERE contents.status = :status AND (types.id_type IN (:roleIDs) OR types.id_type IS NULL)

FAQ

  1. Change class name postfix:
define('DAO_CLASS_POSTFIX', 'DAO');
  1. Change the default path to objects files
define('DAO_CLASSES_PATH', 'PATH_TO_OBJECTS_DIR');