The Stream Blog

Build Scalable Newsfeeds with PHP 7 and Laravel – in 60 Minutes

Introduction

With all the excitement around the release of PHP 7 – here at Stream we have decided to give our own PHP a refresher – from our client, example apps, and Laravel framework integration.

PHP 7

PHP 7 boasts huge jumps in performance over PHP 5.X, as well as language feature additions. Here’s a benchmark of Laravel and Zend 2, for example:

php7-laravel-zend-infographic

Laravel 5.2

Since it’s original release by Taylor Otwell back in 2011, Laravel has emerged as one of the most popular PHP frameworks (right up there with decade-long players Symfony and Zend).

This is for good reasons – Laravel boasts a modular composition with awesome components such as Composer for package management, the Eloquent ORM, and the Blade templating engine. In addition, it has also offered developers a clean, object oriented syntax. Laravel is currently in version 5.2, which was released late in 2015.

Stream and Laravel

The original Stream PHP example application is built upon Laravel 5.0, as well as a branch for use in Laravel 4.

An Example Application for Laravel 5.2

We’re happy to say that the Stream-Laravel framework integration is compatible with 5.2 – and if you follow this post, we’ll be building an example application that works with 5.2 using Stream-Laravel.

Read on to see how Stream-Laravel integrates seamlessly with the major components of Laravel, such as Eloquent and Blade.

Overview

Here’s all the things we go over in this post:

  • Create LEMP Stack Droplet in DigitalOcean
  • Installing and Configuring PHP 7
  • Laravel 5.2 Quickstart Example Application Setup
  • Integrating Stream via Stream-Laravel into Quickstart Example

The final result is a todo app social network running on a LEMP PHP 7 stack on a DigitalOcean droplet.

If that seems like a whole lot – it’s actually not too much. You can go from nothing to having a LEMP server running your Stream-powered Laravel application on top of PHP 7 in about 60 minutes or less!

Why This Tutorial?

There are two major reasons to do this tutorial. The first is to learn how to set up a LEMP stack for PHP 7. The second is to integrate Stream’s scalable newsfeed technology into a Laravel application.

Learn How To Setup PHP 7 and Laravel 5.2

This is a one-stop shop for getting set up with PHP 7 and Laravel 5.2 – on a DigitalOcean droplet.

Otherwise, you will have to reference almost a dozen tutorials and StackOverflow threads to accomplish the same thing. So, why do that? Get moving with the new – Laravel 5.2 running on PHP 7 – within 15 – 30 minutes.

Scalable Newsfeed Functionality

Learn how to build a scalable news feed with PHP and Laravel – also in less than 30 minutes. The Stream-Laravel integration hooks in seamlessly to the Eloquent ORM.

Add in other Laravel-based goodies, and access to the low level Stream-PHP library and this makes for a productive way to build a stream.

Create Droplet

Alright, let’s get started building our LEMP stack server. With DigitalOcean, this will only take a couple minutes – by creating a DigitalOcean droplet.

One of the big strengths of DigitalOcean is the simple dashboard. DigitalOcean’s dashboard really makes it easy to get us going with a LEMP stack running on Ubuntu 14.

DigitalOcean-Create-LEMP-Droplet

Create LEMP Stack on Ubuntu 14 Droplet.

Note: Remember to add an SSH key so you can login.

Next, Login to your server via SSH – using the IP address provided in the DigitalOcean dashboard.

ssh root@[YOUR-SERVER-IP-ADDRESS]

When you first login you should see:


-------------------------------------------------------------------------------------
Thank you for using DigitalOcean's LEMP Application.
Your web root is located at /usr/share/nginx/html and can be seen from http://[YOUR-IP-ADDRESS]/
The details of your PHP installation can be seen at http://[YOUR-IP-ADDRESS]/info.php
Your MySQL root user's password is [YOUR-MYSQL-PASSWORD]
You are encouraged to run mysql_secure_installation to ready your server for production.
-------------------------------------------------------------------------------------

Note: You should copy down your IP address, and your MySQL password. We will be using both.

After you SSH with root access, you probably want to create a new user with sudo privileges – and log in to that user. Using root any longer is not recommended!

DigitalOcean provides a quick guide to creating a sudo user.

Upgrading to PHP 7

So, you have created an Ubuntu 14.04 LEMP droplet, and logged in (hopefully not as root, still).

Now, follow these easy steps provided by DigitalOcean to install and set up PHP 7 and the related parts of the stack.

Note: Make sure you are following “Upgrading PHP-FPM with Nginx” as we are on a LEMP stack.

Use apt-get to add additional PHP extensions:

sudo apt-get install php7.0-mbstring
sudo apt-get install php-xml
sudo apt-get install php7.0-mcrypt

