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 callcreate
on the table object as inTable->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.
Best Answer
I'm not sure why you have a
Table
object, or why yourdb
class extendsPDO
, 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: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:
...and your data access object abstract an implementation would look something like this:
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:
Result
object instead offetch
orfetchAll()
, so that you can get additional information, such asrowCount
,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:
Accessor
which is responsible for a connection to a particular type of data sourceAccessorConfiguration
which holds configuration information for anAccessor
PersistenceStrategy
which hold information about which artefact in the data source will be the target for queries and commandsCalls
, which are equivalent to SQL query stringsResponses
, which hold information about the execution of theCalls
(e.g. number of rows/columns returned, error info, etc.)OperationRepository
which is responsible for the execution ofCalls
and returnsResponses
OperationCache
that storesResponses
againstCalls
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:
Disclaimer: this code is from Circle314 framework, which I am currently developing