Second take, a better post entity creation from Markdown files

If you haven't read the A bespoke PHP SSG, post entity creation from Markdown files, do so.

TL;DR: by extracting the logic that determines which factory to use, we ended up with a class like this:

class PostTypeFactoryPicker
{
public function pick(MdFile $inputFile): PostTypeFactory
{
}
}

The "problem" with it

Attempting to test or even just imagining how to test a class reveals the untestability of the code. This is not necessarily due to the code being "bad"; rather, it lacks the "right" structure.

We need to organize the code in a specific way to facilitate testing. Consider the difference it makes when you are creating classes within other classes or if you pass them as a dependency.

Extensibility is similar.

If we don't attempt to extend or at least envision how someone else could extend our code, we never grasp certain aspects of it.

Imagine that

To introduce another post-type factory, what steps must we take? Creating the factory is a given. But as a second step, we need to add some logic to the PostTypeFactoryPicker.

This means even if someone passed in a new factory to our dependency container, they would have a hard time adding that logic. They can't simply edit the pick method.

They could create a decorator for the PostTypeFactoryPicker, but that seems a lot of trouble.

We can solve this by altering the design. By not containing the determination logic in the PostTypeFactoryPicker class and keeping it in factories, the problem disappears.

The possible solution

A few good names exist for such a method, from supports, canCreate, to isValidFor.

As a first step, we can add to our existing interface:

interface PostTypeFactory
{
public function isValidFor(MdFile $inputFile): bool
public function create(MdFile $inputFile): Post
}

Then, the PostFactory can be modified as follows, and the picker class can be removed:

class PostFactory implements PostTypeFactory
{
public function create(MdFile $inputFile): Post
{
foreach($factories as $factory) {
if ($factory->isValidFor($inputFile)) {
return $factory->create($inputFile);
}
}
return new NullPost();
}
 
public function isValidFor(MdFile $inputFile): bool
{
// ???
}
}

One more change

We have to add the isValidFor to the PostFactory, which doesn't make sense.

We could return true and call it a day, even throw an exception saying it's not implemented.

Alternatively, we can solve this problem by creating more granular interfaces:

interface PostTypeFactoryValidityChecker {
public function isValidFor(MdFile $inputFile): bool
}
 
interface PostTypeFactory
{
public function create(MdFile $inputFile): Post
}

With this change, we only have to implement what we need:

class ArticleFactory implements PostTypeFactory, PostTypeFactoryValidityChecker
{
}
 
class PostFactory implements PostTypeFactory
{
}

As a conclusion

Now, we are in a position where all we have to do is pass in the factory after creating it. There's nothing else we have to change in our code.

This even makes certain people with specific ideas about a particular topic smile.

While the advantages may not be significant for a solo pet project, nurturing good habits and reflexes for situations where they will be necessary is not fruitless.