2

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

3
  • Have you considered using a dependency injection container? The user would then see: $lib = $container->get('my_lib');
    – Cerad
    Mar 16, 2014 at 14:15
  • would you use that with a standalone library you are distributing? I can see just making a big "parent library" if I went that direction
    – jmadsen
    Mar 16, 2014 at 22:38
  • For a standalone library I would tend to go with your second approach knowing that a user could then use a dependency injection container for easier access.
    – Cerad
    Mar 16, 2014 at 23:43

2 Answers 2

3

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);
    }
}
3
  • Hi David, All perfectly correct, but I'm looking for a workaround for the two-step instantiation. While this prolly goes all into a single controller (so maybe I'm making too big a deal of it), seems like there ought to be a way to say "library - here's my connection" & profit$$$
    – jmadsen
    Mar 17, 2014 at 23:04
  • I'm sure there is a way, but the library will lose focus if it also has to sometimes create the model. You could just have a factory method in the library that is called if there is no model passed in, but there is a connection object. Mar 17, 2014 at 23:10
  • The factory model example seems to meet your goals perfectly. +1 Mar 17, 2014 at 23:19
0

It might not be the "cleanest" but what about adding a function to overload the model. So by default the library can instantiate your model so it's all clean for the user but when testing you have the option to overload it.

I'm not a testing die hard though so this answer might be refuted easily.

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.