Php – Best design pattern for library with data model

design-patternsPHP

I am refactoring an old Codeigniter library to be php framework agnostic. There are these core parts:

  • the connection object – this will come from the "user"; ie, the framework's connection, or their own connection library. My library will not want to "know about it" except that (at first) it will be a PDO conn object (but things should be written to handle a native driver type)

  • the model object – runs the queries & returns results array (or error array) to the main lib. As it is now, it is very "thin" and only has transactions, query building & the actual db query

  • the model interface – I'm a big fan of interfaces, and I see this model as being swappable for a NoSQL model, for example

  • the main library – in my current CI library, this 1) receives the function request, 2) validates the params passed to it, 3) requests from the model, 4) creates a response array that includes the data & error codes and messages (multilingual configurable, but not really part of this question)

So, in summary, the way it works NOW is in your controller or calling page:

1) call main lib function
2) main lib calls model
3) model returns to main lib
4) main lib returns to controller


NOW starts my real design pattern question:

I would like to pass my conn object via DI to my main library, but I'd also like to pass my model via DI to the main library to keep things flexible. The flexibility goal I'm after would be to allow people to create their own Native DB or NoSQL models & pass non-PDO conns into this, so keeping them decoupled seems optimal.

BUT to do this, the only thing I can think of would end up as:

(controller – where I might actually execute the query in the lib, not model)

$conn //from the user
$model = new MyModel();
$lib = new MyLib($conn, $model);

or

$conn //from the user
$model = new MyModel($conn);
$lib = new MyLib($model);

which feels more "natural" since the query & conn tend to work together

Either case feels pretty clumsy for the user, and I can't help but wonder if I'm not either overdoing this whole DI thing or just have picked the wrong design pattern to go about this.

Any guidance as to how to best set this up would be greatly appreciated – spent more time thinking about this than it will take me to refactor the actual code!

TIA

Best Answer

I could be waaaaaay off, but...

It seems that a model is necessary to use the library, you just don't care which one, right? Further, the library doesn't care how the model gets its data, or where the data comes from, it just wants an object that meets the criteria of the interface.

If the library doesn't need the connection object, then make that the responsibility of the model.

interface MyFooModelInterface 
{
    public function getItem($id);
    public function addItem($item);
    ...
}

And here is a simple class example:

class MyLib
{
    protected $model;

    public function __construct(MyFooModelInterface $model)
    {
        $this->model = $model;
    }

    public function getItemById($id)
    {
        return $this->model->getItem($id);
    }
}

And a Model example:

class MyModel implements MyFooModelInterface
{
    protected $conn;

    public function __construct(\PDO $conn)
    {
         $this->conn = $conn;
    }

    public function getItem($id)
    {
        ...
    }
    public function addItem($item)
    {
        ...
    }
}

And here is an example of using it:

// the model is responsible for handling the data retrieval, not the library
$model = new Model($conn);

$lib = new MyLib($model);
return $lib->getItem($id);

Now, if I decide that I want a file-based solution, I can easily swap the model. To test the model, the connection obj is injected, so that's easy. To test the library, the model is also injected, so, again, that's easy.

In this case, the model is only responsible for getting the data, while the library is responsible for handling and processing the data. Neither cares what or how the other one does it.

EDIT

the library with a factory method to create the model if none was given...

class MyLib
{
    protected $model;

    public function __construct(MyFooModelInterface $model = null, $conn = null)
    {
        // if we have a model, use it
        if (!is_null($model)) { 
            $this->model = $model; 
        }

        // if we don't have a model, but have a connection obj, make a model
        if (is_null($model) && !is_null($conn)) { 
            $this->model = $this->factoryModel($conn); 
        }

        // if we don't have either, quit and go home
        if (is_null($model) && is_null($conn)) {
            throw new Exception('need moar inputs');
        }
    }

    protected factoryModel($conn)
    {
        $this->model = new MyModel($conn);
    }

    public function getItemById($id)
    {
        return $this->model->getItem($id);
    }
}
Related Topic