Protected Classes

Christopher Pitt
4 min readSep 20, 2014

--

Today I had a curious discussion with David Murray about the idea of protected models from being used in unapproved contexts. He has been developing an application which has some repositories which talk to some models, and we thought it might be worth a try securing the models from being used directly.

Limitations

PHP doesn’t support this kind of thing. It’s quite a ludicrous idea actually — preventing classes from being called at certain times. But it’s something (I am keen to demonstrate) can be done using the tools at hand.

Let’s say that particular classes can only be created within the context of other classes. We can only create Doctrine models within Doctrine repositories, Eloquent models within Eloquent repositories and so forth.

Don’t confuse my use of the word model to mean that all models are just wrappers for database stuff. I’m using the word model to describe implementations of the Row Data Gateway pattern. That is; when I say model I mean a class wrapper around a database table, where each instance represents a row in the database.

Looking Back

There is a particular function in PHP, which will help us to achieve this humble goal. It’s called debug_backtrace() and it produces the kind of data you are probably familiar with seeing whenever you generate an error in PHP.

Whoops!

debug_backtrace() can tell us in which class statements are made. By looking a few frames back, we can know if new instances of classes are created within the bounds of other classes. Let’s try something simple:

class DoctrineModel
{
public function __construct()
{
print_r(debug_backtrace(
DEBUG_BACKTRACE_IGNORE_ARGS, 5
)
);
}
}

new DoctrineModel();

The DEBUG_BACKTRACE_IGNORE_ARGS flag reduces the amount of resulting data, because all we’re really interested in is the class key. 5 is the number of frames we would like the trace to go back. This results in something resembling:

Array
(
[0] => Array
(
[file] => /path/to/debug.php
[line] => 37
[function] => __construct
[class] => DoctrineModel
[type] => ->
)
)

We can expand on this idea by creating the new model within another class:

class DoctrineModel
{
public function __construct()
{
print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5));
}
}

class DoctrineRepository
{
public function __construct()
{
new DoctrineModel();
}
}

new DoctrineRepository();

This results in a backtrace resembling:

Array
(
[0] => Array
(
[file] => /path/to/debug.php
[line] => 41
[function] => __construct
[class] => DoctrineModel
[type] => ->
)
[1] => Array
(
[file] => /path/to/debug.php
[line] => 46
[function] => __construct
[class] => DoctrineRepository
[type] => ->
)
)

…and that is exactly how we’re going to protect the model classes. We merely need to have some reusable code to crawl the backtrace in search of necessary creator.

Another Trail, Another Trait

The reusable code may as well be part of a trait. I love traits, deeply flawed creatures that they are. We just need something simple:

trait ProtectedClassTrait
{
/**
* @param string $parent
* @param int $depth
*/
protected function protect($parent, $depth = 5)
{
$trace = debug_backtrace(
DEBUG_BACKTRACE_IGNORE_ARGS,
$depth
);

$i = count($trace);

while ($i--) {
if ($trace[$i]["class"] === $parent) {
return;
}
}

throw new LogicException();

}
}

This, in turn, gets used in the classes we want to protect:

class EloquentModel
{
use ProtectedClassTrait;

/**
* @return EloquentModel
*/
public function __construct()
{
$this->protect(EloquentRepository::class);
}
}

We can now only create these model instances from within the intended class:

class EloquentRepository
{
/**
* @var EloquentModel
*/
protected $model;

/**
* @return EloquentRepository
*/
public function __construct()
{
$this->model = new EloquentModel();
}
}

BAAAWWWW, Dependency Injection!

I know, I know. This sucks because dependencies should be created outside of classes that need them. Ideally, the fewer concrete implementations a class depends on (read: the more interfaces a class depends on instead); the better off we are.

The answer is to use a factory!

class DoctrineRepository
{
/**
* @var ModelFactory
*/
protected $factory;

/**
* @param ModelFactory $factory
*
* @return DoctrineRepository
*/
public function __construct(ModelFactoryInterface $factory)
{
$this->factory = $factory;

$factory->make(DoctrineModelInterface::class);
}
}

So Java, WOW.

class ModelFactory implements ModelFactoryInterface
{
/**
* @var array
*/
protected $models = [
DoctrineModelInterface::class => DoctrineModel::class,
EloquentModelInterface::class => EloquentModel::class
];

/**
* @param string $interface
*
* @return ModelInterface
*/
public function make($interface)
{
if (isset($this->models[$interface])) {
$model = $this->models[$interface];

return new $model();

}

throw new InvalidArgumentException();
}
}

There you have it, my friends. Whether or not you think it’s a good idea to do this sort of thing; this is one method for achieving just such a thing.

I skipped a few of the interfaces, but the meaning should be clear. Depend on a factory so you can feel less horrible about creating dependencies inside the class. At least the interns can’t fiddle with the models now…

--

--