4

I'm learning about OOD and good practices in OOP and find myself struggling with some key concepts. As a practice I'm rewriting my custom PDO database abstraction layer which used to be a single file class with >2000 lines of code.

I learned one should use inheritance if classes are in a "is an" relationship and composition if they have a "has a" relationship. Composition can be implemented as this, given that I would avoid php's traits (example from here):

<?php

class Head {
}

class Human {
    private $head;
    public function __construct(Head $head) {
       $this->head = $head;
    }
}

$bob = new Human(new Head);

Good. However, in my case I want to composite a class B to A, while there can be multiple instances of B. Precisely, the main database class (A) has one or multiple table classes (B). Injecting a table object similar to the head object in the above example might be not what I want. Later, there might be also maybe a select class or a insert class. I do this just for practice and learn how I can keep my classes small in file size. Should I all inject all dependencies during construction and recylcle them? Or should I instantiate them within the main database class and inject the connection to the subclasses. The main database class holds the PDO object in '$_connection'.

Q1: what is the best way to compose the classes database and table.

I can think of these strategies.

Strategy #1

<?php

class db extends PDO{

  private $_connection;

  public function __construct($dsn){

    $this->_connection = new parent::__construct($dsn);

  }

  public function createTable($def){
    
     $table = new Table(this->_connection, $def);    

  }

}

Cons:

  • I have the new operator in a method which I assume is generally not ideal. Better, I should inject all instances.
  • I have to declare a createTable method in the base class. This spams my base class. If functionality increases the base class will be bigger and bigger, which is what I wanted to circumvent in the first place. I would rather like to be able to call create on the table object as in Table->create().
  • I'm not sure about the the injection of the connection to the table class. Is that good practice?

Strategy #2

<?php

class db extends PDO{

  private $_connection;
  public  $table;

  public function __construct($dsn, $table){

    $this->_connection = new parent::__construct($dsn);
    $this->table = $table;

  }    

}

$db = new db($dsn, new $Table)
$db->table->create($def);

Cons:

  • I don't have the connection available in the Table class as it is neither a child nor is the connection manually injected.

I don't think the db and Table classes are in a "is a" relationship and thus should not be inherited from each other. But currently I'm lacking a good composition implementation.

Disclaimer

I tried to work for a solution but need help on what could be the best practice for this. Composition, as posted with the example (human, head), just doesn't feel right here in the case of database and table. I hope I'll receive helpful answers, also links or buzz words are welcome as I'm just learning and I seem to have a hard time to enter the next level.

9
  • 1
    What is your specific question? Commented Jan 2, 2018 at 19:54
  • See Q1. Not clear? I need to know how an expert would arrange class table and the base class.
    – bln_dev
    Commented Jan 2, 2018 at 21:48
  • To be more precise. I find a lot of examples that show how to compose classes. Basically inject all dependencies to the constructor. But I have a hard time to use this pattern in that case of a table class. And I wanted to know if my other strategy with new Table is a valid one.
    – bln_dev
    Commented Jan 2, 2018 at 21:55
  • 2
    Here is what I suggest: focus on writing each of your classes so that it only does one job, and does that job well. If your problem is that your classes are too big, your solution is to write smaller classes. Stop worrying about inheritance for the time being, and focus on composition. Composition is merely the way you get your classes to communicate with each other and to work together to accomplish a goal, and that's all that it is. Commented Jan 3, 2018 at 1:06
  • 2
    Here is an example of a class pattern that does one job and does it well: martinfowler.com/eaaCatalog/repository.html Commented Jan 3, 2018 at 1:07

1 Answer 1

3

I'm not sure why you have a Table object, or why your db class extends PDO, but I'll try to explain a decent approach to database access in a PHP context, as this is an area I've spent a lot of time on and have a great deal of interest in.


Basic DI approach with PDOs and prepared statements

When considering database access for PHP in a PDO/PDOStatement context, we can really boil it down to a bare few things:

  1. We want to be able to open and close connections to a database
  2. We want to be able to begin, commit and rollback transactions
  3. We want to be able to prepare statements
  4. We want to be able to execute SQL as prepared statements by providing parameters

Point 1 is the responsibility of a database accessor, and involves the creation of a PDO. This is a "has a" relationship.

Points 2 and 3, I would argue, are also the responsibility of a database accessor, by way of the database accessor's PDO. The database accessor would act as a Mediator or Facade in this case.

Point 4 is the responsibility of a data access object (DAO).

Your database accessor would implement an interface something like this:

interface DatabaseAccessorInterface
{
    public static function beginTransaction(): void;
    public static function commitTransaction(): void;
    public static function dropConnection(): void;
    public static function getConnection(): PDO;
    public static function isActiveTransaction(): bool;
    public static function prepare($sql): PDOStatement;
    public static function rollbackTransaction(): void;
}

...and your data access object abstract an implementation would look something like this:

abstract class AbstractDAO
{
    private $db;

    public function __construct(DatabaseAccessorInterface $db)
    {
        $this->db = $db;
    }

    protected function db(): DatabaseAccessorInterface
    {
        return $this->db;
    }
}

class UserDAO extends AbstractDAO
{
    public function getAllUsers(): array
    {
        $sql = "SELECT * FROM user";
        $stmt = $this->db()->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }

    public function getUserByUsername($username): array
    {
        $sql = "SELECT * FROM user WHERE username = :username";
        $stmt = $this->db()->prepare($sql);
        $parameters = [
            ':username' => $username
        ];
        $stmt->execute($parameters);
        return $stmt->fetch();
    }
}

Note that this is a traditional "easy" approach to the problem, and is not the best approach IMO but is a great starting point if you're getting into designing around PDOs and prepared statements.

Ways to augment this approach include:

  • Adding a configurator, such that the database accessor "has a" configuration
  • Implementing a cache of prepared statements
  • Returning a custom Result object instead of fetch or fetchAll(), so that you can get additional information, such as rowCount, errorCode, etc.

Author's thoughts

Personally, I believe thinking about data access in a database context is heavily restricting in this day and age. Nowadays, databases are simply one form of persistence amongst a myriad of choices. We could be connecting to an SQL database, a NoSQL database, a CSV file, a remote API, etc. I think it's better to broaden the idea of data access from simply connecting to and querying against a database, to that of accessing a remote data store that could take on many forms.

When we look at it this way, we find that there are various parts in play:

  • An Accessor which is responsible for a connection to a particular type of data source
  • An AccessorConfiguration which holds configuration information for an Accessor
  • A PersistenceStrategy which hold information about which artefact in the data source will be the target for queries and commands
  • Calls, which are equivalent to SQL query strings
  • Responses, which hold information about the execution of the Calls (e.g. number of rows/columns returned, error info, etc.)
  • An OperationRepository which is responsible for the execution of Calls and returns Responses
  • An OperationCache that stores Responses against Calls

Once we start down this road, we find the interfaces become a lot simpler in their language, and you avoid polluting your code with database-specific lingo:

interface PersistenceStrategyInterface
{
    /**
     * Deletes a DataEntity from the persistence mechanism.
     *
     * @param DataEntityInterface $dataEntity
     * @return ResponseInterface
     */
    public function delete(DataEntityInterface $dataEntity);

    /**
     * Gets a DataEntity from the persistence mechanism.
     *
     * @param DataEntityInterface $dataEntity
     * @return ResponseInterface|null
     */
    public function get(DataEntityInterface $dataEntity);

    /**
     * Saves the DataEntity to the persistence mechanism.
     *
     * @param DataEntityInterface $dataEntity
     * @return ResponseInterface
     */
    public function save(DataEntityInterface $dataEntity);
}

interface OperationRepositoryInterface
{
    /**
     * Gets the Response from the OperationRepository.
     *
     * @param CallInterface $call The Call that generates the Response.
     * @param AccessorInterface $accessor The Accessor that the Call is made against.
     * @return ResponseInterface
     */
    public function response(CallInterface $call, AccessorInterface $accessor);
}

Disclaimer: this code is from Circle314 framework, which I am currently developing

2
  • Wow. Your answer is mind opening! Thanks many times. This is a good starting point. I think moving the prepare once and for all to the main class is elegant. May I ask, how would you instantiate UserDAO? Either outside the DatabaseAccessor (e.g. $db = new DatabaseAccessor(); $user_dao = new UserDAO($db);) or within ($user_dao = new UserDAO($this->db);)? Again, thanks for sharing your knowledge!
    – bln_dev
    Commented Jan 3, 2018 at 7:51
  • 1
    I generally use a dependency injection container, to ensure that only one instance of the UserDAO (and Accessor for that matter) are created. These classes are essentially services, so should be single instance. The best DI container I've found in PHP is one called PHP-DI (I have no affiliation to it, it's the work of Matthieu Napoli), though there are others of quality such as Pimple from Symfony and Container by PHP League.
    – e_i_pi
    Commented Jan 3, 2018 at 8:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.