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:
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.
Cool looking service for building streams / feeds: @getstream_io https://t.co/16JpOrA2xt
— Taylor Otwell (@taylorotwell) June 30, 2016
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.
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.
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:
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.
In our example, we use "Laravel-Test" as a name, but you can name it anything you like!
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:
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:
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? Join the conversation below.