DEV Community

Cover image for Using Twig for rendering Markdown with PHP
Roberto B.
Roberto B.

Posted on

Using Twig for rendering Markdown with PHP

Twig is a go-to templating engine for rendering HTML when developing web applications with Symfony.
However, Twig’s flexibility extends beyond generating just HTML pages. It can be a powerful tool for delivering content across multiple channels, such as generating Markdown files, JSON outputs, and even plain text, all from the same set of content.

This adaptability allows you to create content for different channels.

You can use Twig for generating HTML, Markdown, JSON, Text etc

Use case: fetching and rendering a recipe as Markdown with Symfony

In this example, we use Symfony's HTTP Client to fetch a recipe from an external API (https://dummyjson.com/recipes/1) and render it as a Markdown document using Twig.
This approach shows how you can combine Symfony's powerful packages, like the Symfony HTTP client for external data retrieval and Twig for rendering views, to deliver content across multiple channels, such as Markdown reports in a command-line tool.

The command/script we are going to build fetches the recipe data (title, description, ingredients, and instructions) from the API, processes it, and then uses Twig to output the content in a structured Markdown format. This practical use case illustrates how to use Twig beyond web templates, making it versatile for generating content in various formats.

So, we are going to use:

Installing necessary Symfony components

Ensure that you have the required components installed for HTTP requests and creating commands:

composer require symfony/http-client symfony/console twig/twig
Enter fullscreen mode Exit fullscreen mode

Create a Symfony command

First, let's create a new Symfony command.

If you want to read more about how to create a command line tool using the Symfony Command component, I wrote a specific article about this: https://dev.to/robertobutti/building-a-command-line-tool-with-php-and-symfony-console-4n6g

Commands classes, typically go into the src/Commands directory.

# create a new empty directory
mkdir -p src/Commands
# create a new empty file
touch src/Commands/FetchRecipeCommand.php
Enter fullscreen mode Exit fullscreen mode

Command Class (e.g., src/Commands/FetchRecipeCommand.php):

<?php

namespace MyExample\Commands;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpClient\HttpClient;


class FetchRecipeCommand extends Command
{

    private $twig;

    public function __construct()
    {
        $this
            ->setName('recipe')
            ->setDescription('Prints a recipe in Markdown')
            ->setHelp('This command prints a simple recipe in Markdown.');
        // Step 1: loading the Twig environment
        $loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../resources/views');
        $twig = new \Twig\Environment(
            $loader,
            // Optional: Enable caching for better performance
            /*[
                'cache' => __DIR__ . '/../../cache',
            ]*/
        );

        $this->twig = $twig;
        parent::__construct();
    }

    protected function configure()
    {
        $this->setDescription('Fetches a recipe from an API and renders it as Markdown');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Step 2: Initialize the HTTP client and fetch the recipe
        $client = HttpClient::create();
        $response = $client->request('GET', 'https://dummyjson.com/recipes/1');

        // Step 3: obtain the array of info
        $recipeData = $response->toArray();

        // Step 4: Render the template using Twig, returning a string
        $markdownOutput = $this->twig->render('recipe.md.twig', $recipeData);

        // Step 5: Output the generated Markdown
        $output->writeln($markdownOutput);

        return Command::SUCCESS;
    }
}

Enter fullscreen mode Exit fullscreen mode

Here’s a detailed explanation of each step in the FetchRecipeCommand example.

Step-by-step breakdown

Step 1: Loading the twig environment

To use Twig outside its typical web context, such as in a PHP command-line tool, you first need to initialize it manually via the Twig\Environment class. Here's an example setup for Twig in a console command:

$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../resources/views');
$twig = new \Twig\Environment(
    $loader,
    // Optional: Enable caching for better performance
    /*[
        'cache' => __DIR__ . '/../../cache',
    ]*/
);
$this->twig = $twig;

Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The twig environment is initialized by creating a FilesystemLoader that tells Twig where to find the templates. This case points to the src/resources/views folder where your Twig templates are stored.
  • Then, the $twig environment is instantiated and responsible for rendering the templates. Optional caching can be enabled to improve performance by storing precompiled templates.
  • Finally, the initialized $twig environment is assigned to $this->twig for later use (in the execute() method).

Step 2: initializing the HTTP Client and fetch the recipe

