Skip to content

Rendering Markdown

Jakob Tapuć edited this page Apr 30, 2021 · 8 revisions

Introduction

In this tutorial you'll learn how to set up an async Markdown rendering service and pair it up with a simple Empress app.

Dependencies

Apart from a working Empress installation we'll need commonmark.

$ composer require league/commonmark

Code layout

We'll use the code layout shown below. We'll go through every file in the following sections.

bin/
├─ app.php
Controller/
├─ HelloMarkdownController.php
Markdown/
├─ MarkdownRenderer.php
Resources/
├─ hello_markdown.md

Writing a renderer

Empress doesn't have any kind of builtin support for template renderers or in fact any renderers for that matter. This doesn't mean you can't use them. The implemention though is left for the user to take care of. We'll create a simple class that will allow us to open files and convert their contents to Markdown.

<?php
// Markdown/MarkdownRenderer.php

namespace Empress\Example\Markdown;

use Amp\File\File;
use Amp\Promise;
use League\CommonMark\MarkdownConverterInterface;
use function Amp\ByteStream\buffer;
use function Amp\call;
use function Amp\File\open;

class MarkdownRenderer
{
    public function __construct(private MarkdownConverterInterface $converter)
    {
    }

    /**
     * @return Promise<string>
     */
    public function render(string $fileName): Promise
    {
        return call(function () use ($fileName) {

            /** @var File $file */
            $file = yield open($fileName, 'rb');
            $contents = yield buffer($file);

            return $this->converter->convertToHtml($contents);
        });
    }
}

Next we'll create a controller where we'll be able to use it:

<?php
// Controller/HelloMarkdownController.php

namespace Empress\Example\Controller;

use Empress\Context;
use Empress\Example\Markdown\MarkdownRenderer;
use Empress\Routing\RouteCollector\AnnotatedRouteCollectorTrait;
use Empress\Routing\RouteCollector\Attribute\Route;
use Empress\Routing\RouteCollector\RouteCollectorInterface;

class HelloMarkdownController implements RouteCollectorInterface
{
    use AnnotatedRouteCollectorTrait;

    public function __construct(private MarkdownRenderer $renderer)
    {
    }

    #[Route('GET', '/hello-markdown')]
    public function index(Context $ctx)
    {
        $ctx->html(yield $this->renderer->render(__DIR__ . '/../Resources/hello_markdown.md'));
    }
}

Our markdown file (Resources/hello_markdown.md):

# Hello World

# This is an h1 tag
## This is an h2 tag
###### This is an h6 tag

*This text will be italic*
_This will also be italic_

**This text will be bold**
__This will also be bold__

_You **can** combine them_


* Item 1
* Item 2
    * Item 2a
    * Item 2b

1. Item 1
1. Item 2
1. Item 3
    1. Item 3a
    1. Item 3b

In our bootstrap file we'll glue all of the pieces together:

// bin/app.php

<?php

use Amp\Loop;
use Empress\Application;
use Empress\Empress;
use Empress\Example\Controller\HelloMarkdownController;
use Empress\Example\Markdown\MarkdownRenderer;
use League\CommonMark\CommonMarkConverter;

require_once __DIR__ . '/../vendor/autoload.php';

$app = Application::create(9000);

$converter = new CommonMarkConverter();
$renderer = new MarkdownRenderer($converter);

$app->routes(new HelloMarkdownController($renderer));

$empress = new Empress($app);

Loop::run([$empress, 'boot']);

Now open up your browser and you'll see a page that looks like this:

Next steps

Our renderer has one obvious flaw - it doesn't cache the parsed contents. In practice, you'd like to cache data e.g. based on file path. Also, you might want to provide another component that will take care of matching file paths to template names.

Clone this wiki locally