Unisys12's Blog

My adventures, trials & tribulations of learning web development as a hobby! I mean... who does that?


Dynamic Product Pages with Laravel 4

Phillip Jackson - 2014-03-27 02:14:44
How to create dynamic product pages using Laravel 4

Overview

Several months ago I rewrote the website for the company I work for. One of the most painful parts of our old site was the product sections. It was clever, for me at the time, but after a few new products came out I quickly realized the error of my ways. I documented how I went about that… if you haven’t just ate or not afraid of the dark or anything, you can have a look at that process over on my old blog over at Blogger. Just noticed that it was posted in Oct 2012. Shew! Glad that’s over!!

To summarize a small bit though, in case you did just eat or about too, I wanted to create a central data store and then create templates that would pull from that store. I was so happy when I got that working. It was just great. But, like I said, after a few other products came out… PAIN! And I was not going to make that same mistake twice. The follow is how I approached it, exactly one year later.

URIs are Important to Me

And this is the case that really drove it home for me. I wanted the uri’s for the different products to be some what memorial. At the very least, easy to find a machine by landing on the product group page the machine belonged to. What do I mean? We sell and service copiers and printers. All of these can be classified in different ways, but I chose to first break them up into their different family groups - MFPs(multi-functional Products), Printers, Wide Formats, Duplicators, etcs. Next each family group will contain one of two different classifications, Color or Black & White. So we have Color MFPs and Black & White MFPs. We have Color printers and we have Black & White Printers. Lastly, we will have our model names. Get it? If not, it will make a bit more sense in just a minute. Basically, I just gave myself three different route parameters to play with, for my uri. Which will look like this, using my route parameters :

Route::get('/products/{family}/{color_class}/{model}')

So given how I have broken up the products, my uri should be able to look like this : http://raycocopiers.com/products/mfp/bw/MP301. And now that I know how I want my uri’s, to look, I can build my routes and controller, making sure to mirror the example above.

Migration

First, lets get our data situated first. There really no need to show the whole migration class since we all pretty much know what’s in it, so I will just show you the up method :

public function up()
{
Schema::create('products', function(Blueprint $table)
    {
        $table->increments('id');
        $table->string('model');
        $table->string('color_class');
        $table->string('family');
        $table->integer('ppmbw');
        $table->integer('ppmcolor');
        $table->string('comment_one');
        $table->string('comment_two');
        $table->string('comment_three');
        $table->string('comment_four');
        $table->string('comment_five');
        $table->timestamps();
    });
}

Product.php

class Product extends Eloquent {

    protected $guarded = array(
        'id', 'created_at', 'updated_at'
        );
}

We have a very simple model, that extends Eloquent, and guards the ‘id’ and ‘timestamps’ from mass assignment. Simple enough.

ProductController.php

Next, we need to return a view for our products index page. Which I will show in just a minute.

class ProductController extends BaseController {

    public function __construct(Product $product)
    {
        $this->product = $product;
    }

    /**
     * Display the products index page, which lists the different families of products.
     * uri : “http://raycocopiers.com/products
         * 
     * @return Response
     */
    public function index()
    {
        // http://www.raycocopiers.com/products
        return View::make('products.index')->with('posts', Post::getPosts());
    }

Remember that I broke each of the different product groups into families and classified those groups by their color classification? Well, we are putting that to work here! Let’s take a look at the method to grab just a single family with a color class..

    /**
     * Display a family within a color class.
     * uri : http://raycocopiers.com/products/mfp/bw
     *
         * @var string $family
         * @var string $color_class
     * @return Response
     */
    public function showFamily($family, $color_class)
    {
        $product = $this->product->where('family', '=', $family)
                                 ->where('color_class', '=', $color_class)
                                 ->get();

        return View::make('products.family')
            ->with('family', $family)
            ->with('color_class', $color_class)
            ->with('product', $product)
            ->with('posts', Post::getPosts());
    }

Next let’s create a method that will return a single model, from a product family with a given color class.

