Examples

Creating a simple page type : display data from an API

You want to add a page where data from an API is displayed ? Here's a tutorial on how to do that :

We'll use the packagist API to find bundles and display a list of packages into the frontend.

At first, go to your AppBundle (or any other bundle if you want), and create the "Type" directory.
This directory is where you'll store every page type you want to add. One page type is one PHP file.
Let's create a new file that we'll name "PackageType.php". Extend the class

VFou\CmsBundle\Type\AbstractType

and implements all methods. You should have a file like this :

<?php

namespace AppBundle\Type;

use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
use VFou\CmsBundle\Entity\Page;
use VFou\CmsBundle\Type\AbstractType;

class PackageType extends AbstractType
{

    /**
     * Frontend rendering
     * @param Page $page
     * @param array $options
     * @return string : HTML to render
     */
    public function display(Page $page, $options = [])
    {
        // TODO: Implement display() method.
    }

    /**
     * adds required fields to formbuilder
     * @param FormBuilderInterface $fb
     * @param Page $page
     * @return FormBuilderInterface
     */
    public function addForm(FormBuilderInterface $fb, Page $page)
    {
        // TODO: Implement addForm() method.
    }

    /**
     * converts form result to database entries
     * @param Request $request
     * @param Page $page
     */
    public function handleRequest(Request $request, Page &$page)
    {
        // TODO: Implement handleRequest() method.
    }
}

We have to implement the three functions if we want our page type to work properly.
The first step is to define what will be in the backend form. The function "addForm" is our target :

We want to search packages, so it's better if you can edit at any time the search query, let's add it to the FormBuilder :

public function addForm(FormBuilderInterface $fb, Page $page)
{
    $fb->add("search", TextType::class, [
         'label' => "Packagist search query",
         'mapped' => false,
    ]);
    return $fb;
}

Now that this function is done, let's start with the "handleRequest" function. His purpose is to fetch the previously created form and store it into the page's data field.
Let's register the user query directly into the data :

public function handleRequest(Request $request, Page &$page)
{
    $query = $request->request->get("vfou_cmsbundle_page")["search"];
    $page->setData($query);
}

We take the request and fetch the backend form (he's always named "vfou_cmsbundle_page"), with the field "search" that we defined in the "addForm" function.

Before defining "display", we have to modify "addForm" one more time :
We have stored the query into the page's data, we'll have to pre-fill the backend form's field with it. We just need to set the "data" option into the field definition :

$fb->add("search", TextType::class, [
    'label' => "Packagist search query",
    'data' => $page->getData(),
    'mapped' => false,
]);

Now, we have our backend form functionnal, let's start with the "display" function :
At first, we have to get the data from the Packagist's API, and extract them as an array of packages :

$json = json_decode(file_get_contents("https://packagist.org/search.json?q=".$page->getData()));
$packages = $json->results;

Note that we use our page's data for the query.
This function require that you return HTML. We'll use twig for rendering our page :

return $this->twig->render("@App/Type/extension_list.html.twig", ["page"=>$page, "packages"=>$packages]);

We'll need to create the template at this location :

Resources\views\Type\extension_list.html.twig
<ul>
{% for pack in packages %}
    <li><a href="{{ pack.repository }}">{{ pack.name }} : {{ pack.description }}</a></li>
{% endfor %}
</ul>

( This template is minimalist, you can edit it at will )

Our page type is now finished, you'll have to register it by overriding the "initialize" function :

public function initialize($name = "Package", $icon = "/bundles/app/icons/package.svg")
{
    parent::initialize($name, $icon);
}

Note that we provide a name and an icon. You are not required to use an icon, but it's better for the page tree view.
The name is really important, you must not use spaces, prefer using translations for that. Note that you can translate the name in the backend form by creating a translation file "forms.**.yml".

At last, you have to register your type in the service.yml :

services:
    # ...
    midnight_type.package:
        class: AppBundle\Type\PackageType

Now go to your backend. If your new type is not displayed in the "type" select-box when you edit a page, clear the cache with the symfony console and retry.

That's all ! Thanks for reading, if you have any questions or problems you can contact me on Twitter @VincentFoulon80