For fun, let’s see the results of our hard work. Run php -v:


HP 7.0.8-3+deb.sury.org~trusty+1 (cli) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.8-3+deb.sury.org~trusty+1, Copyright (c) 1999-2016, by Zend Technologies

Oh yeah! Also, you should be able to point your browser to the IP address of your droplet:

http://[YOUR-IP-ADDRESS]/info.php

Installing Composer

Composer, the now de facto dependency manager, is a must-use in the PHP ecosystem. It will manage the dependencies – pull in required libraries, dependencies and manage them all for you. Composer also has a main repository: Packagist. Combined, Composer and Packagist make the experience similar to npm or Ruby’s Bundler.

logo-composer-transparent

Just thinking about managing PHP dependencies without Composer now makes me shutter!

In order to use Composer, install zip/unzip:

sudo apt-get install zip

Also, we will need Git installed on our system:

sudo apt-get install git

Okay, now – let’s go ahead and actually install Composer:


cd ~
curl -sS https://getcomposer.org/installer | php

This will create a file called composer.phar in your home directory. This is a PHP archive, and it can be run from the command line.

We want to install it in a globally accessible location though. Also, we want to change the name to composer (without the file extension). We can do this in a couple steps.

Let’s make it an executable that we can run as composer:


sudo mv composer.phar /usr/local/bin/composer

Alright, alright, alright! We should be able to see a list of Composer commands:


composer

Saw the list? Great. Let’s move on to Laravel.

Install Laravel & Quickstart Tutorial

Now that we have our shiny new PHP 7 based stack operational along with Composer install – it’s time to get started with Laravel 5.2.

MySQL

First, let’s set up our database. It will only take a second. We’re going to use MySQL – remember that password you saw after you logged in?

Login to MySQL on the command line – using your saved password:


mysql -uroot -p

You will be prompted for your password. Once in – enter in the following commands:

Once logged in to MySQL – enter in the following commands:


mysql> CREATE USER 'laravel'@'localhost' IDENTIFIED BY 'laravel';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT ALL PRIVILEGES ON *.* TO 'laravel'@'localhost' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE DATABASE laravel;
Query OK, 1 row affected (0.00 sec)

You can name your user, password, and database anything you want – we are using laravel for the sake of being easy.

Laravel & Stream Example Project

To make it as easy as possible to get started, let’s just clone the completed Stream-Laravel example app.

Please note that you do not need to copy and paste any code, or create any files. Rather, this is intended to help you learn while you follow along with the process.

Follow the steps below to add Laravel, along with the Stream-Laravel example application:

First, move to /var/www/:


cd /var/www/

You may want to use sudo with the following commands to avoid any permissions errors. Check out this DigitalOcean how-to and search for “permissions”.

Next, clone the Stream-Laravel-Example project as “quickstart”:


git clone https://github.com/GetStream/Stream-Laravel-Example quickstart
cd quickstart
composer install

Okay, cool. If all went well – you just installed a bunch of dependencies inside of vendor.

Note: The Stream-Laravel example application is based off of the Laravel quickstart-intermediate project. Please take a look at the additional references below for more information.

Additional References:
GitHub Repository
Laravel Quickstart Documentation

Set Database Credentials

One thing we need to do is set our new application’s database credentials – which happens in a .env file.

Copy .env.example, which is located in the root directory, to .env:


cp .env.example .env

Next, let’s add our database credentials. For the sake of an easy example we use “laravel”:


DB_HOST=127.0.0.1
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=laravel

Now let’s run php artisan migrate.

If all goes well, you should see something like:


Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrated: 2016_02_04_041533_create_tasks_table
Migrated: 2016_07_12_182728_create_follows_table

Let’s also generate and set an encryption key:

php artisan key:generate

NGINX Configuration

Open NGINX configuration file:

sudo vim /etc/nginx/sites-available/default

Ensure that your server object matches below:


server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/quickstart/public;
    index index.php index.html index.htm;

    server_name localhost;

    location / {
            try_files $uri $uri/ /index.php?$query_string;
    }
}

Restart the NGINX server:


sudo service nginx restart

Note: You may have some permissions errors with Laravel’s storage. If so, we can fix that up. You may want to do the following:


php artisan cache:clear 
chmod -R 775 storage 
composer dump-autoload

This should give your web group write permissions. If everything goes right, we should be able to pull up the example application in the browser:


http://[YOUR-IP-ADDRESS]/login

It should look like this:

Laravel-Example-App-Login

Next up, let’s integrate Stream-Laravel into our application to create some awesome social functionality.

Stream

