If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
... 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.
// 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
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"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
}
}