$client = HttpClient::create();
$response = $client->request('GET', 'https://dummyjson.com/recipes/1');
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The Symfony HTTP client is created using the HttpClient::create() method, which allows the command to perform HTTP requests.
  • To fetch a recipe, the request() method performs a GET request to the specified URL (https://dummyjson.com/recipes/1).
  • The API returns a JSON response stored in the $response variable.

Step 3: obtaining the array of information

$recipeData = $response->toArray();
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The toArray() method converts the JSON response from the API into a PHP array. This array contains the recipe’s data (e.g., name, ingredients, instructions), which will be used to populate the Twig template in the next step.

Step 4: rendering the template using Twig

$markdownOutput = $this->twig->render('recipe.md.twig', $recipeData);
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The Twig environment's render() method generates the Markdown output. It loads the template (recipe.md.twig) from the src/resources/views folder.
  • The recipe data fetched from the API ($recipeData) is passed into the template, where it will replace placeholders such as the recipe name, ingredients, and instructions, creating a fully formatted Markdown file.
  • The rendered Markdown content (the string returned by the render() method) is stored in the $markdownOutput variable.

Step 5: generating output for the generated Markdown

$output->writeln($markdownOutput);
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The final Markdown content is printed to the console using the $output->writeln() method. This method outputs the string to the console, allowing users to see the formatted recipe in Markdown format (eventually, you can redirect the output into a file in the shell).

Creating the starter file

You have to create a starter file to allow the user to launch your Symfony command directly from the shell. (In this article, we are not creating a Symfony application; we are building a PHP script using the Symfony packages.)
In the project directory, where you have the composer.json file and where you have the src directory, you can create a my-app file.

#!/usr/bin/env php
<?php

use MyExample\Commands\FetchRecipeCommand;
use Symfony\Component\Console\Application;

if (file_exists(__DIR__ . '/../../autoload.php')) {
    require __DIR__ . '/../../autoload.php';
} else {
    require __DIR__ . '/vendor/autoload.php';
}

/**
 * Start the console application.
 */
$app = new Application('Recipe to Markdown', '1.0.0');
$app->setDefaultCommand("recipe");

$app->add(new FetchRecipeCommand());


$app->run();

Enter fullscreen mode Exit fullscreen mode

To use correctly the namespaces and the classes be sure to set the autoload section in the composer.json file:

{
    "require": {
        "symfony/http-client": "^7.1",
        "symfony/console": "^7.1",
        "twig/twig": "^3.14"
    },
    "autoload": {
        "psr-4": {
            "MyExample\\": "src/"
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

If you change the autoload section I suggest to dump the autoload file:

composer dump-autoload
Enter fullscreen mode Exit fullscreen mode

Now, you have to create the Twig template/view to render the data retrieved by the API.

Create a Twig template/view for the recipe

Next, create a Twig template/view to render the recipe in Markdown format.
This template should go in the views directory (e.g., src/resources/view/recipe.md.twig).

# Recipe: {{ name }}

- Preparation time: {{ prepTimeMinutes }} minutes
- Cooking time {{ cookTimeMinutes }} minutes
- Difficulty level: {{ difficulty }}
- Cuisine: {{ cuisine }}
- Servings {{ servings }} people, with {{ caloriesPerServing }} calories per person

## Ingredients:

{% for ingredient in ingredients %}
- {{ ingredient }}
{% endfor %}

## Instructions:

{% for instruction in instructions %}
- {{ instruction }}
{% endfor %}

Enjoy!
Enter fullscreen mode Exit fullscreen mode

This Twig view file will render the recipe in Markdown format, with sections for the recipe's name, ingredients, and instructions.

Running the command

To execute the command, run the following in the terminal:

php my-app
Enter fullscreen mode Exit fullscreen mode

In the case you have more than one command, you can launch:

php m-app recipe
Enter fullscreen mode Exit fullscreen mode

To see all the available commands:

php my-app list
Enter fullscreen mode Exit fullscreen mode

Conclusion

With this approach, you can easily retrieve data from an external API using Symfony's HTTP client, process the response, and render the output in a structured format using Twig. In this case, the command outputs a recipe as Markdown, but this technique can be adapted to any other content or data type you need to process.
Enjoy your recipe!

Top comments (0)