Is there any important logic in your "entry point" method? It sounds like its only job is to instantiate some other classes and wire them together. If that's right then I'd say this is trivial code, it's not business critical, you'd notice immediately if it was broken (generally if you get this sort of 'composition' code wrong it won't even compile), so - I know this seems blasphemous - it doesn't need unit tests. "The best code is no code" also applies to tests: if a test isn't testing something important, difficult, or liable to break without detection, then that test is not worth the effort of writing or maintaining.
If the main
method is more than just trivial code (try...catch
blocks are common in this sort of method, as are if
statements for configuration) then you should probably consider extracting some logic. For example, if you have lots of if
statements in your app maybe you could extract a Configuration
class which handles arbitrary configurations and can easily be unit tested or extended.
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);
}
}
Best Answer
Generally the advice is not to use the singleton pattern to implement global variables.
However, here your objective seems to be 'Implement a global variable' as this will 'make it easy' for your customer.
You could well be right, global variables do make it easy to get up and running with something. If your customer needs help writing a client for your api maybe that is exactly the kind of help they want and need.
However, I would do both, allow the client to be instantiated if required AND provide a singleton wrapper class for newbies.
This will cover you for other more advanced customers who want to do things the singleton prevents, whilst also giving you the easy 'just make it work' minimal code tutorial for your 'my first app' customers