Prismic.io for Laravel - Re-defining the CMS

Posted by Adam Engebretson on October 15, 2013

prismic.io is a web software you can use to manage content in any kind of website or app. API-driven, it is the easiest way to integrate your content with no technology or design constraint. It is also the easiest way for content writers to edit, preview and plan updates.

One of the greatest hurdles I've had to jump over as a web developer is the decision of what CMS/framework combination I should use for content-driven sites. Until now, there was no solution that allowed me to use my favorite framework without having to build my own CMS from the ground-up.

Today, that's all about to change.

Introducing Prismic.io

Prismic.io is the ideal solution for the developer of a content-driven site who hates re-inventing the wheel. The problem, to date, is the fact that most web developers like and utilize their favorite frameworks when it comes to developing complex logic and data representation/manipulation. This is great for things like, for example, inventory management, CRMs, printing shops, eCommerce, or any other custom-built web application that serves a function other than displaying content. However, in order to use these frameworks for the development of a content-driven site, such as a blog or magazine/news website, the developer has to find a way for the client to manage content. This is usually accomplished by the development of a custom CMS (I've built several myself).

Other options, such as Wordpress, Joomla!, or ModX, are capable of providing the content management aspect, as well as the front-end development aspect. The issue with this, I've found, is that the developer is then restricted to the logic, philosophy, infrastructure, and features provided by said CMS. This means learning a new way to accomplish tasks that we have already learned while browsing php.net as teenagers. It also forces us to restrict ourselves to the functionality provided by either the CMS itself, or custom-built addons built by developers who know the ins-and-outs of the CMS.

But for those of us who don't want to spend the time to build a custom CMS, and who want to be able to integrate with the content in a way that we are familiar with, in a way that allows us to use the amazing features of our favorite framework, to which we are already accustomed, the options are few.

This is where Prismic.io rocks the boat for us web developers. They've created a beautiful, easy-to-use, yet powerful back-end CMS designed with one thing in mind: managing content. While developers have the ability to customize the structure and fields of each piece of content, the end-user is presented with a simple form that can be well-documented, organized, and beautiful. The greatest part: I get to plug into the Prismic.io API to fetch the content and display it any way I'd like!

The CMS

Prismic.io has made it incredibly easy for a web developer to create several content types (aka. document masks) with different fields (aka. fragments) to suit the needs of the content. It is this powerful feature that allows the developer to build the CMS for the content, and not to build the content for the CMS.

Content masks are configured using JSON. Below is the JSON settings for the document mask entitled "article", which is what you're looking at right now!

{
  "Headings" : {
    "title" : {
      "type" : "StructuredText",
      "fieldset" : "Main title",
      "config" : {
        "single" : "heading1",
        "placeholder" : "As displayed when with the content"
      }
    },
    "illustration" : {
      "fieldset" : "Main illustration",
      "type" : "Image",
      "config" : {
        "constraint" : {
          "width" : 800.0
        },
        "thumbnails" : [ {
          "name" : "Icon",
          "width" : 100.0,
          "height" : 100.0
        }, {
          "name" : "Column",
          "width" : 320.0,
          "height" : 320.0
        }, {
          "name" : "Wide",
          "width" : 600.0,
          "height" : 300.0
        } ]
      }
    }
  },
  "Content" : {
    "content" : {
      "fieldset" : "Main content",
      "type" : "StructuredText",
      "config" : {
        "placeholder" : "This is where your all your content goes.",
        "minHeight" : "400px",
        "imageConstraint" : {
          "width" : 300.0
        }
      }
    }
  },
  "Metadata" : {
    "date" : {
      "fieldset" : "Post properties",
      "type" : "Date",
      "config" : {
        "label" : "Post date"
      }
    },
    "allow_comments" : {
      "type" : "Select",
      "config" : {
        "label" : "Allow comments",
        "options" : [ "Yes", "No" ]
      }
    }
  }
}

The amazing functionality that this can bring to the CMS is limitless, and a true test of the developer's creativity and intuition. The Prismic.io Developer Center has a thorough explanation of what sort of fragments can be put here, and how they can be configured. Furthermore, the JSON-editor used in Prismic.io has syntax checking, so you'll never go back-and-forth trying to catch that extra comma in your JSON.

The Framework

As a loyal member of the Laravel community, I am constantly searching for ways to enrich the development of Laravel applications. When I found Prismic.io, I hopped on the opportunity to develop a Laravel package that will integrate the two seamlessly. You can read up on my Laravel package on GitHub.

For those unfamiliar with Laravel, swing over to the Laravel Documentation on routing so you can see how easy it is to program the various routes that you'd like available for your app. Below, I've posted the routes.php file for this blog.

<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the Closure to execute when that URI is requested.
|
*/

Route::get('/', function()
{
    return View::make('index')
    ->with('articles', Prismic::collection('articles')->getBy('date', 'desc'))
    ->with('projects', Prismic::collection('projects')->get());
});

Route::get('post/{slug}', function($slug)
{
  $document = Prismic::collection('articles')->getSlug($slug);
  $blocks = Paginator::make($document->get($document->type.'.content')->blocks, count($document->get($document->type.'.content')->blocks), 50);
  return View::make('document', compact('document', 'blocks'));
});

Route::get('project/{slug}', function($slug)
{
  $document = Prismic::collection('projects')->getSlug($slug);
  $blocks = Paginator::make($document->get($document->type.'.content')->blocks, count($document->get($document->type.'.content')->blocks), 50);
  return View::make('document', compact('document', 'blocks'));
});

Route::get('tag/{tag}', function($tag)
{
  return View::make('index')
    ->with('tag', $tag)
    ->with('articles', Prismic::collection('articles')->tags([$tag])->get())
    ->with('projects', Prismic::collection('projects')->tags([$tag])->get());
});

You'll see that I use closures to declare the functionality of each route. However, Laravel allows for controller routing, and my favorite, resource routing. But for this project, the closure routes work just fine.

Just to give you a better idea of how this is put into practice, I've pasted below the contents of /app/views/document.blade.php, which effectively displays when View::make('document'... is called.

@extends('layouts.master')

@section('content')
  <h2 class="color">
    <span class="icon-{{Config::get('icons.'.$document->type)}}"></span>
    {{$document->getText($document->type.'.title')}}
  </h2>
  <hr>
  <p>
    <span><strong>Tags</strong>
      @foreach($document->tags as $tag)
        | <a href="/tag/{{$tag}}">{{$tag}}</a>
      @endforeach
    </span>
    <span class="pull-right">{{date("d M Y", $document->get($document->type.'.date')->asEpoch())}} | <strong>Date</strong></span>
  </p>

  <div class="modify lead">
    {{\Prismic\Fragment\StructuredText::asHtmlBlock($blocks->offsetGet(0))}}
  </div>

  @if($blocks->getCurrentPage() == 1)
    <div class="modify">
      {{@$document->getHtml($document->type.'.illustration')}}
    </div>
  @else
    {{$blocks->links()}}
  @endif

  <div class="modify">
    @for($i=$blocks->getFrom()-1;$i<=$blocks->getTo()-1;$i++)
      @if($i == 0)
        {{--<p class="lead">{{$blocks->offsetGet(0)->text}}</p>--}}
      @else
        {{\Prismic\Fragment\StructuredText::asHtmlBlock($blocks->offsetGet($i))}}
      @endif
    @endfor
  </div>

  {{$blocks->links()}}

  @if($document->getText($document->type.'.allow_comments') == 'Yes')
    <hr>
    <a href="#" name="comments"></a>
    <div id="disqus_thread"></div>
    <script type="text/javascript">
        /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
        var disqus_shortname = 'blogengeme'; // required: replace example with your forum shortname

        /* * * DON'T EDIT BELOW THIS LINE * * */
        (function() {
            var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
            dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
        })();
    </script>
    <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
    <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
  @endif
@stop

@section('scripts')
<script>
$(function(){
  $(".modify a[href^='http']").attr('target', '_blank').append(" ").append($("<span/>").addClass('icon-external-link').css('font-size', '0.75em'));
  $(".modify img").addClass('img-responsive img-thumbnail').wrap($("<p/>").addClass('text-center'));
  $(".modify.lead p").addClass('lead');
});
</script>
@stop

You'll see that the variable $document is available, which is an instance of \Prismic\Document (provided by Prismic.io's php-kit, which is automatically resolved as a dependancy when installing Prismic.io for Laravel).

You'll notice that I've utilized various parts of my document mask in various places on this document. The CMS is able to configure whether or not comments are allowed, and I've extracted the first paragraph of the content and placed it as the lead paragraph before the illustration. Additionally, I've paginated the content, so any long blog articles may have multiple pages.

Roundup

So what's the big deal? Prismic.io has saved me countless hours spent developing a custom CMS for a client who simply wants a recipe website. Next time you're looking at a content-based project, make sure to check out Prismic.io. Tell them Adam sent you. ;)