    /**
     * Display a model, from a family within a color class.
     * uri : http://raycocopiers.com/products/mfp/bw/mp305
     *
         * @var string $family
         * @var string $color_class
     * @return Response
     */
    public function showModel($family, $color_class, $model)
    {
        $product = $this->product->where('family', '=', $family)
                                 ->where('color_class', '=', $color_class)
                                 ->where('model', '=', $model)
                                 ->get();

        return View::make('products.model')
            ->with('family', $family)
            ->with('color_class', $color_class)
            ->with('model', $model)
            ->with('product', $product)
            ->with('posts', Post::getPosts());
    }

Views/Products

Now that we have our controller methods return some stuff to some views, let make those now.

index.blade.php

The products.index view is just a list of links to the different families and classes of products. I decided to do this, instead of a drop-down menu, for a more mobile first friendly approach.

@extends('layouts.master')

@section('content')

<section class="large-8 columns">
    <header>
        <h3></h3>
    </header>
    <nav class="content">
        <li><a href=" {{ url('/products/mfp/bw') }} ">Black and White MPFs</a></li>
        <li><a href=" {{ url('/products/printer/bw') }} ">Black and White Printers</a></li>
        <li><a href=" {{ url('/products/mfp/color') }} ">Color MFPs</a></li>
        <li><a href=" {{ url('/products/printer/color') }} ">Color Printers</a></li>
        <li><a href=" {{ url('/products/wideformat/bw') }} ">Wide Formats</a></li>
        <li><a href=" {{ url('/products/duplicator/bw') }} ">Duplicators</a></li>
    </nav>
</section>

@stop

family.blade.php

The products.family view is doing a few more interesting things than just displaying a list of links. First off, looking back at the ‘showFamily()’ method, you notice that we are passing the route parameters as arguments to the method itself. This allows us to use that info not only in our queries, but they can also be passed to our views and displayed in the resulting pages as well. We use these to our advantage and create a simple page header with the data, using the php method ‘strtoupper’ to convert the strings to all uppercase letters when displayed. Next, we use a foreach loop to iterate over our collection of products that was returned from our showFamily() method. We then use that to fetch the image of the product, (notice that I name my images in certain way. Naming conventions are very important for things like this to work) and display the product's model name, followed by a link to view that single model. Which just so happens to hit the next route and controller method, ‘showModel()’.

@extends('layouts.master')

@section('content')

<section class="large-8 columns">
    <header>
        <h3> {{ strtoupper($color_class) ." ". strtoupper($family) }} </h3>
    </header>
    <article class="family">
        @foreach($product as $item)
        <img src= "http://raycocopiers.com/images/products/{{ $item->family }}/{{ $item->color_class }}/{{ strtoupper($item->model) }}.jpg" alt="{{ $item->model }}">
        <h6>{{ $item->model }}</h6>
        <a href="{{ $item->color_class }}/{{ $item->model }}">Want more info?</a>
        <hr>
        @endforeach
    </article>
</section>

@stop

model.blade.php

Here, we are returning a single product, so we display it as such. Again, passing the route parameter through the controller, into the view, we can simply display the model name that we are currently viewing, at that time. Followed by a similar process as before, to fetch the image. This time though, we are also listing comments that are stored in the database, that go along with that particular product model.

@extends('layouts.master')

@section('content')

<section class="large-8 columns">
    <header>
        <h3>{{ strtoupper($model) }}</h3>
    </header>
    <article class="model">
        @foreach($product as $item)
        <img src= "http://raycocopiers.com/images/products/{{ $item->family }}/{{ $item->color_class }}/{{ strtoupper($item->model) }}.jpg" alt="{{ $item->model }}" class="left">
        <div class="large-6 columns description">
            <ul>
                <li>{{ $item->comment_one }}</li>
                <li>{{ $item->comment_two }}</li>
                <li>{{ $item->comment_three }}</li>
                <li>{{ $item->comment_four }}</li>
                <li>{{ $item->comment_five }}</li>
            </ul>
        @endforeach
        </div>
        <h6>Want to contact one of our sales staff about this product? This <a href="{{ route('info_form') }}?model={{ $model }}" title="Model Info Link">form</a> can help!</h6>
    </article>
</section>
@stop

Notice the contact statement in the h6 element at the bottom. Yeah, neat little trick I figured out. Actually got mentioned on the old Laravel.io site. Will rewrite that tutorial over here soon, I promise.

Route.php

Now that we have some data being returned from our controller methods, we have some views to display all that data, so let’s create a few routes that will allow us to access those methods and views.

// Display view to show a single machine
// raycocopiers.com//products/mfp/bw/MP301
Route::get('/products/{family}/{color_class}/{model}', array('uses' => 'ProductController@showModel'));

// Display view to show a class of machines
// raycocopiers.com/products/mfp/bw
Route::get('/products/{family}/{color_class}', array('uses' => 'ProductController@showFamily'));

// Display a list of product families
// raycocopiers.com/products
Route::get('/products', array('as' => 'products', 'uses' => 'ProductController@index'));

Nothing fancy here at all. Just note the use of route parameters (remember, we pass those to the controller and through there to the view), assigning a name for the route and assigning a controller method to the route.

So that’s it, really! To see how all this works or get a feel for it, head over to http://rayococpiers.com/products and give it go. I know, there are always far more complicated ways of doing this or abstracting things out of the controllers and such. To be honest, this was my first full Laravel 4 project. I had played with stuff here and there, just nothing from beginning to end or that required more than just working with Blade a great deal. Sure, there is always room for improvements and I am always open to suggestions. Overall, nothing fancy or ground breaking. Just plain old code. But a far cry better than what I was doing just a year ago. Like I said, glad that’s over.

PHP, LARAVEL, ELOQUENT, PRODUCT DISPLAYS
comments powered by Disqus