Now, our current Laravel app is pretty basic – we can create users, login, and add/complete/delete todo tasks. Next, we’ll get integrate Stream-Laravel into the mix – which we use to build following and feed functionality into our app.

Application Config

Now that we have Stream-Laravel installed, let’s add it to our application config.

Open config/app.php.

Add the service provider to the providers array:


'providers' => [
        GetStream\StreamLaravel\StreamLaravelServiceProvider::class,
        ...
    ],

Add the FeedManager facade to the aliases array:


'aliases' => [
        'FeedManager'       => GetStream\StreamLaravel\Facades\FeedManager::class,
        ...
    ],

Okay, next let’s generate a config file for Stream-Laravel:


php artisan vendor:publish --provider="GetStream\StreamLaravel\StreamLaravelServiceProvider"

Now, we will need to add API credentials to the newly generated config file. Let’s head over to the Stream Dashboard.

Stream Dashboard

Head over to the Dashboard.

If you don’t have a Stream account, you’ll be prompted to sign up. It only takes a second.

Create an application.

Stream-Create-New-App

In our example, we use “Laravel-Test” as a name, but you can name it anything you like!

Stream-Feed-Groups

Create four feed groups – as seen below:

  • user – type: “flat”
  • timeline – type: “flat”
  • timeline_aggregated – type: “aggregated”
  • notification – type: “notification”

Don’t worry too much about the different feed types, right now. We will get into that a bit later.

Copy down your API key, API secret, and API app id from the application page.

All set?

Next, open config/stream-laravel.php and add your API key, API secret, and API app id. Also, add your location as well for good measure:


