This tutorial has a new version, check it out!

Being Awesome with Type-Hints

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $6.00

... lines 1 - 4
class MarkdownTransformer
{
... lines 7 - 8
public function __construct($markdownParser)
... lines 10 - 18
}

What type of object is this $markdownParser argument? Oh, you can't tell? Well, neither can I. With no type-hint, this could be anything! A MarkdownParser object, a string, an octopus!

We need to add a typehint to make our code clearer... and avoid weird errors in case we accidentally pass in something else... like an octopus.

Run:

./bin/console debug:container markdown

And select markdown.parser - that's the service we're passing into MarkdownTransformer. Ok, it's an instance of Knp\Bundle\MarkdownBundle\Parser\Preset\Max. We can use that as the type-hint.

But hold on - I'm going to complicate things... but then we'll all learn something cool and celebrate. Press shift+shift, type "max" and open that class:

... lines 1 - 6
/**
* Full featured Markdown Parser
*/
class Max extends MarkdownParser
{
}

Ah, this extends MarkdownParser and that does all the work:

... lines 1 - 8
/**
* MarkdownParser
*
* This class extends the original Markdown parser.
* It allows to disable unwanted features to increase performances.
*/
class MarkdownParser extends MarkdownExtra implements MarkdownParserInterface
{
... lines 17 - 243
}

And this implements a MarkdownParserInterface. We could type-hint with Max, MarkdownParser or MarkdownParserInterface: they will all work. BUT, when possible, it's best to find a base class - or better - and interface that has the methods on it you need, and use that.

Type-hint the argument with MarkdownParserInterface:

... lines 1 - 4
use Knp\Bundle\MarkdownBundle\MarkdownParserInterface;
class MarkdownTransformer
{
... lines 9 - 10
public function __construct(MarkdownParserInterface $markdownParser)
... lines 12 - 20
}

Why is this the best option? Two small reasons. First, in theory, we could swap out the $markdownParser for a different object, as long as it implemented this interface. Second, it's really clear what methods we can call on the $markdownParser property: only those on that interface.

But hold on a second, PhpStorm is angry about calling transform() on $this->markdownParser:

Method "transform" not found in class MarkdownParserInterface

Weird! Open that interface. Oh, it has only one method: transformMarkdown():

... lines 1 - 4
interface MarkdownParserInterface
{
/**
* Converts text to html using markdown rules
*
* @param string $text plain text
*
* @return string rendered html
*/
function transformMarkdown($text);
}

Hold on: to be clear: everything will work right now. Refresh to prove it.

The weirdness is just that we are forcing an object that implements MarkdownParserInterface to be passed in... but then we're calling a method that's not on that interface.

Change our call to transformMarkdown():

... lines 1 - 6
class MarkdownTransformer
{
... lines 9 - 15
public function parse($str)
{
return $this->markdownParser
->transformMarkdown($str);
}
}

Inside MarkdownParser, you can see that transformMarkdown() and transform() do the same thing anyways:

... lines 1 - 14
class MarkdownParser extends MarkdownExtra implements MarkdownParserInterface
{
... lines 17 - 114
public function transformMarkdown($text)
{
return parent::transform($text);
}
... lines 119 - 243
}

This didn't change any behavior: it just made our code more portable: our class will work with any object that implements MarkdownParserInterface.

And if this doesn't completely make sense, do not worry. Just focus on this takeaway: when you need an object from inside a class, use dependency injection. And when you add the __construct() argument, type-hint it with either the class you see in debug:container or an interface if you can find one. Both totally work.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}