return [

    /*
    |-----------------------------------------------------------------------------
    | Your GetStream.io API credentials (you can them from getstream.io/dashboard)
    |-----------------------------------------------------------------------------
    |
    */

    'api_key' => '[YOUR API KEY]',
    'api_secret' => '[YOUR API SECRET]',
    'api_app_id' => '[YOUR API APP ID]',
    /*
    |-----------------------------------------------------------------------------
    | Client connection options
    |-----------------------------------------------------------------------------
    |
    */
    'location' => 'us-east',
    'timeout' => 3,

And now, we should be ready to rock with Stream. Let’s go!

Integrating Stream

Okay – now for the fun part! Let’s build some Stream functionality into application.

Adding Tasks as Activities – Eloquent ORM Integration

One of the coolest – and easiest – things to do with Stream-Laravel is have it hook into your Eloquent models. Let’s start with the Tasks class.

Add \GetStream\StreamLaravel\Eloquent\ActivityTrait to app/Task.php:


class Task extends Model
{

    use \GetStream\StreamLaravel\Eloquent\ActivityTrait;

No, really. Really – that’s it!

Let’s give it a quick test by adding a task in your app. Head over to the Stream Dashboard and click on “Explorer”. You should see something like:

Stream-Dashboard-Explorer

Additional Activity Data

Next, let’s flesh out our activity data a bit.

Add some extra activity data to the Task model – the task name:


    /**
     * Stream: Add extra activity data - task name, and the display name:
     */
    public function activityExtraData()
    {
        return array('name'=> $this->name, 'display_name' => $this->display_name);
    }

Let’s also modify the activity verb – changing it from the default of 'app\task' to 'created':


   /**
    * Stream: Change activity verb to 'created':
    */
    public function activityVerb()
    {
        return 'created';
    }

Follow / Unfollow Functionality – Using FeedManager

Now that we are pushing our tasks as activities to Stream, let’s take advantage of some other Stream functionality to make our app social.

Let’s create a Users page that displays name. We will also want to be able to follow or unfollow users.

It will look like this:

Screenshot 2016-07-12 11.51.56

Creating Database Tables with Artisan

We will need to store our follow/unfollow information in a database table.

We can use an Artisan migration file that has been created to automate this task. All we have to do is run the migrate function.

Run php artisan migrate.

Note: Check out more information on migration with Artisan here.

We should be ready to add some application code. Let’s go!

Models

We have several pieces of application code we need to make this new piece of functionality happen.

Model

Create a Follow model in app/Follow.php:


use App\User;
use Illuminate\Database\Eloquent\Model;

class Follow extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['follow_id'];


    /**
     * Get the user that has the follow.
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }

}

Repository

Now, let’s also create a “repository” – which is an abstraction that moves our database queries into their own functions.

Add app/Repositories/UserRepository.php:


class UserRepository
{
    /**
     * Get all users, sorted by 'following', and 'not_following' - and follow information for a given user
     *
     * @param  User  $user
     * @return Collection
     */
    public function forUser(User $user)
    {

        // Following:
        $query = 'SELECT id, name, email FROM users WHERE id IN (SELECT follow_id FROM follows WHERE user_id = ?)';

        $following = DB::select($query, [$user->id]);

        foreach($following as $key => $value) {
            $query = 'SELECT id FROM follows WHERE user_id = ? AND follow_id = ? LIMIT 1';
            $following[$key]->follow_id = DB::select($query, [$user->id, $value->id])[0]->id;
        }

        // Not Following:
        $query = 'SELECT id, name, email FROM users WHERE id
        NOT IN (SELECT follow_id FROM follows WHERE user_id = ?)';
        $not_following = DB::select($query, [$user->id]);

        return [
            'following' => $following,
            'not_following' => $not_following
        ];
    }
}

With this, we will return an array of arrays – one for users we are following, and one for users we are not.

This code uses the DB object and MySQL queries. Not the prettiest thing in the world, but it will get the job done.

Controller

Alright, next – we need a controller. It is a MVC architecture, after all.

Create a controller in Http/Controllers/FollowController.php – with the following three methods:

Add a store function – so we can add new follows:


    /**
     * Create a new task.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request, User $user)
    {

        $this->validate($request, [
            'follow_id' => 'required|max:255',
        ]);

        $request->user()->follows()->create([
            'follow_id' => $request->follow_id,
        ]);

        \FeedManager::followUser($request->user()->id, $request->follow_id);

        return redirect('/users');
    }

As you can see, we are using FeedManager which provides a followUser method that takes two simple parameters – our user id, and the id of the user we want to follow (the follow_id).

Next, add a destroy function – which is an ‘unfollow’ function:


/**
     * Destroy the given task.
     *
     * @param  Request  $request
     * @param  Follow $follow
     * @return Response
     */
    public function destroy(Request $request, Follow $follow)
    {

        $this->authorize('destroy', $follow);
        $follow->delete();

        \FeedManager::unfollowUser($request->user()->id, $follow->id);

        return redirect('/users');
    }

And finally an index function – which is used to return a view of the users (we follow, or do not follow):


/**
     * Display a list of all users in system - sorted by users following, and not following.
     *
     * @param  Request  $request
     * @return Response
     */
    public function index(Request $request)
    {
        return view('users.index', [
            'users' => $this->users->forUser($request->user()),
        ]);
    }

Again, we’re using FeedManager which provides a unfollowUser method that takes two simple parameters – our user id, and the id of the user we want to unfollow.

Routing

Add the following to app/Http/route.php:


    // Users // Follow
    Route::get('/users', 'FollowController@index');
    Route::post('/follow', 'FollowController@store');
    Route::delete('/follow/{follow}', 'FollowController@destroy');

Blade HTML Template

Alright, one more piece before we complete our Follow / Unfollow functionality – a Blade HTML template.

Create resources/views/users/index.blade.php:

For the sake of the length of this post, click on the link to view the code.

News Feed Functionality

Alright, the final piece to the puzzle. We are adding activities to Stream feeds, and we can now follow/unfollow other users. Let’s build a news feed, so we can see what the people we are following are creating for tasks.

Routing

Open up app/Http/routes.php – and add another route for the news feed:


// News Feed
Route::get('/feed', 'NewsFeedController@index');

Controller

Next, let’s create the controller that the route we just created follows – in app/Http/Controllers/NewsFeedController.php:


namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class NewsFeedController extends Controller
{
    /**
     * Construct a new NewsFeedController instance
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Display news feed.
     *
     * @param  Request  $request
     * @return Response
     */
    public function index(Request $request)
    {
        // Get your timeline:
        $feed = \FeedManager::getNewsFeeds($request->user()->id)['timeline'];

        // Get your timeline activities from Stream:
        $activities = $feed->getActivities(0,25)['results'];

        return view('news_feed.index', [
            'activities' => $activities,
        ]);
    }
}

Here we are using the FeedManager – getting the 25 most recent activities from our timeline feed.

Blade HTML Template

Let’s finish this tutorial up by adding an Blade template for our news feed.

Create resources/views/news_feed/index.blade.php:

Again, for the sake of brevity – click on this link to look at the code.

Conclusion

Well, we really jam-packed a ton inside this tutorial – DigitalOcean, PHP 7, Laravel, and Stream-Laravel. Hopefully it went well for you – and were able to cram a lot of know-how into 60 minutes.

More on Stream-Laravel

In a future post that is coming soon we will dive even further. You will learn more about Stream-Laravel, such as using the enricher to automatically enrich your activities, templating functions, low level API access, and a whole lot more. By the end, you’ll have the next Facebook on your hands!

Questions, comments, suggestions? Hit me up at erik@getstream.io or on Twitter at @8EJ3.

Until next time!


Also published on Medium.