Laravel 5.1 Beauty - Creating Beautiful Web Apps With Laravel 5.1 PDF
Laravel 5.1 Beauty - Creating Beautiful Web Apps With Laravel 5.1 PDF
com
[email protected]
Laravel 5.1 Beauty
by Chuck Heintzelman
[email protected]
* * * * *
* * * * *
[email protected]
Table of Contents
Thank You
The Source Code is on GitHub
Feedback
Other places to learn Laravel 5.1
Chapter 1 - Introduction
Chapter Contents
Long Term Support
Why This Book
GitHub and the Blog
What is the Application?
Conventions Used This Book
Have Fun
Chapter 2 - Required Software and Components
Chapter Contents
The Rise of the Virtual Machines
About Laravel Homestead
Installing Virtual Box
Installing Vagrant
Where Do I Execute Things?
Recap
Chapter 3 - Setting up a Windows Machine
Chapter Contents
Multiple Ways to Setup Windows
Step 1 - Installing PHP Natively
Step 2 - Install Node.js
Step 3 - Install Composer
Step 4 - Install GIT and set up SSH Key
Step 5 - Adding the Homestead box
Step 6. Installing Homestead
Step 7 - Bring up the Homestead VM
Step 8 - Setting up PuTTY
Step 9 - Installing Laravel’s Installer
Recap
Chapter 4 - Setting up an OS X or Linux Machine
Chapter Contents
Slight Variations with Linux
Step 1 - Installing PHP
[email protected]
Step 2 - Install Node.js
Step 3 - Install Gulp
Step 4 - Install Composer
Step 5 - Adding SSH Keys
Step 6 - Adding the Homestead box
Step 7 - Installing Homestead
Step 8 - Bring up the Homestead VM
Step 9 - Installing the Laravel Installer
Recap
Chapter 5 - Homestead and Laravel Installer
Chapter Contents
The Homestead Tool
Overview of Common Homestead Commands
Examining Homestead.yaml
Adding Software to the Homestead VM
Daily Workflow
Six Steps to Starting a New Laravel 5.1 Project
Other Homestead Tips
Recap
Chapter 6 - Testing
Chapter Contents
Creating the l5beauty Project
Running PHPUnit
Using Gulp for TDD
Creating a Markdown Service
Other Ways to Test
Recap
Chapter 7 - The 10 Minute Blog
Chapter Contents
Pre-work before the 10 Minute Blog
0:00 to 2:30 - Creating the Posts table
2:30 to 5:00 - Seeding Posts with test data
5:00 to 5:30 - Creating configuration
5:30 to 7:30 - Creating the routes and controller
7:30 to 10:00 - Creating the views
Recap
Chapter 8 - Starting the Admin Area
Chapter Contents
Establishing the Routes
Creating the Admin Controllers
[email protected]
Creating the Views
Testing logging in and out
Recap
Chapter 9 - Using Bower
Chapter Contents
Stealing Code
Installing Bower
Pulling in Bootstrap
Creating admin.less
Gulping Bootstrap
Running gulp
Updating the admin layout
Adding FontAwesome and DataTables
Recap
Chapter 10 - Blog Tags
Chapter Contents
Creating the Model and Migrations
Implementing admin.tag.index
Implementing admin.tag.create
Implementing admin.tag.store
Implementing admin.tag.edit
Implementing admin.tag.update
Finishing the Tag System
Recap
Chapter 11 - Upload Manager
Chapter Contents
Configuring the File System
Adding a Helpers file
Creating an Upload Manager Service
Implementing UploadController index
Finishing the Upload Manager
Setting Up Your S3 Account
Configuring L5Beauty to Use S3
Installing an Additional Package
Test The Upload Manager
Fixing Bucket Permissions
Recap
Chapter 12 - Posts Administration
Chapter Contents
Modifying the Posts table
[email protected]
Updating the Models
Adding Selectize.js and Pickadate.js
Creating the Request Classes
Creating the PostFormFields Job
Adding to helpers.php
Updating the Post Model
Updating the Controller
The Post Views
Removing the show route
Recap
Chapter 13 - Cleaning Up the Blog
Chapter Contents
Using the Clean Blog Template
Creating the BlogIndexData Job
Updating the BlogController
Building the Assets
The Blog Views
Adding a Few Model Methods
Updating the Blog Config
Updating our Sample Data
Recap
Chapter 14 - Sending Mail and Using Queues
Contents
Setting Up for Emails
Adding a Contact Us Form
About Queues
Queuing the Contact Us Email
Automatically Processing the Queue
Queing Jobs
Recap
Chapter 15 - Adding Comments, RSS, and a Site Map
Contents
The Problem with Comments
Adding Disqus Comments
Adding Social Links
Creating a RSS Feed
Create a Site Map
Recap
Chapter 16 - General Recap and Looking Forward
Contents
[email protected]
Testing
Eloquent Models and the Fluent Query Builder
Advanced Routing
Migrations, Seeding, and Model Factories
Dependency and Method Injection
Facades vs. helpers vs. IoC objects
Laravel Elixir
Tinker
Artisan Commands
Events
Form Requests
Blade Template Engine
Flysystem
Queues
Blog Features to Add
Final Recap and Thank You
[email protected]
Thank You
Thank you for purchasing this book. I hope you find it informative and useful.
Feedback
Feedback is encouraged!
If you find a typo, have a correction, or just want to comment on something you’ve found
useful please drop by LaravelCoding.com and comment on the appropriate chapter
where you’ve found an issue.
Chapter Contents
Long Term Support
Why This Book
GitHub and the Blog
What is the Application?
Conventions Used This Book
Have Fun
This is important because the applications you build today will still be supported by the
framework tomorrow.
But the Getting Stuff Done with Laravel 4 book isn’t really a manual covering every
aspect of Laravel 4. It’s a process and design book. The principles discussed within that
book still are valid in Laravel 5.1, even if the implementation may vary slightly.
Instead of updating my previous book, I’ve created a new book, Laravel 5.1 Beauty, to
highlight some of the new features. This book is bigger and better than Getting Stuff
Done with Laravel 4.
This book has a different tone than my previous book. No lame attempts to be funny. (I
guess we all can’t be Dayle Rees.)
Laravel 5.1 Beauty goes through the process of creating, designing and coding a real-
world application while focusing on the the architecture that makes Laravel the number
one PHP framework available today.
The standard indentation for PHP code is 4 spaces. Since this book is available in a
variety of eBook formats and some devices with small screens don’t have much
horizontal space, code within this book is indented 2 spaces instead of 4 to save space.
for ($i = 1; $i <= 10; $i++) {
echo "I can count to $i\n";
}
If you see any line ending with a backslash, that means the code should continue
uninterrupted with text from the next line.
$ here_is_a_really_really_long_command that_has_a_long list of_arguments\
which should continue
In the above line, even though two lines are shown you should type in everything,
[email protected]
excluding the backlash into one line.
Whenever a Windows command prompt is used, the prompt always begins with C: and
ends with the > symbol.
C:\some\path>
Whenever the OS X Console or Linux console is used, the prompt also ends with the >
symbol, but slashes are used instead of backslashes. Often there’s a tilde (~) in the path.
~/some/path>
Finally, whenever the console for the Homestead Virtual Machine is used, the standard
dollar sign $ prompt is used. (The majority of the book uses the Homestead Virtual
Machine.)
~/somepath$
With the Homestead Virtual Machine, your prompt actually shows the username and
hostname before the path. For example: vagrant@homestead:~$, but the username
and hostname are only occasionally illustrated.
Chapter Contents
The Rise of the Virtual Machines
About Laravel Homestead
Installing Virtual Box
Installing Vagrant
Where Do I Execute Things?
Recap
Laravel embraces VM technology and packages it’s own “box” with the most common
requirements for web applications. This pre-packaged development environment is
called Laravel Homestead.
A car is the perfect metaphor for how this all works together. Homestead is the
driver’s seat of the car, Vagrant is the car’s frame, and VirtualBox is the engine. Once
Vagrant and VirtualBox are installed, there’s no need to worry about them again. All
interaction with the VM occurs through Homestead. (Just like when driving a car,
there’s no need to worry about the frame or engine.)
Laravel Homestead allows you to use a virtual Ubuntu Linux machine, pre-installed
[email protected]
with the software required for web development. This VM includes:
Ubuntu 14.04
PHP 5.6
HHVM
Nginx
MySQL
PostgresSQL
Node (With Bower, Grunt, and Gulp)
Redis
Memcached
Beanstalkd
Laravel Envoy
Fabric + HipChat Extension
And best of all, Laravel Homestead allows the same development environment to be
used on Windows, OS X, or Linux systems without worrying about conflicting software
on the host machine.
But if you don’t yet have a back-end installed, use the VirtualBox platform package. It’s
free and works on every major platform.
[email protected]
Go to `www.virtualbox.org](https://www.virtualbox.org/wiki/Downloads), download
and install the package for your operating system.
Installing Vagrant
Once you have VirtualBox (or another back-end provider) installed, you need to install
Vagrant.
[email protected]
When the Vagrant installation is complete, you may need to reboot your machine. After
the reboot, verify Vagrant is installed by opening the console (command prompt in
Windows, terminal in OS X or Linux) and checking the version.
Checking the Vagrant Version
% vagrant --version
Vagrant 1.6.5
Nginx is the web server used to serve the web pages. The Host OS can access the web
pages using the standard HTTP port (80) at the address 192.168.10.10. The Host OS
can also access web pages at 127.0.0.1 on port 8000.
Editing Files
The edited files are immediately available in the Homestead VM through shared
folders.
MySQL
You can access MySQL from your Host OS with the following information.
Memcached
Beanstalkd
Beanstalkd is a simple and fast work queue. It runs within the Homestead Virtual
Machine.
Git or Subversion
[email protected]
Run from your Host OS.
Although you can run these version control systems from either place, it is strongly
recommended to only run them from your Host OS. Consistently running them in one
location avoids potential conflicts.
For example, let’s say you install subversion in the Homestead Virtual Machine and it’s
version 1.8. You check out source code within the Homestead Virtual Machine and then
try to check it in from your Host OS. If subversion v1.7 is installed on your Host OS you
won’t be able to do anything until upgrading subversion on your Host OS.
Bower
Bower is a simple to use package manager for the web. You can run this from either
place if bower’s installed on your Host OS.
Gulp
Gulp is a simple build system Laravel Elixir uses to concatenate assets, minify assets,
combine assets, copy assets, and automate unit tests.
When running Gulp from your Host OS, growl-like notifications will appear in your OS
when certain tasks are performed (such as LESS files compiled). If you execute Gulp
within the Homestead Virtual Machine there will be warning errors when these
notification attempts are made.
Composer
If you’re Host OS is OS X or Linux you can run from either place, but if your Host OS is
Windows then Composer creates necessary batch files required to operate correctly.
Artisan
Only run artisan from the Homestead Virtual Machine. The main reason for this is that
any specific database, queue, and cache drivers are installed within Homestead and may
not be available (or installed) on your Host OS. Also, the database setting of
localhost is from the Homestead VM perspective, not from your Host OS’s
perspective.
[email protected]
Recap
In this chapter we discussed the various software required to develop applications in
Laravel 5.1 and installed VirtualBox and Vagrant.
If your machine is a Windows box, continue to the next chapter, Setting up a Windows
Machine. Otherwise, skip to the chapter Setting up an OS X or Linux Machine.
[email protected]
Chapter 3 - Setting up a Windows Machine
This chapter goes through the steps required to set up and install the supporting software
for Laravel Homestead on a Windows machine. It is assumed VirtualBox and Vagrant
were already installed from the previous chapter.
Chapter Contents
Multiple Ways to Setup Windows
Step 1 - Installing PHP Natively
Step 2 - Install Node.js
Step 3 - Install Composer
Step 4 - Install GIT and set up SSH Key
Step 5 - Adding the Homestead box
Step 6 - Installing Homestead
Step 7 - Bring up the Homestead VM
Step 8 - Setting up PuTTY
Step 9 - Installing Laravel’s Installer
Recap
Then edit php.ini in a text editor and change the following lines.
Changes in php.ini
// change
; extension_dir = "ext"
// to
extension_dir = "ext"
// change
;extension=php_openssl.dll
// to
extension=php_openssl.dll
// change
;extension=php_mbstring.dll
// to
extension=php_mbstring.dll
Now, within the C:\Php directory, you should be able to execute php.
Checking the PHP version
C:\Php> php --version
PHP 5.6.10 (cli) (built: Oct 30 2014 16:05:53)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2014 Zend Technologies
The next time you open up a command prompt, php will be available in your path.
Install using defaults. Once installed, open up a new command prompt and check the
installation by looking at the versions installed.
Checking node and npm versions
C:\Users\Chuck> node --version
v0.10.33
Composer Page
Download and install the Windows setup program, Composer-Setup.exe. Use the
defaults when installing and if it asks you the path to PHP, enter C:\Php\php.exe.
Once Composer installs, close any command prompts and open up a new one. Check the
version of composer to see if installed correctly.
Checking the Composer version
C:\Users\Chuck> composer --version
Composer version 1.0-dev (b23a3cd36870ff0eefc161a4638d9fcf49d998ba)\
2014-11-21 17:59:11
(At the time of this writing, the file downloaded is named Git-1.9.4-
preview20140920.exe.)
Step 4.2 - Install, choosing the ‘Use Git from Command Prompt’ option
Run the file just downloaded and choose default options until you get to the following
screen.
Make sure you select the Use Git from the Windows Command Prompt option.
[snip]
In order to access homestead from any command prompt, add this path to your User path
variable.
Follow the same steps to do this as you did back in Step 1.3 - Adding C:\Php to the
path but this time the path to add will be below (replacing YOU as appropriate).
Paths to add
C:\Users\YOU\AppData\Roaming\Composer\vendor\bin;vendor\bin
Remember
You only need to initialize Homestead once on your machine
[snip]
Now the Homestead Virtual Machine is running. If you exit the Windows command
prompt, the VM is still running. It’ll remain active until you issue a homestead halt
command from the Windows command prompt.
You can log onto the Homestead Virtual Machine, but on Windows we won’t use the
homestead ssh command, we’ll use PuTTY.
The first time you run this session you’ll have a confirmation box, but after that you’ll
log onto the Homestead Virtual Machine without having to type a password.
You may want to create a shortcut on your desktop. The item you want the shortcut to
point to is: "C:\Program Files (x86)\PuTTY\Putty.exe" -load homestead and
name the shortcut homestead.
* Documentation: https://help.ubuntu.com/
Since your path was already updated in Step 6.2 to contain composer’s bin directory,
the laravel command should already be accessible from a DOS Prompt. Verify it by
checking the version.
Checking the Laravel Version
C:\Users\Chuck>laravel --version
Laravel Installer version 1.1
Congratulations!
You now have a virtual Ubuntu 64-bit machine, ready for developing your Laravel 5.1 web
applications.
Recap
[email protected]
This chapter was basically a laundry list of steps to follow in order to get Laravel
Homestead up and running on your Windows machine. The good news is, these steps
only have to be performed once.
Now, skip to the Using Homestead chapter for some information about Laravel
Homestead.
[email protected]
Chapter 4 - Setting up an OS X or Linux Machine
This chapter goes through the steps required to set up and install the supporting software
for Laravel Homestead on an OS X or Linux machine. It is assumed VirtualBox and
Vagrant were already installed from the Required Software and Components
chapter.
Chapter Contents
Slight Variations with Linux
Step 1 - Installing PHP
Step 2 - Install Node.js
Step 3 - Install Bower and Gulp
Step 4 - Install Composer
Step 5 - Adding SSH Keys
Step 6 - Adding the Homestead box
Step 7 - Installing Homestead
Step 8 - Bring up the Homestead VM
Step 9 - Installing the Laravel Installer
Recap
OS X Yosemite
Yosemite (the version of OS X at the time of this writing) ships with PHP 5.5.14. So no worries there.
[snip]
Often, Node.js is already installed. You can check the version of npm to see if Node.js
is installed on your system.
Checking the npm version
$> npm --version
1.5.0-alpha-4
If it’s not installed, there are a couple options for installing it. You can use your package
manager to install it. With OS X you can install it with Homebrew. Or you can just go to
nodejs.org/download and download the correct version for your operating system.
[email protected]
Once installed, be sure to check the version to make sure node and npm are available in
your path.
Checking node and npm versions
~> node --version
v0.10.29
If you don’t see id_rsa and id_rsa.pub create them with the following command.
(Press [Enter] all the way through to use the defaults and set up the SSH key with no
pass phrase.)
Creating SSH Keys
~> ssh-keygen -t rsa -C "[email protected]"
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/Chuck/.ssh/id_rsa):
Created directory '/Users/Chuck/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
[snip]
In order to access homestead from any command prompt, add this path to your path
variable. This should be added to whatever the startup script is for your operating
system. Common startup files are: .bashrc, .bash_profile, .zshrc, etc.
Remember
You only need to initialize Homestead once on your machine
[snip]
Now the Homestead VM is running. If you exit the terminal window, Homestead is still
running. It’ll remain active until you issue a homestead halt command from a terminal
window.
Now you can log onto homestead with the homestead ssh command.
Shelling to homestead
~> homestead ssh
Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-11-generic x86_64)
* Documentation: https://help.ubuntu.com/
Since your path was already updated in Step 7.2 to contain composer’s bin directory,
the laravel command should already be accessible from the Console window. Verify
it by checking the version.
Checking the Laravel Version
~> laravel --version
Laravel Installer version 1.2.1
Congratulations!
You now have a virtual Ubuntu 64-bit machine, ready for developing your Laravel 5.1 web
applications.
Recap
This chapter contained a series steps to follow in order to get Laravel Homestead up
and running on your OS X or Linux machine. The good news is, these steps only have to
be performed once.
The next chapter, Using Homestead, contains information about using Homestead.
[email protected]
Chapter 5 - Homestead and Laravel Installer
This chapter explores the two composer tools previously installed: homestead and
laravel. A typical daily workflow is examined, as are the six steps to set up any new
Laravel 5.1 project.
Chapter Contents
The Homestead Tool
Overview of Common Homestead Commands
Examining Homestead.yaml
Adding Software to the Homestead VM
Daily Workflow
Six Steps to Starting a New Laravel 5.1 Project
Step 1 - Create the app skeleton
Step 2 - Configure the web server
Step 3 - Add the Host to Your Hosts File
Step 4 - NPM Local Installs
Step 5 - Create the app’s database
Step 6 - Testing in the Browser
Other Homestead Tips
Recap
Console Defined
Whenever you are prompted to do something from the console, context is important.
The homestead console means connecting to the Homestead VM via SSH. For Windows, this
means using PuTTY (explained in the chapter on setting up a Windows machine). With other
operating systems you can execute the homestead ssh command from within the terminal. Whenever
you see the $ prompt in this book you are in the homestead console.
The OS console means either the Windows command prompt or the terminal application you use.
(The % prompt in the book is used for your OS specific console.)
From the console of your host operating system, you can easily see what the valid
homestead commands are by typing the homestead command without any arguments.
Homestead Commands
% homestead
[email protected]
Laravel Homestead version 2.0.9
Usage:
[options] command [arguments]
Options:
--help -h Display this help message.
--quiet -q Do not output any message.
--verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal \
output, 2 for more verbose output and 3 for debug.
--version -V Display this application version.
--ansi Force ANSI output.
--no-ansi Disable ANSI output.
--no-interaction -n Do not ask any interactive question.
Available commands:
destroy Destroy the Homestead machine
edit Edit the Homestead.yaml file
halt Halt the Homestead machine
help Displays help for a command
init Create a stub Homestead.yaml file
list Lists commands
provision Re-provisions the Homestead machine
resume Resume the suspended Homestead machine
run Run commands through the Homestead machine via SSH
ssh Login to the Homestead machine via SSH
status Get the status of the Homestead machine
suspend Suspend the Homestead machine
up Start the Homestead machine
update Update the Homestead machine image
The main command you’ll use each day is the homestead up command to start the
Homestead Virtual Machine.
homestead up
Starts the Homestead Virtual Machine. It turns on the power to the VM. If you use
the provision option (homestead up --provision) then any new sites you’ve
added will be provisioned.
homestead halt
Stops the Homestead Virtual Machine. In other words, powering off.
homestead suspend
Suspends the Homestead Virtual Machine. It’s like hibernate.
homestead resume
Resumes the suspended Homestead Virtual Machine.
homestead edit
Edit the Homestead.yaml file. This launches whatever editor is associated with
[email protected]
YAML files on your operating system.
homestead status
See the current status of the Homestead Virtual Machine.
Examining Homestead.yaml
The configuration settings for Laravel Homestead are contained in the Homestead.yaml
file. This file is located in the .homestead directory of your Host OS’s home directory.
If you view the contents of this file, you’ll see what’s below.
Contents of Homestead.yaml
---
ip: "192.168.10.10"
memory: 2048
cpus: 1
authorize: ~/.ssh/id_rsa.pub
keys:
- ~/.ssh/id_rsa
folders:
- map: ~/Code
to: /home/vagrant/Code
sites:
- map: homestead.app
to: /home/vagrant/Code/Laravel/public
databases:
- homestead
variables:
- key: APP_ENV
value: local
ip
The internal IP used to access the machine.
memory
How much memory the VM will use.
cpus
The number of CPUs the VM will use.
authorize
This should point to your public SSH key.
[email protected]
keys
Your private SSH key.
folders
The shared folders. These are the directories in your Host Operating System and
where they will appear within the VM. For Windows the ~/Code equates to
something like C:\Users\YOU\Code. In OS X, this is /Users/YOU/Code. Under
Linux it’s usually something like /home/YOU/Code. Whenever you edit a file in
this directory tree on your host machine, it’s instantly available to the Homestead
Virtual Machine.
sites
A list of sites (paths each domain points to) that will be set up on the Homestead
Virtual Machine each time you provision.
databases
A list of database Homestead should automatically create.
variables
Variables to make available to the homestead environment.
A configuration note
The only change I usually make to the configuration is to change the list of databases to have one
database named xhomestead instead of homestead. This way if I forget create an app’s database
when creating a new Laravel application, an error occurs. (Otherwise, since the default database
for a new application is homestead, no error will occur and I’ll be using the homestead db
without realizing it.)
For now, don’t change any homestead configuration values except the databases setting
(and then, only if you want to.)
What Value
Hostname homestead
IP Address 192.168.10.10
Username vagrant
SU Password vagrant
Database Host 127.0.0.1
Database Port 33060
Database Username homestead
[email protected]
Database Password secret
1. Upgrade Ubuntu
2. Install with apt-get
For example, here’s how to install unzip, a handy utility for dealing with zip archives.
You may have to choose “Y” to continue. If prompted during the installation to pick a
configuration it’s generally best to go with the existing or default.
Daily Workflow
The daily workflow when working with homestead consists of three steps:
Step 1 - homestead up - Start the day by booting your Homestead Virtual Machine.
Step 2 - homestead ssh or PuTTY - SSH to the Homestead VM to access files directly
and execute artisan commands.
Step 3 - write beautiful code - In your favorite code editor, on your host operating
system, write code.
[email protected]
Optional 4th Step - homestead halt - When you are done for the day, you can
optionally power off the Homestead Virtual Machine with the halt command.
Let’s say we want to create a project called test.app and use test as the project folder.
The homestead environment makes this easy with the serve command.
Setting up a new virtual host in Homestead
~/Code$ serve test.app ~/Code/test/public
dos2unix: converting file /vagrant/scripts/serve.sh to Unix format ...
* Restarting nginx nginx [ OK
php5-fpm stop/waiting
php5-fpm start/running, process 2169
Even when you reboot the machine, this configuration file will be there.
[email protected]
You can skip this step if you know you will not use gulp.
Change to your project directory in your Host OS’s console and execute the following.
NPM Local Installs
~% cd Code/test
[email protected]
~% cd Code/test
~/Code/test% npm install
npm WARN package.json @ No repository field.
[snip]
This will install everything required by gulp locally into the node_modules directory of
your project.
Once the database is created, edit the .env file in your project’s root directory and
change the DB_NAME appropriately.
Change DB_NAME in .env
// Change the following line
DB_DATABASE=homestead
Easy. Now you’ll be able to migrate and create tables. This is covered in a later
chapter.
This is a handy place to add aliases, or functions, or even other environment variables.
[email protected]
Keep the Homestead VM up-to-date
As mentioned earlier, two commands will keep the Ubuntu operating system within the
Homestead Virtual Machine up to date.
Keeping Ubuntu Updated
$ sudo apt-get update
$ sudo apt-get upgrade
Recap
This chapter provided details on the homestead and laravel commands. And the Six
Steps to a New Laravel 5.1 Project were outlined.
Chapter Contents
Creating the l5beauty Project
Running PHPUnit
Laravel 5.1’s PHPUnit Configuration
Laravel 5.1 Crawler Methods and Properties
Laravel 5.1 PHPUnit Application methods and properties
Laravel 5.1 PHPUnit Assertions
Using Gulp for TDD
Creating a Markdown Service
Pulling in Markdown Packages
Creating the Markdown Test Class
Creating the Markdowner Service
A Few More Tests
Other Ways Test
phpspec
Unit Testing
Functional / Acceptance Testing
Behavior Driven Development
Recap
Back in your Host OS, add the following line to your hosts file.
Step 3 - Add l5beauty.app to Your Hosts File
192.168.10.10 l5beauty.app
From your Host OS, do the step to install the NPM packages locally.
Step 4 - NPM Local Installs
~% cd Code/l5beauty
~/Code/l5beauty% npm install
|
> [email protected] install /Users/chuck/Code/l5beauty/node_modules/laravel-\
elixir/node_modules/gulp-sass/node_modules/node-sass
> node scripts/install.js
[snip]
Go back within the Homestead VM and create the database for this project.
Step 5 - Create the app’s database
$ mysql --user=homestead --password=secret
mysql> create database l5beauty;
Query OK, 1 row affected (0.00 sec)
mysql> exit;
Bye
Running PHPUnit
Laravel 5.1 comes out of the box ready for testing. There’s even a very simple unit test
supplied to make sure a web request to the application returns the expected 200 HTTP
response.
To run PHPUnit, execute the phpunit command from the project’s root directory.
Running PHPUnit
~% cd Code/l5beauty
[email protected]
~% cd Code/l5beauty
~/Code/l5beauty% phpunit
PHPUnit 4.7.4 by Sebastian Bergmann and contributors.
OK (1 test, 2 assertions)
Examination of the phpunit.xml will show the tests reside within the tests directory.
There are two files located there.
The TestCase class provides additional Laravel 5.1 specific application methods and
properties to your unit tests. TestCase also provides a long list of additional assertion
methods and crawler type tests.
$response
The last response returned by the web application.
$currentUri
The current URL being viewed.
visit($uri)
(Fluent) Visit the given URI with a GET request.
get($uri, array $headers = [])
(Fluent) Fetch the given URI with a GET request, optionally passing headers.
post($uri, array $data = [], array $headers = [])
(Fluent) Make a POST request to the specified URI.
put($uri, array $data = [], array $headers = [])
(Fluent) Make a PUT request to the specified URI.
patch($uri, array $data = [], array $headers = [])
(Fluent) Make a PATCH request to the specified URI.
delete($uri, array $data = [], array $headers = [])
(Fluent) Make a DELETE request to the specified URI.
followRedirects()
(Fluent) Follow any redirects from latest response.
see($text, $negate = false)
(Fluent) Assert the given text appears (or doesn’t appear) on the page.
seeJson(array $data = null)
(Fluent) Assert the response contains JSON. If $data passed, also asserts the
JSON value exactly matches.
seeStatusCode($status)
(Fluent) Assert the response has the expected status code.
seePageIs($uri)
(Fluent) Assert current page matches given URI.
seeOnPage($uri) and landOn($uri)
(Fluent) Aliases to seePageIs()
[email protected]
click($name)
(Fluent) Click on a link with the given body, name or id.
type($text, $element)
(Fluent) Fill an input field with the given text.
check($element)
(Fluent) Check a checkbox on the page.
select($option, $element)
(Fluent) Select an option from a dropdown.
attach($absolutePath, $element)
(Fluent) Attach a file to a form field.
press($buttonText)
(Fluent) Submit a form using the button with the given text.
withoutMiddleware()
(Fluent) Disable middleware for the test.
dump()
Dump the content of the latest response.
$app
The instance of the Laravel 5.1 application.
$code
The latest code returned by artisan
refreshApplication()
Refreshes the application. Automatically called by the TestCase’s setup()
method.
call($method, $uri, $parameters = [], $cookies = [], $files = [],
$server = [], $content = null)
Calls the given URI and returns the response.
callSecure($method, $uri, $parameters = [], $cookies = [], $files =
[], $server = [], $content = null)
Calls the given HTTPS URI and returns the response.
action($method, $action, $wildcards = [], $parameters = [], $cookies
= [], $files = [], $server = [], $content = null)
Calls a controller action and returns the response.
route($method, $name, $routeParameters = [], $parameters = [],
$cookies = [], $files = [], $server = [], $content = null)
Calls a named route and returns the response.
instance($abstract, $object)
Register an instance of an object in the container.
expectsEvents($events)
Specify a list of events that should be fired for the given operation.
[email protected]
withoutEvents()
Mock the event dispatcher so all events are silenced.
expectsJobs($jobs)
Specify a list of jobs that should be dispatched for the given operation.
withSession(array $data)
Set the session to the given array.
session(array $data)
Starts session and sets the session values from the array.
flushSession()
Flushes the contents of the current session.
startSession()
Starts the application’s session.
actingAs($user)
(Fluent) Sets the currently logged in user for the application.
be($user)
Sets the currently logged in user for the application.
seeInDatabase($table, array $data, $connection = null)
(Fluent) Asserts a given where condition exists in the database.
notSeeInDatabase($table, $array $data, $connection = null)
(Fluent) Asserts a given where condition does not exist in the database.
missingFromDatabase($table, array $data, $connection = null)
(Fluent) Alias to notSeeInDatabase().
seed()
Seeds the database.
artisan($command, $parameters = [])
Executes the artisan command and returns the code.
Any of these methods or properties can be accessed within your test classes. The
provided ExampleTest.php file contains a line using $this->call(...) inside the
testBasicExample() method.
Laravel 5.1 includes Laravel Elixir which allows Gulp tasks to be built in easy ways.
Elixir adds an elegant syntax to gulp. Think of it this way … what Laravel is to PHP,
Elixir is to Gulp.
One of the most common uses of Gulp is to automate unit tests. We’ll follow the TDD
(Test Driven Development) process here and let Gulp automatically run our tests.
First, edit the gulpfile.js file in the l5beauty project’s root directory to match
what’s below.
Configuring Gulp to run PHPUnit Tests
var elixir = require('laravel-elixir');
elixir(function(mix) {
mix.phpUnit();
});
Here we call the elixir() function, passing a function. The mix object this function
receives is a stream on which multiple things can occur. You might want to build LESS
[email protected]
files into CSS files here, then concatenate those CSS files together, and then provide
versioning on the resulting concatenated files. All of those things can be specified by
using a fluent interface on the mix object.
Next, from the project root on your Host OS, run gulp to see what happens.
Running Gulp
~% cd Code/l5beauty
~/Code/l5beauty% gulp
[15:26:23] Using gulpfile ~/Code/l5beauty/gulpfile.js
[15:26:23] Starting 'default'...
[15:26:23] Starting 'phpunit'...
[15:26:25] Finished 'default' after 2.15 s
[15:26:25]
OK (1 test, 2 assertions)
[15:26:28] gulp-notify: [Green!]
[15:26:28] Finished 'phpunit' after 4.96 s
You should have received a notification, a popup alert of some sort, on your Host OS.
The notification should be green which indicates everything tested successfully.
PHPUnit Success
To have gulp go into automatic mode for unit tests, use the gulp tdd command in your
Host OS.
Running Gulp
~% cd Code/l5beauty
~/Code/l5beauty% gulp tdd
[15:29:49] Using gulpfile ~/Code/l5beauty/gulpfile.js
[email protected]
[15:29:49] Starting 'tdd'...
[15:29:49] Finished 'tdd' after 21 ms
The command will just hang there, watching for source file changes and running unit
tests when needed.
To see how this works, let’s break the existing unit test.
When you save this file, gulp will notice and run PHPUnit again. The will fail and you
will see a notice on your computer similar to the one below.
PHPUnit Failure
Change the line back to what it was before, save it, and again gulp will run PHPUnit.
This time you should receive a notice indicating you are “back to green”.
To illustrate testing, we’ll build a service to convert markdown text to HTML text using
TDD.
We’ll use the package created by Michel Fortin because there’s another package called
SmartyPants by the same author that converts quotation marks to the nice looking curly
quotes.
From your Host OS’s console do the following to pull in the packages.
Adding Markdown and SmartyPants
~/Code/l5beauty% composer require michelf/php-markdown
Using version ^1.5 for michelf/php-markdown
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing michelf/php-markdown (1.5.0)
Downloading: 100%
Did you notice that the specific version of the package was specified when requiring
SmartyPants? This is because at the time of this writing there isn’t a stable package that
can be pulled in automatically.
Now that Gulp is watching for changes and ready to run PHPUnit as soon as it detects
any, let’s create the test class.
[email protected]
In the tests directory, create a new folder named Services and a file called
MarkdownerTest.php.
Initial tests/Services/MarkdownerTest.php
1 <?php
2
3 class MarkdownerTest extends TestCase
4 {
5
6 protected $markdown;
7
8 public function setup()
9 {
10 $this->markdown = new \App\Services\Markdowner();
11 }
12
13 public function testSimpleParagraph()
14 {
15 $this->assertEquals(
16 "<p>test</p>\n",
17 $this->markdown->toHTML('test')
18 );
19 }
20 }
Line 6
Store an instance of the markdown object
Line 8
Have the setup() method create a new instance of the Markdowner class. (Yes,
this doesn’t exist yet.)
Line 13
A simple test we know should work.
You should have received a failure notice. (If you didn’t Ctrl+C out of Gulp and
restart it.)
Even though a notice appeared saying the test failed, sometimes it’s useful to look at the
console to determine what the failure was. In this case, it’s pretty obvious. The
App\Services\Markdowner class doesn’t exist.
Line 3
Don’t forget the namespace.
Lines 5 and 6
The classes we’ll be using.
Line 11
The toHTML() method which runs the text through the transformations.
Line 14
Notice we’re using the Markdown Extra version of the library.
Line 20
In case we want to later do our own transformations before anything else.
Line 25
Like preTransformText(), but this time if we later want to add our own final
transformations.
When you save this file, Gulp should notice and you will receive a “GREEN” alert
[email protected]
telling you everything worked as expected.
If you don’t receive the green alert, go back and check for typos in both the
App\Services\Markdowner and MarkdownerTest classes.
And so forth. Even the structure of our Markdowner class is flawed when it comes to
testing. To do pure unit testing on this class it should be structured such that instances of
both the MarkdownExtra and SmartyPants classes are injected into the constructor.
This way our unit test could inject mock objects and only verify the behavior of
MarkdownExtra and not the subordinate classes it calls.
But this isn’t a book on testing. In fact, this is the only chapter where testing occurs.
For now, we’ll leave the structure as is but add a few more tests.
Here we changed the test class to test multiple conversions at once and added three tests
in conversionsProvider(). Your tests should be green before moving forward.
Once the tests are green hit Ctrl+C in your Host OS console to stop Gulp.
phpspec
Besides PHPUnit, Laravel 5.1 also provides phpspec out of the box. This is another
popular PHP test suit with more of a focus on Behavior Driven Development.
The binary is in vendor/bin, thus you can call phpspec from your project’s root
directory.
The configuration file is in the project root. It’s named phpspec.yml.
To run phpspec from Gulp, Laravel Elixir provides the phpSpec() function you
can call on the mix object.
If you change your application’s namespace from App to something else, be sure to
[email protected]
update phpspec.yml accordingly.
Unit Testing
Although PHPUnit is the standard when it comes to PHP unit testing, there are other
packages you can use.
Enhance PHP - A unit testing framework with support for mocks and stubs.
SimpleTest - Another unit testing framework with mock objects.
SpecDD focuses on the technical aspects of your code. Laravel 5.1 includes phpspec
which is the standard for SpecDD.
StoryBDD emphasizes business or feature testing. Behat is the most popular StoryBDD
framework. Although, Codeception can also be used for StoryBDD.
Recap
The first thing we did in this chapter was creating a project named l5beauty. Then we
explored unit testing using PHPUnit within this project. Finally, we created a
Markdowner service class for the dual purposes of having something to test and to use
later to convert markdown text to HTML.
This was a pretty long chapter because testing is a large topic and a single chapter
cannot give it justice. But, as I’ve mentioned, testing is not the focus of this book. There
will be no more testing in subsequent chapters.
[email protected]
How about something quicker? In the next chapter we’ll create a blog in 10 minutes.
Chapter 7 - The 10 Minute Blog
In this chapter we’ll turn the l5beauty project into a blog, complete with test data.
Harnessing the power of Laravel 5.1 a blog can be created in less than 10 minutes. This
time is from start to finish, without spending time reviewing the detailed discussions
below. There’s not many bells or whistles, and no administration of the blog, but what
do you expect for less than 10 minutes of development time.
Chapter Contents
Pre-work before the 10 Minute Blog
0:00 to 2:30 - Creating the Posts table
2:30 to 5:00 - Seeding Posts with test data
5:00 to 5:30 - Creating configuration
5:30 to 7:30 - Creating the routes and controller
7:30 to 10:00 - Creating the views
Recap
All that is needed to create a blog in 10 minutes is a freshly created Laravel 5.1 project
with the database created. No need for migrations to have been run, although it won’t
hurt anything if they have been.
We’ll use the 10 Minute Blog as the starting point for a project which will grow into a
full-featured blogging application. This application won’t use the password reset
feature of Laravel 5.1 so delete the password resets migration as illustrated below.
Deleting the password resets migration
~/Code/l5beauty$ rm database/migrations/2014_10_12_100000_create_\
password_resets_table.php
1. It creates the model class App\Post class in the app directory of the project.
2. It creates a migration for the posts table. This migration will end up in the
database/migrations folder.
What is a Migration?
Think of migrations as version control for your database structure. Check out the documentation at
laravel.com. Migrations and seeding are are explained well there.
Edit the migration file just created in the database/migrations directory to match
what’s below.
The create_posts_table migration
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
/**
* Run the migrations.
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('slug')->unique();
$table->string('title');
$table->text('content');
$table->timestamps();
$table->timestamp('published_at')->index();
});
}
/**
* Reverse the migrations.
*/
public function down()
{
Schema::drop('posts');
}
}
We’ve added four additional columns here from the default ones provided when the
migration was created.
slug
We’ll convert the title of each post to a slug value and use that as part of the URI to
the post. This helps make the blog search engine friendly.
title
Every post needs a title.
content
Every post needs some content.
published_at
We want to control when the post is to be published.
Now that the migration is edited, run migrations to create the table.
Running the migrations
~/Code/l5beauty$ php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2015_03_22_210207_create_posts_table
Line 9
Tell Laravel 5.1 that the published_at column should be treated as a date.
Line 11
Whenever a value is assigned to the title property of this object, the
setTitleAttribute() method will be called to do it. We assign the property as
normal and if the row has not yet been saved to the database then we convert the
title to a slug value and assign the slug.
Line 1
We’re defining the factory for the AppPost class. The function gets an instance of a
Faker object.
Line 2
The factory function should return an array of column keys and values.
Line 3
For the title, we’ll use a random sentence between 3 and 10 words long.
Line 4
For the content, we’ll use between 3 and 6 random paragraphs.
Line 5
And for published_at, we’ll use a random time between a month ago and 3 days
from now.
Line 15
Call the seeder for the posts table
Line 20
Yeah, stuffing another class into this file. Sometimes rules are made to be broken.
Although, this will probably change in the future if we need to add more seeds.
Line 24
Truncate any existing records in the posts table.
Line 26
Call the Post model factory, 20 times, creating the resulting rows in the database.
Finally, to get the random data into the database, use artisan to seed it.
Seeding the database
~/Code/l5beauty$ php artisan db:seed
Seeded: PostTableSeeder
Once artisan db:seed is executed there will be 20 rows of data in the posts
database. You can use your favorite database client to view it, but I’m not going to here
because we’re on a tight schedule.
Create a new file in the config directory called blog.php with the contents below.
Contents of config/blog.php
1 <?php
2 return [
3 'title' => 'My Blog',
4 'posts_per_page' => 5
5 ];
Within our Laravel 5.1 application it’ll be easy to refer to these settings using the
config() helper function. For instance, config('blog.title') will return the title.
You could also return these values using the Config facade, for example
Config::get('blog.title').
The Timezone
This is a good time to update the config/app.php file if you wish to change your app’s timezone from
UTC to your local timezone.
Line 3
When a GET request to http://l5beauty.app/ is made, we return a redirect to
the user, redirecting them to http://l5beauty.app/blog.
Line 7
When a GET request to http://l5beauty.app/blog is made, the index()
method of the BlogController class will be called and the result returned to the
user. (The full class name of the controller is
App\Http\Controllers\BlogController.)
Line 8
When a GET request to http://l5beauty.app/blog/ANYTHING-HERE is made,
then the showPost() method of BlogController is called. The value
ANYTHING-HERE will be passed to the showPost() method as an argument.
(The --plain option is used to create an empty class without the standard RESTful
methods.)
Line 12
Start an Eloquent query where we want all posts not scheduled in the future.
Line 13
We’ll order the posts with the most recently published first.
Line 14
And we’ll tap into Eloquent’s powerful pagination feature to only return the
maximum number of posts per page we’ve set up in the configuration.
Line 16
Return the blog\index.blade.php view (yet to be created), passing it the
$posts variable.
Line 21
Fetch a single post from the slug and throw an exception if it’s not found.
Line 23
The ->withPost() method is another way to pass variables to the view. In this
case we’re passing the $post variable. It’ll be available to the view as $post.
You can check the application’s routing table with the following artisan command.
Showing the routes
~/Code/l5beauty$ php artisan route:list
+----------+-------------+----------------------------------------------+
| Method | URI | Action |
+----------+-------------+----------------------------------------------+
| GET|HEAD | / | Closure |
| GET|HEAD | blog | App\\Http\\Controllers\\BlogController@index |
| GET|HEAD | blog/{slug} | App\\Http\\Controllers\\BlogController@showPost |
+----------+-------------+----------------------------------------------+
Then create a new file, index.blade.php. Since this file ends with .blade.php it is a
Blade template, which is Laravel 5’s built in template engine. Make index.blade.php
match the content below.
Content of blog.index View
1 <html>
2 <head>
3 <title>{{ config('blog.title') }}</title>
4 <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"
5 rel="stylesheet">
6 </head>
7 <body>
8 <div class="container">
9 <h1>{{ config('blog.title') }}</h1>
10 <h5>Page {{ $posts->currentPage() }} of {{ $posts->lastPage() }}</h5>
11 <hr>
12 <ul>
13 @foreach ($posts as $post)
14 <li>
15 <a href="/blog/{{ $post->slug }}">{{ $post->title }}</a>
16 <em>({{ $post->published_at->format('M jS Y g:ia') }})</em>
17 <p>
18 {{ str_limit($post->content) }}
19 </p>
20 </li>
21 @endforeach
22 </ul>
23 <hr>
24 {!! $posts->render() !!}
25 </div>
26 </body>
27 </html>
Line 3
Here we use two curly braces to surround the output of whatever value is assigned
in the blog config file. Note that two curly braces will escape the output, making it
safe for HTML.
Line 4
We’ll use bootstrap to provide just a touch of styling
Line 10
The $posts variable is actually a pagination object which basically means it’s a
collection but also has methods for determining the page metrics.
Line 13
Here’s the blade syntax for a foreach loop
Line 16
Since we specified published_at is a date column, we can use the format()
method here to show the date just how we want it.
Line 24
Two interesting things about this line. First, notice how we use the a single curly
brace with double exclamation marks before and after a call to the render()
method. Unlike the double curly brace syntax, this does not escape the output. The
second thing is the render() method itself. We use this to display next and
previous links.
The last step in the 10 minute blog is to create the view to display posts. Create a new
file named post.blade.php in the resources/views/blog directory and match its
contents to what’s below.
Content of blog.post View
1 <html>
2 <head>
3 <title>{{ $post->title }}</title>
4 <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"
5 rel="stylesheet">
6 </head>
7 <body>
8 <div class="container">
9 <h1>{{ $post->title }}</h1>
10 <h5>{{ $post->published_at->format('M jS Y g:ia') }}</h5>
11 <hr>
12 {!! nl2br(e($post->content)) !!}
13 <hr>
14 <button class="btn btn-primary" onclick="history.go(-1)">
15 « Back
16 </button>
17 </div>
18 </body>
19 </html>
Congratulations
Point your browser to http://l5beauty.app and browse around a bit.
You have created a simple and clean blog in just a few minutes. Isn’t the power of Laravel 5.1
amazing?
Yes. I timed myself, working slowly and methodically, copying and pasting much of the
code. It took a total of 7 minutes and 8 seconds.
Recap
In this chapter we created a working blog with test data in less than 10 minutes.
‘Nuf said.
Chapter 8 - Starting the Admin Area
In this chapter we’ll continue building on the l5beauty project and start developing the
administration area.
Chapter Contents
Establishing the Routes
Middleware
Resource Controllers
Creating the Admin Controllers
Creating the Views
Creating an Admin Layout
Creating the Navbar Partial
Creating the Login Form
Creating the Errors Partial
Creating the Post Index View
Testing logging in and out
Creating the admin user
Fixing log out location
Fix default login location
Logging in and out
Cleaning up a couple unneeded files
Recap
Why Routes?
Laravel 5.1 needs a way to tie web requests to the code that handles the web request. This is called
routing. All routing within this book is defined in the app/Http/routes.php file.
Whenever a web request is made to a file that doesn’t exist within the public directory, Laravel 5.1
looks at the routes file to determine what to return. For instance, a web request to /css/app.css will
be handled by the web server (assuming public/css/app.css exists), but a request to /blog/my-
welcome-page doesn’t exist in the public directory and Laravel 5.1 tries to find a matching route to
execute.
Update the app/Http/routes.php to match what’s below.
Adding Admin Routes
1 <?php
2
3 // Blog pages
4 get('/', function () {
5 return redirect('/blog');
6 });
7 get('blog', 'BlogController@index');
8 get('blog/{slug}', 'BlogController@showPost');
9
10 // Admin area
11 get('admin', function () {
12 return redirect('/admin/post');
13 });
14 $router->group([
15 'namespace' => 'Admin',
16 'middleware' => 'auth',
17 ], function () {
18 resource('admin/post', 'PostController');
19 resource('admin/tag', 'TagController');
20 get('admin/upload', 'UploadController@index');
21 });
22
23 // Logging in and out
24 get('/auth/login', 'Auth\AuthController@getLogin');
25 post('/auth/login', 'Auth\AuthController@postLogin');
26 get('/auth/logout', 'Auth\AuthController@getLogout');
Line 11 - 13
Redirect requests to /admin to the /admin/post page
Line 14 - 16
Start a routing group using the namespace Admin (which expands out to the
App\Http\Controllers\Admin namespace) and force the auth middleware to be
active. (See the section below on Middleware).
Line 18 - 19
Within the route group, add two Resource Controllers (see below).
Line 20
Add a route so whenever a GET request is made to /admin/upload the index()
method of App\Http\Controllers\Admin\UploadController will be called.
Line 24 - 26
Here we add routes for logging in and logging out.
Once you save the routes.php file, the next step will be to create any missing
controllers. But before we get to that, let’s explore a couple concepts: Middleware and
Resource Controllers.
Tip - Read the Docs
Check out the Laravel 5.1 documentation at laravel.com for more information about routing.
Middleware
If you used Laravel 4 you may recall the concept of filters. The middleware in Laravel
5.1 provides the functionality that filters did in Laravel 4, but they are more aptly
named.
Abstract Flow of Request to Response
HTTP request received
-> Check for maintenance mode*
-> Start session*
-> Get response from controller action
-> Encrypt Cookies*
-> Add cookies to response*
Return response to user
In this simplified flow from a request to the response, the items with the asterisk(*) at
the end are considered middleware.
The file app/Http/Kernel.php contains a list of the middleware for your application.
When viewing this file note the $middleware property contains the global middleware
(that is, middleware always executed) and the $routeMiddleware property contains a
list of middleware that can be applied at the route level
Auth Middleware
Let’s say a route has the auth middleware active, the flow would look similar to what’s
below.
Request to Response with auth middleware
HTTP request received
-> Global "before" middleware
-> (auth) If not logged on then return redirect to logon form
-> Get response from controller action
-> Global "after" middleware
Return response to user
Thus, if the auth middleware detects the user is not logged on, the controller action is
never executed. Instead, the user is redirected to the logon page.
More About Middleware
The best place to learn more about Laravel 5.1 Middleware is the official Laravel 5.1 documentation.
Resource Controllers
Earlier, in our routing file, we specified a resourceful route using the resource()
function. This single declaration creates multiple routes, names those routes, and points
them to a series of expected action methods on the controller.
Action
HTTP Verb Path Route Name
Method
GET admin/post index() admin.post.index
GET admin/post/create create() admin.post.create
POST admin/post store() admin.post.store
GET admin/post/{post} show() admin.post.show
GET admin/post/{post}/edit edit() admin.post.edit
PUT/PATCH admin/post/{post} update() admin.post.update
DELETE admin/post/{post} destroy() admin.post.destroy
Upon completion of the above three artisan commands, there will be three new
controller files in the app/Http/Controllers/Admin directory.
NOTE: The --plain option was only used on the upload controller. The
PostController.php and TagController.php files will be created with all the
resource methods stubbed out.
Update the index() method within the PostController class to match what’s below.
PostController’s index() method
1 /**
2 * Display a listing of the posts.
3 *
4 * @return Response
5 */
6 public function index()
7 {
8 return view('admin.post.index');
9 }
The index() method simply returns the view. We’ll build it shortly.
This snippet of code may look familiar. It’s the basic template for the Boostrap CSS
Framework. There’re just a few additional hooks in it.
<title>{{ config('blog.title') }} Admin</title>
Here we set the title of the page to the blog’s title with the word “Admin” added
on.
@yield('styles')
This Blade directive will output the section (if there is one) named styles. It
allows us to put some extra CSS at the top of the template on a page-by-page basis.
@include('admin.partials.navbar')
Here we’re including another blade template (which does not yet exist).
@yield('content')
This will output the main content of the page.
@yield('scripts')
Here’s where additional JavaScript can be output.
Escaped or Unescaped?
Blade templates provide two ways to output PHP expressions. You can wrap the PHP code in double
curly braces {{ 'like this' }} and the value of the PHP expression will be output at that point in
the template, but the output will be escaped–meaning HTML entities will be encoded. If you want the
value to not be escaped, then wrap the expression in a curly brace and double exclamation {!! 'like
this' !!}.
Create the new directory resources/admin/partials and within that directory create
a navbar.blade.php file with the following content.
Initial Content of admin.partials.navbar View
1 <ul class="nav navbar-nav">
2 <li><a href="/">Blog Home</a></li>
3 @if (Auth::check())
4 <li @if (Request::is('admin/post*')) class="active" @endif>
5 <a href="/admin/post">Posts</a>
6 </li>
7 <li @if (Request::is('admin/tag*')) class="active" @endif>
8 <a href="/admin/tag">Tags</a>
9 </li>
10 <li @if (Request::is('admin/upload*')) class="active" @endif>
11 <a href="/admin/upload">Uploads</a>
12 </li>
13 @endif
14 </ul>
15
16 <ul class="nav navbar-nav navbar-right">
17 @if (Auth::guest())
18 <li><a href="/auth/login">Login</a></li>
19 @else
20 <li class="dropdown">
21 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
22 aria-expanded="false">{{ Auth::user()->name }}
23 <span class="caret"></span>
24 </a>
25 <ul class="dropdown-menu" role="menu">
26 <li><a href="/auth/logout">Logout</a></li>
27 </ul>
28 </li>
29 @endif
30 </ul>
If a user is logged in, then this template displays a menu for Posts, Tags, and Uploads
on the left and a Logout on the right.
If there is no user logged in then only a Login link is displayed on the right.
The $errors variable is available to every view. It will contain a collection of errors,
if there are any. So here we just check if there’s any errors and output them.
This is just a temporary view at this point. In a future chapter we’ll finish it.
1. The admin URI was matched (in app/Http/routes.php) and the closure was
executed which redirected your browser to /admin/post.
2. The route named admin.post.index (see artisan route:list) was matched from
the /admin/post URI, but the auth middleware determined no user was logged in
and did another redirect, this time to /auth/login.
3. The /auth/login URI was matched and the getLogin() method of the
Auth\AuthController was executed. (This method actually resides in the
AuthenticateUsers trait.)
4. The getLogin() method returned the contents of the auth.login view, which is the
login screen you should have seen.
Now, you could try logging in, but at this point it won’t do much good since we haven’t
added a user to the system yet.
Now you’ll be able to log in with this user you just created. Go back to the login page
on the browser and give it a try.
Why is this?
// Change it to
return new RedirectResponse('/admin/post');
This path will redirect when the guest middleware is used on a route.
Try it.
Recap
Quite a bit was accomplished in this chapter. The routes were established for most of
the administration area and there was a brief discussion about Middleware and
Resource Controllers in Laravel 5. The logging in and out process was customized
specifically for our administration area.
All in all, we now have a strong base to build the admin area upon. In the next chapter
we’ll start making the admin area useful and add Tags to our blog system.
Chapter 9 - Using Bower
In this chapter we’ll work on some of the supporting software the administration area
will be built on. Namely, how the assets are pulled in and which assets are used. The
build system will use bower and gulp to automatically download and combine jQuery,
Bootstrap, Font Awesome, and DataTables from the Internet.
Chapter Contents
Stealing Code
Installing Bower
Pulling in Bootstrap
Creating admin.less
Gulping Bootstrap
Running gulp
Updating the admin layout
Adding FontAwesome and DataTables
Recap
Stealing Code
One of the fastest ways to develop web applications is to leverage the work of others.
In other words, stealing their code.
For instance, look at Twitter Bootstrap’s license. It states anyone is allowed to use, free
of charge, the Bootstrap framework.
Point is, today’s web sites consist of many things: frameworks, utilities, libraries,
assets, and so forth. It’d take forever to create a decent application if every component
had to be created from scratch.
We’ll use Bower to manage fetching and installing many of the packages we’ll steal.
Installing Bower
Where Should Bower Run?
Bower’s one of those utilities you can run either from your Host OS or from the Homestead VM. In
this book I’ll use the Host OS, but if you have any issues just use the Homestead VM.
Decide where you’ll run bower and consistently only run it from there.
Since bower runs with NodeJS, you need to install it globally first. If you haven’t
already installed bower globally, follow the instructions below.
Installing Bower Globally
~% npm install -g bower
/usr/local/bin/bower -> /usr/local/lib/node_modules/bower/bin/bower
[email protected] /usr/local/lib/node_modules/bower
├── [email protected]
[snip]
(Note you may need to use sudo or run the above command from a Windows
Command Prompt with Administration privileges.)
Next create a .bowerrc file in the root of the l5beauty project. This is optional. What
we’re doing here is telling bower to stash anything it downloads into the vendor
directory. If you skip this step then bower will create a directory named
bower_components in your root directory and store items there.
Contents of .bowerrc
{
"directory": "vendor/bower_dl"
}
Finally, create the bower.json file in the project root. This is will be where bower
keeps track of packages to maintain. It’s like composer.json, but for bower.
Contents bower.json file
{
"name": "l5beauty",
"description": "My awesome blog",
"ignore": [
"**/.*",
"node_modules",
"vendor/bower_dl",
"test",
"tests"
]
}
Pulling in Bootstrap
Now that bower’s set up to use, let’s use it to pull some assets off from the web which
we want to be part of the administration area of l5beauty.
Since we’re currently using Bootstrap, we’ll start with that (and jQuery).
Installing Jquery and Bootstrap
~/Code/l5beauty% bower install jquery bootstrap --save
bower jquery#* not-cached git://github.com/jquery/jquery.git#*
bower jquery#* resolve git://github.com/jquery/jquery.git#*
bower bootstrap#* not-cached git://github.com/twbs/bootstrap.git#*
bower bootstrap#* resolve git://github.com/twbs/bootstrap.git#*
bower jquery#* download https://github.com/.../2.1.4.tar.gz
bower bootstrap#* download https://github.com/.../v3.3.5.tar.gz
bower jquery#* extract archive.tar.gz
bower bootstrap#* extract archive.tar.gz
bower jquery#* resolved git://github.com/jquery/jquery.git#2.1.4
bower bootstrap#* resolved git://github.com/....git#3.3.5
bower jquery#>= 1.9.1 install jquery#2.1.4
bower bootstrap#~3.3.5 install bootstrap#3.3.5
jquery#2.1.4 vendor/bower_dl/jquery
bootstrap#3.3.5 vendor/bower_dl/bootstrap
└── jquery#2.1.4
Now if you look at bower.json you’ll notice two dependencies were added. One for
jquery and one for bootstrap.
New dependencies in bower.json
{
...
"dependencies": {
"jquery": "~2.1.4",
"bootstrap": "~3.3.5"
}
}
Creating admin.less
We’ll use gulp to compile Bootstrap’s less file for the administration pages. Create
admin.less in the resources/assets/less directory with the following content.
Content of admin.less
@import "bootstrap/bootstrap";
@import "//fonts.googleapis.com/css?family=Roboto:400,300";
@btn-font-weight: 300;
@font-family-sans-serif: "Roboto", Helvetica, Arial, sans-serif;
First we import the bootstrap.less file (which doesn’t exist yet, we’ll copy it to the
correct location with gulp shortly.) Then we import the font we’ll be using and add a
few small tweaks to the CSS.
Gulping Bootstrap
Now that bower is pulling in the latest jQuery and Bootstrap, Gulp can be used to merge
it into your project.
First of all we added a copyfiles gulp task. The reason we’re doing this is twofold:
1. Gulp runs asynchronously. When files are copied they may still be being copied
when they’re combined with mix.less() or mix.scripts().
2. Copying the files only needs to occur after a bower update.
The scripts() function will combine jquery.js and bootstrap.js into one file,
which we’re naming public/assets/js/admin.js. And the less() function will
process the admin.less file we just created, pulling in bootstrap.
phpUnit() was removed from gulpfile.js because we’re not worried about testing
in this book.
Running gulp
Since bower has been updated, run gulp copyfiles followed by gulp without any
argument.
Running gulp twice
~/Code/l5beauty% gulp copyfiles
[11:54:39] Using gulpfile ~/Projects/l5beauty/gulpfile.js
[11:54:39] Starting 'copyfiles'...
[11:54:39] Finished 'copyfiles' after 19 ms
~/Code/l5beauty% gulp
[11:55:33] Using gulpfile ~/Projects/l5beauty/gulpfile.js
[11:55:33] Starting 'default'...
[11:55:33] Starting 'scripts'...
[11:55:33] Merging: resources/assets/js/jquery.js,
resources/assets/js/bootstrap.js
[11:55:34] Finished 'default' after 106 ms
[11:55:34] Finished 'scripts' after 228 ms
[11:55:34] Starting 'less'...
[11:55:34] Running Less: resources/assets/less/admin.less
[11:55:35] gulp-notify: [Laravel Elixir] Less Compiled!
[11:55:35] Finished 'less' after 829 ms
The only changes were the CSS link in the header and replacing the two script lines at
the bottom with the new, combined admin.js.
Next edit gulpfile.js to copy the needed assets into our project.
Updated gulpfile.js
1 var gulp = require('gulp');
2 var rename = require('gulp-rename');
3 var elixir = require('laravel-elixir');
4
5 /**
6 * Copy any needed files.
7 *
8 * Do a 'gulp copyfiles' after bower updates
9 */
10 gulp.task("copyfiles", function() {
11
12 // Copy jQuery, Bootstrap, and FontAwesome
13 gulp.src("vendor/bower_dl/jquery/dist/jquery.js")
14 .pipe(gulp.dest("resources/assets/js/"));
15
16 gulp.src("vendor/bower_dl/bootstrap/less/**")
17 .pipe(gulp.dest("resources/assets/less/bootstrap"));
18
19 gulp.src("vendor/bower_dl/bootstrap/dist/js/bootstrap.js")
20 .pipe(gulp.dest("resources/assets/js/"));
21
22 gulp.src("vendor/bower_dl/bootstrap/dist/fonts/**")
23 .pipe(gulp.dest("public/assets/fonts"));
24
25 gulp.src("vendor/bower_dl/fontawesome/less/**")
26 .pipe(gulp.dest("resources/assets/less/fontawesome"));
27
28 gulp.src("vendor/bower_dl/fontawesome/fonts/**")
29 .pipe(gulp.dest("public/assets/fonts"));
30
31 // Copy datatables
32 var dtDir = 'vendor/bower_dl/datatables-plugins/integration/';
33
34 gulp.src("vendor/bower_dl/datatables/media/js/jquery.dataTables.js")
35 .pipe(gulp.dest('resources/assets/js/'));
36
37 gulp.src(dtDir + 'bootstrap/3/dataTables.bootstrap.css')
38 .pipe(rename('dataTables.bootstrap.less'))
39 .pipe(gulp.dest('resources/assets/less/others/'));
40
41 gulp.src(dtDir + 'bootstrap/3/dataTables.bootstrap.js')
42 .pipe(gulp.dest('resources/assets/js/'));
43
44 });
45
46 /**
47 * Default gulp is to run this elixir stuff
48 */
49 elixir(function(mix) {
50
51 // Combine scripts
52 mix.scripts([
53 'js/jquery.js',
54 'js/bootstrap.js',
55 'js/jquery.dataTables.js',
56 'js/dataTables.bootstrap.js'
57 ],
58 'public/assets/js/admin.js',
59 'resources/assets'
60 );
61
62 // Compile Less
63 mix.less('admin.less', 'public/assets/css/admin.css');
64 });
Since we’re renaming a file using gulp instead of elixir (for that copyfiles task), we
need to pull in the gulp-rename module.
Pulling in gulp-rename
%/Code/l5beauty% npm install gulp-rename --save
[email protected] node_modules/gulp-rename
Now run gulp twice. Once to copy the files (gulp copyfiles) because we added
bower assets and once to process and combine the assets.
Running gulp twice
~/Code/l5beauty% gulp copyfiles
[12:52:02] Using gulpfile ~/Projects/l5beauty/gulpfile.js
[12:52:02] Starting 'copyfiles'...
[12:52:02] Finished 'copyfiles' after 31 ms
~/Code/l5beauty% gulp
[12:52:26] Using gulpfile ~/Projects/l5beauty/gulpfile.js
[12:52:26] Starting 'default'...
[12:52:26] Starting 'scripts'...
[12:52:26] Merging: resources/assets/js/jquery.js,
resources/assets/js/bootstrap.js, resources/assets/js/jquery.dataTables.js,
resources/assets/js/dataTables.bootstrap.js
[12:52:26] Finished 'default' after 107 ms
[12:52:26] Finished 'scripts' after 401 ms
[12:52:26] Starting 'less'...
[12:52:26] Running Less: resources/assets/less/admin.less
[12:52:30] gulp-notify: [Laravel Elixir] Less Compiled!
[12:52:30] Finished 'less' after 4.19 s
Recap
In this chapter we used bower to download jQuery, Bootstrap, Font Awesome, and
DataTables from the Internet. Then gulp was used to combine everything into a single
CSS file (admin.css) and a single JavaScript file (admin.js).
These pieces will come together in the next chapter when we implement a tagging
system for the l5beauty project.
Chapter 10 - Blog Tags
The basic blog built in the Chapter 7 - The 10 Minute Blog chapter wasn’t very fancy.
Most blogging platforms allow blog posts to be categorized or “tagged” in different
ways. In this chapter we’ll develop a tagging system for the l5beauty project.
Chapter Contents
Creating the Model and Migrations
Editing tags migration
Editing post tag pivot migration
Running Migrations
Implementing admin.tag.index
Implementing TagController index
Adding the admin.tag.index view
The success and errors partial
The empty list
Implementing admin.tag.create
Implementing TagController create
Adding the admin.tag.create view
Adding the admin.tag._form partial
The screen
Implementing admin.tag.store
Adding TagCreateRequest
Implementing TagController store
Implementing admin.tag.edit
Implementing TagController edit
Adding admin.tag.edit view
The screen
Implementing admin.tag.update
Adding TagUpdateRequest
Implementing TagController update
Finishing the Tag System
Implementing TagController destroy
Removing admin.tag.show
Recap
This creates the model in the file app/Tag.php. Because we used the --migration
option the make:model command will automatically create a migration for the database.
There will be a many-to-many relationship between Tags and Posts. Follow the
command below to create a migration for the pivot table to store this relationship.
Creating the Post and Tag Pivot Migration
~/Code/l5beauty$ php artisan make:migration --create=post_tag_pivot \
create_post_tag_pivot
Created Migration: 2015_04_07_044734_create_post_tag_pivot
page_image
The look and feel of the finished blog will allow a large image at the top of each
page. If we’re viewing a page of posts for a particular tag it’ll be nice to show a
different image. This column allows the image to be set.
meta_description
A description to embed in a meta tag for search engines.
layout
The blog will eventually use layouts
reverse_directions
Normally blogs list posts in the order of publication with the most recent on top.
This flag will flip that order.
Running Migrations
Run the migrations to create the two new tables.
Running the migrations
~/Code/l5beauty$ php artisan migrate
Migrated: 2015_04_07_044634_create_tags_table
Migrated: 2015_04_07_044734_create_post_tag_pivot
Implementing admin.tag.index
Back in Chapter 8 - Starting the Admin Area we created the TagController and
routes to it. Implementing the admin.tag.index route will be fairly trivial. We’ll
update the index() method in the controller and provide a view.
Easy, huh? All we’re doing here is returning a view and passing it a variable that will
be called $tags containing all the tags from the database. The Tag is a model so we can
use the all() method to return a collection of all tags on file.
Adding the admin.tag.index View
Create a new tag directory in the resources/views/admin folder and then create the
index.blade.php file with the following contents.
This template is easy to follow. We’re using the admin layout. At the top is the page title
and a button to create new tags. Then a table outputting all the tags we have on file (that
$tags variable we passed to the view). And finally, there’s a bit of JavaScript at the
bottom to convert the table to DataTables.
We’ll use flash data to store success whenever we want this type of feedback to the
user.
Implementing admin.tag.create
Next we’ll implement the screen that allows a new tag to be created. This way when
you click on the [New Tag] button you’ll have a form to fill out.
If the first case we want the form to be populated with default values. In the second case
we want to pass any data that was previously input back to the form. This is
accomplished with the old() function which returns the previous input or a default.
The view is returned with $data. Thus each column will be a variable in the view. For
example the $layout variable will exist in the view (and it’ll have a default value of
‘blog.layouts.index’).
This is a simple to follow Blade template. There’s no navigation back to the Tags
Listing page, but it’s easy enough to use the Tags link in the navigation bar whenever
the user needs to get back to the Tags Listing page.
The fields for the form itself will be in the admin.tag._form view because we can use
the same form for both creation and editing.
The screen
In your browser navigate to the screen we just created by clicking the [New Tag] button
on the Tags Listing screen.
Implementing admin.tag.store
Now that we have a form for creating the tag, we need to build the code that will be
executed when this form is filled out and submitted.
Adding TagCreateRequest
One of the great features of Laravel 5.1 are Form Requests. These are classes which
contain validation logic for a particular form.
Create the new TagCreateRequest class with the artisan command below.
Creating the TagCreateRequest class
vagrant@homestead:~/Code/l5beauty$ php artisan make:request TagCreateRequest
Request created successfully.
Content of TagCreateRequest.php
1 <?php
2 namespace App\Http\Requests;
3
4 class TagCreateRequest extends Request
5 {
6
7 /**
8 * Determine if the user is authorized to make this request.
9 *
10 * @return bool
11 */
12 public function authorize()
13 {
14 return true;
15 }
16
17 /**
18 * Get the validation rules that apply to the request.
19 *
20 * @return array
21 */
22 public function rules()
23 {
24 return [
25 'tag' => 'required|unique:tags,tag',
26 'title' => 'required',
27 'subtitle' => 'required',
28 'layout' => 'required',
29 ];
30 }
31 }
There’s many validation rules available and you can even create your own. See the
Laravel Documentation for details.
The store() method will then create and save the new Tag. Finally, it redirects back to
the Tags Listing page, flashing a “success” message as it does.
Try adding a couple tags. You’ll be able to delete these tags later. Just get some data
into the system to make sure it’s working.
Implementing admin.tag.edit
Next will edit the route named admin.tag.edit to present the form for editing a tag.
Nothing magical is happening here. This function loads a Tag object based on the id,
builds the associative array $data with either values from this object or the old input
values, and returns a view passing the values to it.
Line 1
As always, we’re using the admin layout
Lines 19 and 20
And we use the partials to provide error or success feedback to the user.
Lines 22 - 25
Starting a form to save any edits. The $id is used so we’re updating the correct
tag. Hidden values are used for the CSRF Token and to fake a PUT method.
Line 35
Using the same basic form as we used for the create.
Lines 43 - 47
In addition to the standard “Save” button, here’s a “Delete” button which will pop
up a Modal dialog.
Lines 60 - 88
Here’s the Modal dialog we’re popping up. If the user chooses “Yes” then a POST
is made which fakes a DELETE method.
The screen
Now if you click the [edit] button in your browser one one of the tags added to the
system (from the Tags Listing) screen, you’ll see the edit form. If you click the
[Delete] button from the edit form you’ll see a screen similar to the one below.
Of course, the tag cannot yet be deleted or updated, we’ll do that next.
Implementing admin.tag.update
In the same way the admin.tag.store route processes the result of the
admin.tag.create form, the admin.tag.update route will process the result of the
admin.tag.edit form.
Adding TagUpdateRequest
To create the skeleton of the Form Request to handle the update, use the artisan
command as illustrated below.
Creating the TagUpdateRequest class
~/Code/l5beauty$ php artisan make:request TagUpdateRequest
Request created successfully.
Now edit the TagUpdateRequest.php file to match what’s below. (This file is in the
app/Http/Requests directory.)
Content of TagUpdateRequest.php
1 <?php
2 namespace App\Http\Requests;
3
4 class TagUpdateRequest extends Request
5 {
6
7 /**
8 * Determine if the user is authorized to make this request.
9 *
10 * @return bool
11 */
12 public function authorize()
13 {
14 return true;
15 }
16
17 /**
18 * Get the validation rules that apply to the request.
19 *
20 * @return array
21 */
22 public function rules()
23 {
24 return [
25 'title' => 'required',
26 'subtitle' => 'required',
27 'layout' => 'required',
28 ];
29 }
30 }
This request class is almost exactly the same as the TagCreateRequest we created
earlier in this chapter.
Implementing TagController update
Update the TagController.php file to match what’s below.
Updates to TagController for update
1 // Add the new use statement near the top
2 use App\Http\Requests\TagUpdateRequest;
3
4 // Replace the update() method with the following
5 /**
6 * Update the tag in storage
7 *
8 * @param TagUpdateRequest $request
9 * @param int $id
10 * @return Response
11 */
12 public function update(TagUpdateRequest $request, $id)
13 {
14 $tag = Tag::findOrFail($id);
15
16 foreach (array_keys(array_except($this->fields, ['tag'])) as $field) {
17 $tag->$field = $request->get($field);
18 }
19 $tag->save();
20
21 return redirect("/admin/tag/$id/edit")
22 ->withSuccess("Changes saved.");
23 }
Now you should be able to edit a tag and click [Save Changes] button successfully.
The two things left are implementing the destroy() method and a bit of cleanup.
No magic here. Loads the tag, deletes it, and returns to the Tags Listing page with a
success message.
Removing admin.tag.show
You may have noticed there’s one route that hasn’t been used. The admin.tag.show
route was set up to show a tag. Often a show is used when a user doesn’t have editing
privileges, but does have viewing privileges.
The admin.tag.show route isn’t needed in our system. So let’s remove it.
This addition tells the router to set up all the resource routes except for the show route.
Congratulations
Now the l5beauty blog administration has all the functionality in place to create, update, and delete
tags from your system.
Recap
This was a fairly long chapter, but in it we fully implemented the administration side of
our tagging system. This included everything from setting up the Tag model, creating
database migration, finishing the TagController class, using Form Requests, and
creating the needed Blade template views.
And, don’t forget, the resulting interface was simple and clean.
In the next chapter we’ll add the “Upload” feature to the administration.
Chapter 11 - Upload Manager
In this chapter we’ll create an Upload Manager for the blog administration. First, the
local file system will be used to store any uploaded files. Then, we’ll change the
configuration to allow files to be stored on Amazon’s S3 cloud storage.
Chapter Contents
Configuring the File System
Adding a Helpers File
Creating an Upload Manager Service
Detecting Mime Types
Creating the UploadsManager class
Implementing UploadController index
Creating the index method
Creating the index view
The Upload Manager Screen
Finishing the Upload Manager
Updating the routes
Adding all the Modal Dialogs
Adding the Request classes
Finishing UploadController
Finishing the UploadsManager Service class
Setting Up Your S3 Account
1. Log in (or sign up) with Amazon
2. Get a Web Services Account
3. Create a S3 Bucket
4. Create an Access Key
Configuring L5Beauty to Use S3
Installing an Additional Package
Test The Upload Manager
Fixing Bucket Permissions
Recap
With the above changes you’ll be able to access the file system used with
config('blog.uploads.storage'). The config('blog.uploads.webpath') will
be the root of our storage on the web.
The first thing changed was the root of the local storage. This changed to the
public/uploads directory just created a moment ago. Then each of the configuration
settings for the Amazon S3 storage driver changed to pull the values from the
environment. This is so we can change them later in our .env file.
Create a new file in the app directory named helpers.php. Populate this file with the
code below.
Content of helpers.php
1 <?php
2
3 /**
4 * Return sizes readable by humans
5 */
6 function human_filesize($bytes, $decimals = 2)
7 {
8 $size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
9 $factor = floor((strlen($bytes) - 1) / 3);
10
11 return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) .
12 @$size[$factor];
13 }
14
15 /**
16 * Is the mime type an image
17 */
18 function is_image($mimeType)
19 {
20 return starts_with($mimeType, 'image/');
21 }
The human_filesize() function returns a file size which is easier to read than just a
count of bytes. The is_image() function returns true if the mime type is an image.
The files array within the autoload section specifies any files to always load. Do a
composer dumpauto to set up the autoloader correctly.
Composer dumpauto
vagrant@homestead:~/Code/l5beauty$ composer dumpauto
Generating autoload files
Now any functions in helpers.php will always be loaded and available to the
l5beauty application.
PHP has the function mime_content_type(), but it’s deprecated. The PHP Fileinfo
functions can detect the mime type, but in Windows a DLL must be copied to make
fileinfo functions work. Let’s use a different solution.
Searching packagist.org for “mime” shows a package by dflydev which looks good.
We’ll use this package in the UploadsManager class to detect a file’s mime type.
__construct()
Inject dependencies. Use the disk defined in the blog configuration and the class
from the dflydev/apache-mime-types package required earlier.
folderInfo()
Here’s the main method that returns everything needed about the contents of a
folder.
Comments within the code explain the rest of the methods well enough you’ll be able to
follow what’s going on.
The constructor injects the UploadsManager dependency and the index() method
simply returns the view with data returned from the folderInfo() method.
You probably noticed the $folder is taken from the request. Yes, we’ll just use a query
argument to handle changing folders.
Did you notice the inclusion of the admin.upload._modals view toward the end? I
broke out the modal dialogs into a separate template.
Nice and clean. Now let’s implement all the modal dialogs and the functionality behind
them.
Changes to routes.php
1 // After the line that reads
2 get('admin/upload', 'UploadController@index');
3
4 // Add the following routes
5 post('admin/upload/file', 'UploadController@uploadFile');
6 delete('admin/upload/file', 'UploadController@deleteFile');
7 post('admin/upload/folder', 'UploadController@createFolder');
8 delete('admin/upload/folder', 'UploadController@deleteFolder');
There’s five different modal popup boxes in that file. Each one will do the appropriate
POST or DELETE to the routes just set up.
You can manually create these files or use the artisan make:request ClassName
command to create a blank file in the app/Http/Requests directory.
Then the Form Request class to handle validating requests to create new folders.
Content of UploadNewFolderRequest.php
1 <?php
2 namespace App\Http\Requests;
3
4 class UploadNewFolderRequest extends Request
5 {
6 /**
7 * Determine if the user is authorized to make this request.
8 *
9 * @return bool
10 */
11 public function authorize()
12 {
13 return true;
14 }
15
16 /**
17 * Get the validation rules that apply to the request.
18 *
19 * @return array
20 */
21 public function rules()
22 {
23 return [
24 'folder' => 'required',
25 'new_folder' => 'required',
26 ];
27 }
28 }
Again, these are very simple classes that validate the input on construction.
Finishing UploadController
Update the UploadController.php file as instructed below.
Updates to UploadController
1 <?php
2 // Add the following 3 lines at the top, with the use statements
3 use App\Http\Requests\UploadFileRequest;
4 use App\Http\Requests\UploadNewFolderRequest;
5 use Illuminate\Support\Facades\File;
6
7 // Add the following 4 methods to the UploadControllerClass
8 /**
9 * Create a new folder
10 */
11 public function createFolder(UploadNewFolderRequest $request)
12 {
13 $new_folder = $request->get('new_folder');
14 $folder = $request->get('folder').'/'.$new_folder;
15
16 $result = $this->manager->createDirectory($folder);
17
18 if ($result === true) {
19 return redirect()
20 ->back()
21 ->withSuccess("Folder '$new_folder' created.");
22 }
23
24 $error = $result ? : "An error occurred creating directory.";
25 return redirect()
26 ->back()
27 ->withErrors([$error]);
28 }
29
30 /**
31 * Delete a file
32 */
33 public function deleteFile(Request $request)
34 {
35 $del_file = $request->get('del_file');
36 $path = $request->get('folder').'/'.$del_file;
37
38 $result = $this->manager->deleteFile($path);
39
40 if ($result === true) {
41 return redirect()
42 ->back()
43 ->withSuccess("File '$del_file' deleted.");
44 }
45
46 $error = $result ? : "An error occurred deleting file.";
47 return redirect()
48 ->back()
49 ->withErrors([$error]);
50 }
51
52 /**
53 * Delete a folder
54 */
55 public function deleteFolder(Request $request)
56 {
57 $del_folder = $request->get('del_folder');
58 $folder = $request->get('folder').'/'.$del_folder;
59
60 $result = $this->manager->deleteDirectory($folder);
61
62 if ($result === true) {
63 return redirect()
64 ->back()
65 ->withSuccess("Folder '$del_folder' deleted.");
66 }
67
68 $error = $result ? : "An error occurred deleting directory.";
69 return redirect()
70 ->back()
71 ->withErrors([$error]);
72 }
73
74 /**
75 * Upload new file
76 */
77 public function uploadFile(UploadFileRequest $request)
78 {
79 $file = $_FILES['file'];
80 $fileName = $request->get('file_name');
81 $fileName = $fileName ?: $file['name'];
82 $path = str_finish($request->get('folder'), '/') . $fileName;
83 $content = File::get($file['tmp_name']);
84
85 $result = $this->manager->saveFile($path, $content);
86
87 if ($result === true) {
88 return redirect()
89 ->back()
90 ->withSuccess("File '$fileName' uploaded.");
91 }
92
93 $error = $result ? : "An error occurred uploading file.";
94 return redirect()
95 ->back()
96 ->withErrors([$error]);
97 }
I’m not going to comment much on those methods because this chapter’s already getting
long. Basically, we’re calling methods on the UploadsManager class to do the actual
file work.
Four new methods here, which implement the new functionality of the Upload Manager.
Congratulations
You now have completed the Upload Manager for your blog’s administration area. Everything should
work. Give it a try. Upload a few files, create a directory. Try uploading an image and you’ll see an
option to preview it.
Converting to Amazon S3 is optional. If you don’t want to do this, skip to the next
chapter.
[email protected]
Here’s a quick startup list to set up S3 services.
3. Create a S3 Bucket
After you’ve created or logged into your AWS account, go to the Console and click on
the S3 link. If you’ve never created an S3 bucket before you’ll be prompted to do so.
Event if you have created a bucket before, it’s still a good idea to create a new one.
Each project should have its own, unique bucket.
Click on the [Create New Access Key] button and you’ll see a screen that follows.
Important
Write down the values from the screen below before closing the window. You may want to download
the key file too. You won’t have access to the secret key again.
[email protected]
S3 Is Now Set Up
For now. Later we’ll make access public
Use the table below to get the code for the region you used when creating your bucket.
Code Name
ap-northeast-1 Asia Pacific (Tokyo)
ap-southeast-1 Asia Pacific (Singapore)
ap-southeast-2 Asia Pacific (Sydney)
eu-central-1 EU (Frankfurt)
eu-west-1 EU (Ireland)
[email protected]
sa-east-1 South America (Sao Paulo)
us-east-1 US East (N. Virginia)
us-west-1 US West (N. California)
us-west-2 US West (Oregon)
Edit the .env file, filling in the AWS parameters like below.
AWS paramters in .env
AWS_KEY=AKIAJTP7B64BVNBJKZ3Q
AWS_SECRET=j1bKrPz7Gm7gdRkgrHULHAcX4/gXh/nQF1VPc+ZM
AWS_REGION=us-west-2
AWS_BUCKET=l5beauty
USE YOUR OWN VALUES. (I’ll delete these values from my account shortly.)
Also, verify from within your AWS Console that changes you make are reflected there.
Important
After uploading a file from the Upload Manager, view your bucket from the AWS console, click on
the file and check the Properties tab. There will be a Link: value. Examine this URL and make any
necessary changes to the uploads.webpath property of config/blog.php.
To fix this we’ll make everything on this bucket you created publicly viewable.
Go into the AWS Console and navigate to the bucket you just created. Then click on the
Properties tab and click on the Permissions accordion to expand it. You should see a
screen like the one below:
[email protected]
Bucket Permissions
Click on the Add bucket policy link and when the Bucket Policy Eidtor appears click
on the new policy link.
Click the [Add Statement] button and then the [Generate Policy] button and you’ll see
a policy like the one below.
Policy JSON Document
{
"Id": "Policy1430071323597",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1430071313897",
"Action": [
"s3:GetObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::l5beauty/*",
[email protected]
"Principal": "*"
}
]
}
Copy this text and paste it into the previous screen’s Bucket Policy Editor box, click
Save and you’re done.
Congratulations
Your Upload Manager should now work, allowing you to manage any files uploaded for your blog in
the cloud.
Recap
In this chapter we configured the file system and built an upload manager that allowed
us to upload files to the public area of the L5 Beauty application. We added a
helpers.php file to the application as a place to store one-off functions.
A couple new packages were pulled in. One to detect mime-types and one required to
access the Amazon S3 service.
Instructions were provided to set up and configure the Amazon S3 service. And finally,
we configured our Upload Manager to use this.
Chapter Contents
Modifying the Posts table
Requiring Doctrine
Creating the Migration
Running Migrations
Updating the Models
Adding Selectize.js and Pickadate.js
Pulling them in with Bower
Managing them with Gulp
Creating the Request Classes
Creating the PostFormFields Job
Adding to helpers.php
Updating the Post Model
Updating the Controller
The Post Views
Removing the show route
Recap
Requiring Doctrine
In Laravel 5.1 whenever columns in a database need to be modified, the Doctrine
package is required. Use composer to install the doctrine/dbal package as instructed
below.
Installing doctrine/dbal
vagrant@homestead:~/Code/l5beauty$ composer require "doctrine/dbal"
[email protected]
Using version ^2.5 for doctrine/dbal
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing doctrine/lexer (v1.0.1)
Downloading: 100%
[snip]
subtitle
Our final blog will also require a subtitle.
content_html and content_raw
We’ll do our editing in Markdown, but whenever content is saved we also want to
save the HTML version.
page_image
Like the tags table, each post will allow a unique header image at the top.
meta_description
To populate the META tags on the post’s page for search engines.
is_draft
We’ll add a flag on each post so as to not inadvertently publish a draft post.
layout
This will give us the ability to use unique layouts on the posts.
Running Migrations
Now that the migration is created, run migrations from the Homestead VM.
Running Migrations
vagrant@homestead:~/Code/l5beauty$ php artisan migrate
Migrated: 2015_04_28_052603_restructure_posts_table
$fillable
Here we set the name of the columns that can be filled with an array. The
[email protected]
addNeededTags() method will use this.
posts()
The many-to-many relationship between posts and tags.
addNeededTags()
A static function to add tags that aren’t in the database already.
In the posts() method we’re only passing two arguments to belongsToMany(). The first argument is
the name of the model class. The second argument is the name of the table to use. The next two
arguments are the foreignKey and the otherKey, but since we’re using post_id and tag_id, the
additional arguments to belongsToMany() can be omitted. (Laravel is smart enough to figure them
out.)
tags()
Similar to the posts() method in the Tag model, but here we’re going the other
way.
setTitleAttribute()
We changed this from the previous version to call the setUniqueSlug() method.
setUniqueSlug()
A function that recurses to set the unique slug when needed.
setContentRawAttribute()
Now when the content_raw value is set in the model, it will be automatically be
converted to HTML and assigned to the content_html attribute.
syncTags()
Synchronizes the tags with the post.
[snip]
selectize#0.12.1 vendor/bower_dl/selectize
├── jquery#2.1.4
├── microplugin#0.0.3
└── sifter#0.4.1
Then let’s pull in Pickadate.js. There’s no shortage of libraries for picking dates and
times, but I wanted to use this one because it works slick on small devices. Follow the
[email protected]
instructions below to pull in Pickadate.js.
Adding Pickadate.js with Bower
~/Code/l5beauty% bower install pickadate --save
bower not-cached git://github.com/amsul/pickadate.js.git#*
bower resolve git://github.com/amsul/pickadate.js.git#*
bower download https://github.com/amsul/pickadate.js/archive/3.5.6.tar.gz
bower extract pickadate#* archive.tar.gz
bower resolved git://github.com/amsul/pickadate.js.git#3.5.6
bower install pickadate#3.5.6
pickadate#3.5.6 vendor/bower_dl/pickadate
└── jquery#2.1.4
This configuration is basically the same as before, but with the needed files from
[email protected]
Selectize.js and Pickadate.js copied into the public asset directory.
First create the requests using artisan in the Homestead VM. This will create skeletons
for each of these classes in the app/Http/Requests directory.
Creating the Request Class Skeletons
~/Code/l5beauty$ php artisan make:request PostCreateRequest
Request created successfully.
~/Code/l5beauty$ php artisan make:request PostUpdateRequest
Request created successfully.
This is a standard request with the authorize() and rules() methods, but we’re also
adding the postFillData() to make it easy to pull all the data from the request to fill a
new Post model with.
NOTE: We’re just inheriting the authorize() and rules() methods from the
PostCreateRequest class. Yes, we could get by with a single class to handle both but
I don’t know how things may change in the future and like the idea of separate classes.
[email protected]
Creating the PostFormFields Job
Let’s create a utility job we can call from the PostController. It’ll be called the
PostFormFields job. This job will get executed when we want to get a list of all the
fields to populate post form.
The point of this job is to return an array of fields and values to use to populate a form.
If a Post isn’t loaded (as will be the case in a create), then default values will be
returned. If a Post is loaded (for updates), then the values will be pulled from the
database.
Adding to helpers.php
We’ll need a couple one-off functions so edit the app/helpers.php file and add the
two functions below.
Additions to helpers.php
1 /**
2 * Return "checked" if true
3 */
4 function checked($value)
5 {
6 return $value ? 'checked' : '';
7 }
8
9 /**
10 * Return img url for headers
11 */
12 function page_image($value = null)
[email protected]
13 {
14 if (empty($value)) {
15 $value = config('blog.page_image');
16 }
17 if (! starts_with($value, 'http') && $value[0] !== '/') {
18 $value = config('blog.uploads.webpath') . '/' . $value;
19 }
20
21 return $value;
22 }
checked()
This helper function will be used in views to output the checked attribute in check
boxes and radio buttons.
page_image()
This function returns the full path to an image in the uploaded area using the value
from the configuration. If a value isn’t specified then it pulls a default image from
the blog config (which you’ll need to set up yourself in as ‘page_image’ in
config/blog.php if you wish to use.)
The $fillable property will let us fill the data during creating.
Because of the Request classes and PostFormFields class created earlier, the size of
the controller will stay relatively small.
index()
Pass index the view $posts will all the posts and file and return it.
create()
Use the PostFormFields job to return all the field values. Return the create view
with these values passed in.
store()
Create the post with the fillable data from the request. Attach any tags and return to
the index route with a success message.
edit()
Use the PostFormFields job to return all the field values for the post being
edited. Return the edit view with these values passed in.
update()
Load the post. Update all the fillable fields. Save any changes and keep the tags in
sync. Then return either back to the edit form or to the index list with a success
message.
destroy()
Load the post. Unlink any associated tags. Delete the post and return to the index
route with a success message.
[email protected]
A pretty slim controller really. About the only thing left to do are the views.
This is a simple view that sets up the table with all the posts and then initializes the
table as a DataTable in the scripts section.
Here we added in the Selectize and Pickadate libraries. You’ll also note that we’re
referencing an admin.post._form partial which isn’t yet created.
And with that, all the views for the posts management are done.
Congratulations!
The administration area of your blog is complete. Try it out. Add a few posts, edit them. Try deleting
them.
The display of the blog pages doesn’t look beautiful yet, we’ll get to that next.
Recap
This was a fairly long chapter but a huge amount was accomplished. We created a
migration to modify the posts table and then updated the Tag and Post models. Then
bower was used to pull in the Selectize.js and Pickadate.js libraries which, of course,
we managed using gulp.
Request classes (to handle form input) were created. As was a Laravel Job class to
return the post form data. Finally, the controller and views were wrapped up.
All this work resulted in a clean and easy way to administer posts.
Chapter Contents
Using the Clean Blog Template
Fetching Clean Blog with Bower
Gulping Clean Blog’s Less Files
Copying Some Header Images
Creating the BlogIndexData Command
Updating the BlogController
Building the Assets
Creating blog.js
Creating blog.less
Updating gulpfile.js
The Blog Views
The blog.layouts.master view
The blog.layouts.index view
The blog.layouts.post view
The blog.partials.page-nav view
The blog.partials.page-footer view
Adding a Few Model Methods
Update the Tag Model
Update the Post Model
Updating the Blog Config
Updating our Sample Data
Updating the Database Seeders
Updating the Model Factories
Seeding the Database
Recap
There may be a couple warning errors about the clean blog repository not having
bower.json set up correctly, but that’s okay.
Now when you do a gulp copyfiles then the latest clean blog files will be copied.
We’ll use these files in a little bit as part of the blog’s CSS.
about-bg.jpg
contact-bg.jpg
home-bg.jpg
post-bg.jpg
The files are located in the img folder of the Clean Blog sources you just coped using
Bower (which should be vendor/bower_dl/clean-blog).
If a tag is specified in the query string, we’ll need to gather the list of posts, filtering
them so only posts with that tag displays. Rather than adding the logic to do this in the
controller, let’s create a one-off job to gather the index data.
Now edit the newly created BlogIndexData.php file. It’s in your app/Jobs directory.
Content of BlogIndexData.php
1 <?php
2
3 namespace App\Jobs;
4
5 use App\Post;
6 use App\Tag;
7 use Carbon\Carbon;
8 use Illuminate\Contracts\Bus\SelfHandling;
9
10 class BlogIndexData extends Job implements SelfHandling
11 {
12 protected $tag;
13
14 /**
15 * Constructor
16 *
17 * @param string|null $tag
18 */
19 public function __construct($tag)
20 {
21 $this->tag = $tag;
22 }
23
24 /**
25 * Execute the command.
26 *
27 * @return array
28 */
29 public function handle()
30 {
31 if ($this->tag) {
32 return $this->tagIndexData($this->tag);
33 }
34
35 return $this->normalIndexData();
[email protected]
36 }
37
38 /**
39 * Return data for normal index page
40 *
41 * @return array
42 */
43 protected function normalIndexData()
44 {
45 $posts = Post::with('tags')
46 ->where('published_at', '<=', Carbon::now())
47 ->where('is_draft', 0)
48 ->orderBy('published_at', 'desc')
49 ->simplePaginate(config('blog.posts_per_page'));
50
51 return [
52 'title' => config('blog.title'),
53 'subtitle' => config('blog.subtitle'),
54 'posts' => $posts,
55 'page_image' => config('blog.page_image'),
56 'meta_description' => config('blog.description'),
57 'reverse_direction' => false,
58 'tag' => null,
59 ];
60 }
61
62 /**
63 * Return data for a tag index page
64 *
65 * @param string $tag
66 * @return array
67 */
68 protected function tagIndexData($tag)
69 {
70 $tag = Tag::where('tag', $tag)->firstOrFail();
71 $reverse_direction = (bool)$tag->reverse_direction;
72
73 $posts = Post::where('published_at', '<=', Carbon::now())
74 ->whereHas('tags', function ($q) use ($tag) {
75 $q->where('tag', '=', $tag->tag);
76 })
77 ->where('is_draft', 0)
78 ->orderBy('published_at', $reverse_direction ? 'asc' : 'desc')
79 ->simplePaginate(config('blog.posts_per_page'));
80 $posts->addQuery('tag', $tag->tag);
81
82 $page_image = $tag->page_image ?: config('blog.page_image');
83
84 return [
85 'title' => $tag->title,
86 'subtitle' => $tag->subtitle,
87 'posts' => $posts,
88 'page_image' => $page_image,
89 'tag' => $tag,
[email protected]
90 'reverse_direction' => $reverse_direction,
91 'meta_description' => $tag->meta_description ?: \
92 config('blog.description'),
93 ];
94 }
95 }
__construct()
Just stash the tag passed to the constructor.
handle()
A simple method. If a value for $tag was passed during construction we call one
method to gather the data, otherwise a different method is called.
normalIndexData()
This method returns the index data the normal way. That is, without a filter on the
tag. The code is almost identical to what we did with the 10 minute blog, but now
any tags the posts have are Eager Loaded (the with() method does this.). Also,
we do filter the query to not include any draft posts.
tagIndexData()
Here we first load the Tag and then filter posts to match the tag. This is
accomplished with the whereHas() method. Don’t forget that line continuation
character (the backslash) where ‘meta_description’ is returned should not be
typed, instead continue typing the next line without hitting enter!
Notice all the extra data we’re returning? Before, in the 10 minute blog, we only
returned the $posts to the view. But now we return all kinds of information. Shortly,
you’ll see how this is used in the index view.
Eager Loading
Eager Loading helps with the n+1 query problem. It loads queries having this issue in two queries
instead of many. This can drastically increase the application’s performance. See the official Laravel
5.1 documentation for a complete example.
index()
Pull any $tag value from the request and use the BlogIndexData command to
figure the data. Because we want the ability to have different index templates for
different tags, we ask the Tag class for the template if a $tag is used, otherwise
we go with the default.
showPost()
Any associated tags are Eager Loaded with the post. If there’s any $tag passed in
the query string, we convert $tag over to the actual Tag record before passing it to
the view.
Creating blog.js
Create blog.js in the resources/assets/js directory with the following content.
Content of blog.js
1 /*
2 * Blog Javascript
[email protected]
3 * Copied from Clean Blog v1.0.0 (http://startbootstrap.com)
4 */
5
6 // Navigation Scripts to Show Header on Scroll-Up
7 jQuery(document).ready(function($) {
8 var MQL = 1170;
9
10 //primary navigation slide-in effect
11 if ($(window).width() > MQL) {
12 var headerHeight = $('.navbar-custom').height();
13 $(window).on('scroll', {
14 previousTop: 0
15 },
16 function() {
17 var currentTop = $(window).scrollTop();
18
19 //if user is scrolling up
20 if (currentTop < this.previousTop) {
21 if (currentTop > 0 && $('.navbar-custom').hasClass('is-fixed')) {
22 $('.navbar-custom').addClass('is-visible');
23 } else {
24 $('.navbar-custom').removeClass('is-visible is-fixed');
25 }
26 //if scrolling down...
27 } else {
28 $('.navbar-custom').removeClass('is-visible');
29 if (currentTop > headerHeight &&
30 !$('.navbar-custom').hasClass('is-fixed')) {
31 $('.navbar-custom').addClass('is-fixed');
32 }
33 }
34 this.previousTop = currentTop;
35 });
36 }
37
38 // Initialize tooltips
39 $('[data-toggle="tooltip"]').tooltip();
40 });
This code implement tooltips and will cause the navigation bar to appear when the user
scrolls up.
Creating blog.less
In the resources/assets/less directory create a file named blog.less with the
following content.
Content of blog.less
1 @import "bootstrap/bootstrap";
[email protected]
2 @import "fontawesome/font-awesome";
3 @import "clean-blog/clean-blog";
4
5 @import "//fonts.googleapis.com/css?family=Lora:400,700,\
6 400italic,700italic";
7 @import "//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,\
8 600italic,700italic,800italic,400,300,600,700,800'";
9
10 .intro-header .post-heading .meta a,
11 article a {
12 text-decoration: underline;
13 }
14
15 h2 {
16 padding-top: 22px;
17 }
18 h3 {
19 padding-top: 15px;
20 }
21
22 h2 + p, h3 + p, h4 + p {
23 margin-top: 5px;
24 }
25
26 // Adjust position of captions
27 .caption-title {
28 margin-bottom: 5px;
29 }
30 .caption-title + p {
31 margin-top: 0;
32 }
33
34 // Change the styling of dt/dd elements
35 dt {
36 margin-bottom: 5px;
37 }
38 dd {
39 margin-left: 30px;
40 margin-bottom: 10px;
41 }
This file pulls in Bootstrap, Font Awesome and Clean Blog. Then the fonts are imported
and a touch of styling is added to a few elements. (Again, careful of those line
continuation characters on the 4th and 5th @import lines.)
Updating gulpfile.js
Update gulpfile.js to match what’s below. The only changes at this point are the
addition of the scripts for blog.js and blog.less.
Final Version of gulpfile.js
1 var gulp = require('gulp');
[email protected]
1 var gulp = require('gulp');
2 var rename = require('gulp-rename');
3 var elixir = require('laravel-elixir');
4
5 /**
6 * Copy any needed files.
7 *
8 * Do a 'gulp copyfiles' after bower updates
9 */
10 gulp.task("copyfiles", function() {
11
12 // Copy jQuery, Bootstrap, and FontAwesome
13 gulp.src("vendor/bower_dl/jquery/dist/jquery.js")
14 .pipe(gulp.dest("resources/assets/js/"));
15
16 gulp.src("vendor/bower_dl/bootstrap/less/**")
17 .pipe(gulp.dest("resources/assets/less/bootstrap"));
18
19 gulp.src("vendor/bower_dl/bootstrap/dist/js/bootstrap.js")
20 .pipe(gulp.dest("resources/assets/js/"));
21
22 gulp.src("vendor/bower_dl/bootstrap/dist/fonts/**")
23 .pipe(gulp.dest("public/assets/fonts"));
24
25 gulp.src("vendor/bower_dl/fontawesome/less/**")
26 .pipe(gulp.dest("resources/assets/less/fontawesome"));
27
28 gulp.src("vendor/bower_dl/fontawesome/fonts/**")
29 .pipe(gulp.dest("public/assets/fonts"));
30
31 // Copy datatables
32 var dtDir = 'vendor/bower_dl/datatables-plugins/integration/';
33
34 gulp.src("vendor/bower_dl/datatables/media/js/jquery.dataTables.js")
35 .pipe(gulp.dest('resources/assets/js/'));
36
37 gulp.src(dtDir + 'bootstrap/3/dataTables.bootstrap.css')
38 .pipe(rename('dataTables.bootstrap.less'))
39 .pipe(gulp.dest('resources/assets/less/others/'));
40
41 gulp.src(dtDir + 'bootstrap/3/dataTables.bootstrap.js')
42 .pipe(gulp.dest('resources/assets/js/'));
43
44 // Copy selectize
45 gulp.src("vendor/bower_dl/selectize/dist/css/**")
46 .pipe(gulp.dest("public/assets/selectize/css"));
47
48 gulp.src("vendor/bower_dl/selectize/dist/js/standalone/selectize.min.js")
49 .pipe(gulp.dest("public/assets/selectize/"));
50
51 // Copy pickadate
52 gulp.src("vendor/bower_dl/pickadate/lib/compressed/themes/**")
53 .pipe(gulp.dest("public/assets/pickadate/themes/"));
54
55 gulp.src("vendor/bower_dl/pickadate/lib/compressed/picker.js")
[email protected]
56 .pipe(gulp.dest("public/assets/pickadate/"));
57
58 gulp.src("vendor/bower_dl/pickadate/lib/compressed/picker.date.js")
59 .pipe(gulp.dest("public/assets/pickadate/"));
60
61 gulp.src("vendor/bower_dl/pickadate/lib/compressed/picker.time.js")
62 .pipe(gulp.dest("public/assets/pickadate/"));
63
64 // Copy clean-blog less files
65 gulp.src("vendor/bower_dl/clean-blog/less/**")
66 .pipe(gulp.dest("resources/assets/less/clean-blog"));
67 });
68
69 /**
70 * Default gulp is to run this elixir stuff
71 */
72 elixir(function(mix) {
73
74 // Combine scripts
75 mix.scripts([
76 'js/jquery.js',
77 'js/bootstrap.js',
78 'js/jquery.dataTables.js',
79 'js/dataTables.bootstrap.js'
80 ],
81 'public/assets/js/admin.js', 'resources//assets');
82
83 // Combine blog scripts
84 mix.scripts([
85 'js/jquery.js',
86 'js/bootstrap.js',
87 'js/blog.js'
88 ], 'public/assets/js/blog.js', 'resources//assets');
89
90 // Compile CSS
91 mix.less('admin.less', 'public/assets/css/admin.css');
92 mix.less('blog.less', 'public/assets/css/blog.css');
93 });
Run gulp twice. First gulp copyfiles to copy the needed clean-blog assets. Then just
a plain gulp to combine everything.
You can delete the index.blade.php and post.blade.php files that are in the
resources/views/blog directory. They are still there from the 10 Minute Blog
chapter and we don’t need them any longer.
The blog.layouts.index view will show the blog index pages. It wraps the page-
header in it’s own section. The content loops through the $posts and displays
navigation afterward.
Like the blog.layouts.index view this one has a page-header and a content section.
The menu on the navbar at top will only have a single option, Home.
The layout() method returns a Tag’s layout, or if there isn’t a tag, or the tag doesn’t
have a layout, then a default value is returned.
url()
This method returns the URI to the particular post with an optional tag in the query
string. The blog.layouts.index view uses it to link to a post details page.
tagLinks()
This method returns an array of links, each link going to the index page for a
particular tag the post has been, uh, tagged with.
newerPost()
Returns the next Post coming after $this or null if there are no newer posts.
olderPost()
Returns the previous Post coming before $this or null if there are no older
[email protected]
posts.
Again, use your own values here. Especially the uploads section. If you’re using
Amazon S3 this will look different.
Let’s update the seeder and factories and re-seed the database to populate tags and other
fields.
This last seeder is a tiny bit longer because we randomly tie some of the tags to the
posts.
Content of ModelFactory.php
1 <?php
2
3 $factory->define(App\User::class, function ($faker) {
4 return [
5 'name' => $faker->name,
6 'email' => $faker->email,
7 'password' => str_random(10),
8 'remember_token' => str_random(10),
9 ];
[email protected]
10 });
11
12 $factory->define(App\Post::class, function ($faker) {
13 $images = ['about-bg.jpg', 'contact-bg.jpg', 'home-bg.jpg', 'post-bg.jpg'];
14 $title = $faker->sentence(mt_rand(3, 10));
15 return [
16 'title' => $title,
17 'subtitle' => str_limit($faker->sentence(mt_rand(10, 20)), 252),
18 'page_image' => $images[mt_rand(0, 3)],
19 'content_raw' => join("\n\n", $faker->paragraphs(mt_rand(3, 6))),
20 'published_at' => $faker->dateTimeBetween('-1 month', '+3 days'),
21 'meta_description' => "Meta for $title",
22 'is_draft' => false,
23 ];
24 });
25
26 $factory->define(App\Tag::class, function ($faker) {
27 $images = ['about-bg.jpg', 'contact-bg.jpg', 'home-bg.jpg', 'post-bg.jpg'];
28 $word = $faker->word;
29 return [
30 'tag' => $word,
31 'title' => ucfirst($word),
32 'subtitle' => $faker->sentence,
33 'page_image' => $images[mt_rand(0, 3)],
34 'meta_description' => "Meta for $word",
35 'reverse_direction' => false,
36 ];
37 });
Recap
This chapter focused on cleaning up the blog pages. To do this we used the Clean Blog
template by StartBootstrap as a model. Clean Blog was pulled in with Bower, then we
made use of much of the assets as we built the index and post pages.
The blogging application is complete and usable. It’s time to start adding a few features
to our blog.
In the next chapter we’ll add a “Contact Us” form as we delve into Laravel 5.1 Queues.
[email protected]
Chapter 14 - Sending Mail and Using Queues
In this chapter we’ll add a Contact Us form to the blog. To do this we’ll explore
Laravel 5.1’s mailing functions and set up a queue for asynchronous processing.
Contents
Setting Up for Emails
Configuring for Gmail
Configuring for Mailgun
Testing Mail with Tinker
Adding a Contact Us Form
Adding the Link and Route
Creating the FormRequest
Adding the Controller
Creating the Views
Sending the Mail
About Queues
How they Work
The Different Queue Drivers
Using the Database Driver
Queuing the Contact Us Email
Changing the Controller
Where’s the Email
Running queue:work
Automatically Processing the Queue
Running queue:listen with supervisord
Using a Scheduled Command
Queing Jobs
Recap
This sets up who the emails are from which is required by Gmail and is good practice
for other providers.
Next, edit .env, changing the mail configuration to what’s below (replacing
USERNAME, PASSWORD, FROM, etc. your own settings).
Gmail Configuration in .env
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=YOUR-GMAIL-PASSWORD
[email protected]
MAIL_NAME=YOUR-NAME
The section after the next one (Testing Mail with Tinker) will explain how you can test
your mail configuration.
Yes, we’re just setting it up to read the values from the .env file.
Next, Mailgun requires the Guzzle Http library, so use composer to require it.
Requiring Guzzle Http
~/Code/l5beauty% composer require "guzzlehttp/guzzle=~5.0"
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing react/promise (v2.2.0)
Loading from cache
The nice thing about Mailgun is that email is sent via an API, which is faster than using
SMTP.
[email protected]
Testing Mail with Tinker
Laravel 5.1’s emailer uses views to send the email. So let’s first create a simple test
view.
Now fire up the artisan tinker command and send an email to yourself as instructed
below.
Testing Email with Tinker
~/Code/l5beauty$ php artisan tinker
Psy Shell v0.4.4 (PHP 5.6.2 — cli) by Justin Hileman
>>> Mail::send('emails.test',
... ['testVar' => 'Just a silly test'],
... function($message) {
... $message->to('[email protected]')
... ->subject('A simple test');
... });
=> 1
>>> exit
The first argument to Mail::send() is the view’s name. The second is an array of any
variables the view requires (and emails.test requires $testVar). The third is a
closure to do additional processing on the message. Here we just set the to address and
the subject line.
The above tinker example used a Gmail configuration, which returns 1 indicating
success. If you use the Mailgun driver in your configuration, a successful return value
will look different.
Testing Email with Tinker (Mailgun config)
~/Code/l5beauty$ php artisan tinker
Psy Shell v0.4.4 (PHP 5.6.2 — cli) by Justin Hileman
>>> Mail::send('emails.test',
... ['testVar' => 'Just a silly test'],
... function($message) {
... $message->to('[email protected]')
... ->subject('A simple test');
... });
=> <GuzzleHttp\Message\Response #0000000024da8555000000017f74f2c8> {}
>>> exit
Notice we used $router directly instead of the get() function. Then instead of the
post() function, the Route facade was used. This is simply to illustrate there’s
multiple ways to set up the routes. Normally, I just use the helper functions directly, but
some people prefer the $router variable or the Route facade.
Let’s create the FormRequest now, so it’s all ready when we build the controller. First
use artisan to create the skeleton.
Creating the FormRequest with Artisan
1 ~/Code/l5beauty$ php artisan make:request ContactMeRequest
2 Request created successfully.
/**
* Get the validation rules that apply to the request.
*/
public function rules()
{
return [
'name' => 'required',
'email' => 'required|email',
'message' => 'required',
];
}
}
No need to comment on the request. After building the administration side of this blog
you should be thoroughly familiar with Form Requests.
Then we use the Mail facade to send the message. You could optionally have the
sendContactInfo() method take an Illuminate\Mail\Mailer object as an argument
[email protected]
(Laravel 5.1 is smart enough to automatically inject it) and use this object to send mail.
See the Facade Class Reference in the Laravel 5.1 documentation for a list of facades
and the equivalent class to use if you want to access the instance directly.
After the message is sent, we redirect back to the contact page, flashing a success
message.
The blog.contact view should be easy to follow. Notice how we included the errors
and success partials from the administration area? They perfectly fit what was needed.
You may notice there’s a slight delay between clicking [Send] and receiving a response
of success back. This delay can be especially long when you’re using the smtp mail
driver.
Does this delay really need to be there? The answer is no, not if we set up a queue to
handle running tasks in the back ground.
About Queues
Queues allow you to defer the processing of time consuming tasks, such as emails. This
allows your web requests to respond quicker to the user.
Queue Flow
A web request hits the controller where it’s processed. During the processing something
is added to the queue (an Email in this figure) and then the response is returned.
Somewhere in the background a queue worker runs. It fetches the next thing from the
queue and processes it.
Bam!
sync - The sync driver effectively short circuits the entire queuing process. When
something is queued and the sync driver is being used, the item is fully processed
immediately and synchronously.
database - The database driver stores queued items in the local database.
Specifically, in a jobs table.
beanstalkd - The beanstalkd driver expects beanstalkd to be configured and
running. You’ll also have to do a composer require "pda/pheanstalk=~3.0"
to use it.
sqs - The sqs driver will queue to your Amazon SQS queue. Also composer
require aws/aws-sdk-php is required to use it.
iron - The iron driver will queue to your IronMQ account and composer
required "iron-io/iron_mq=~1.5" is required.
redis - The redis driver will store queued items in the Redis database. It
requires composer require "presdis/presis=~1.0" to operate.
Finally, edit .env and change the QUEUE_DRIVER setting from sync to database.
Change to ContractController.php
1 // Find the line below
2 Mail::send('emails.contact', $data, function ($message) use ($data) {
3
4 // And change it to match what's below
5 Mail::queue('emails.contact', $data, function ($message) use ($data) {
Still, you can wait forever for the email and it will never arrive.
Why?
Because there’s no process running in the background, watching for and handling items
arriving in the queue.
Running queue:work
To process the next item on the queue, we can manually run artisan’s queue:work
[email protected]
command.
This command will do nothing if the queue is empty. But if there’s an item on the queue
it will fetch the item and attempt to execute it.
Running Artisan queue:work
vagrant@homestead:~/Code/l5beauty$ php artisan queue:work
Processed: mailer@handleQueuedMessage
As you can see here it handled the queued email message. Now the email should arrive
in your inbox within moments.
One is load up artisan queue:listen in the startup scripts of your server. This
command automatically calls artisan queue:work when items appear in the queue.
The problem with this technique is something will invariably happen. The
queue:listen command will hang. Or it will stop running. A better way to run
queue:listen is with supervisord.
You’ll need to replace the /PATH/TO/ to match your local install. Likewise, the user
setting will be unique to your installation.
[email protected]
Using a Scheduled Command
Another option for low volume sites is to schedule queue:work to run every minute. Or
even every 5 minutes. This is best done using Laravel 5.1’s command scheduler.
This will run the queue:work command once a minute. You can change this frequency
in many ways.
Various Run Frequencies in Console Kernel
// Run every 5 minutes
$schedule->command('queue:work')->everyFiveMinutes();
The second step in setting up the scheduled command is to modify your machine’s
crontab. Edit crontab and add the following line.
Crontab Line for Artisan Scheduler
* * * * * php /path/to/artisan schedule:run 1>> /dev/null 2>&1
This will call artisan to run anything currently scheduled, sending any output to the null
device.
Queing Jobs
Another great use for queues are asynchronous jobs. These are jobs you execute as
normal with $this->dispatch(new JobName) from your controller, but they’ll simply
[email protected]
be placed in the queue to be run later by queue:work or whatever method is processing
queue items in your application.
First, when creating the job class, use the --queued option.
Example of a Queued Job
~/Projects/newbeauty$ php artisan make:job --queued TestJob
Job created successfully.
Now, if you examine the template Laravel 5.1 created for TestJob you’ll notice few
small changes at the top.
Difference in Queued Jobs
1 // These three use statements are new
2 use Illuminate\Queue\SerializesModels;
3 use Illuminate\Queue\InteractsWithQueue;
4 use Illuminate\Contracts\Queue\ShouldQueue;
5
6 // The class will also implement ShouldQueue
7 class TestJob extends Job implements SelfHandling, ShouldQueue
8 {
9
10 // And the class uses two traits
11 use InteractsWithQueue, SerializesModels;
ShouldQueue
By having the TestJobclass implement ShouldQueue, the handle() method won’t
be called. Instead, TestJob will be constructed and the instance will be pushed
onto the queue. When the item is processed from the queue, then the handle()
method will be called..
InteractsWithQueue
This will make several queue interaction methods available such as $this-
>delete() to delete the item from the queue or $this->release() to release the
item back onto the queue. Normally you won’t need these methods.
SerializesModels
When the job is serialized to be placed on the queue, this Trait will look for
properties of the Job that are models and serialize them correctly.
Queued Jobs are an excellent way to run time consuming processes which you
don’t want the user to have to wait for.
Recap
The main thing accomplished in this chapter was adding a Contact form to the blog, but
we covered several interesting topics to do it. We talked about sending mail with
Mailgun and testing mail with Tinker. Several alternative routing methods were
presented.
We discussed queues and set up a database queue. Finally, contact emails were sent
through the queue.
Contents
The Problem with Comments
Adding Disqus Comments
Creating a Disqus account
Passing the Slug to the page
Creating the Disqus Partial
Updating the Footer
Adding Social Links
Creating a RSS Feed
Pulling in the Composer Package
Creating the RSS Feed Service
Updating the Blog Configuration
Adding rss Route, Link, and Method
Create a Site Map
Creating the SiteMap Service
Adding the sitemap.xml Route and Method
Recap
First of all there’s the moderation, approving, and general management of the comments.
Yes, with Laravel 5.1 we could add this functionality to the blog’s administration,
allowing users to sign up, allowing the users to make comments and so forth. It’d be
easy enough to create management screens to handle all of this.
Frankly, I don’t want the headaches. I want to use a proven, third-party solution that will
keep my involvement to a minimum.
After you fill out the simple form, choose Universal Code from the next screen. Then
you’ll see a screen like the one below.
[email protected]
Be sure an use the correct shortname noted earlier for SHORTNAME HERE in the
above snippet.
If $slug is set then the disqus identifier will group all the comments together for that
page. $slug is now set for all post pages.
You’ll also note that the whole template is wrapped in a big @if statement. This is
because I didn’t want to show comments when I’m working on the blog locally. Feel
free to change this to suit your needs.
Updated blog.partials.page-footer
1 <hr>
2 <div class="container">
3 <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
4 @include('blog.partials.disqus')
5 </div>
6 </div>
7 <hr>
[email protected]
8 <footer>
9 <div class="container">
10 <div class="row">
11 <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
12 <p class="copyright">Copyright © {{ config('blog.author') }}</p>
13 </div>
14 </div>
15 </div>
16 </footer>
Be sure an update all those PERSONALIZE values with your own settings. If you don’t
have a particular social account, just remove the entire item from the list.
Did you notice that first one? The RSS feed? That’s not yet created, so let’s do it next.
getRSS()
This method returns the entire feed as a string. We cache the results for 2 hours so
the feed isn’t constantly being built.
buildRSSData()
This method build the feed itself from the posts table.
That’s it. Point your browser to http://l5beauty.app/rss and you’ll see the feed.
We’ll use the same technique as we did with the RSS Feed, to build it on-the-fly, but
cache the results so it’s only rebuilt a maximum of once every couple hours.
It’s A Wrap!
That’s the end of the l5beauty project. You’ve developed a complete blogging system in Laravel 5.1
and hopefully have learned a lot along the way!
Recap
This chapter put all the final pieces together for the blog. We started using Disqus for
comments and then created a RSS Feed for the project. Finally, the project was finished
by adding a Site Map.
[email protected]
There’s one more chapter left. It contains a review of what was learned and some
general topics about features in Laravel 5.1.
[email protected]
Chapter 16 - General Recap and Looking Forward
This is the end of the book. The L5Beauty project is complete. You may be thinking
Now What? After a few chapters on setting up Laravel and Homestead, and one chapter
on basic testing, this book has been all about writing the the blogging application and
often with a minimum of explanation. The focus has been building and finishing the
application.
But now it’s time to slow down, look back, and think about what’s been built and where
to go from here.
This chapter presents a mishmash of Laravel 5.1 topics. Some areas discussed below
were used in developing the blogging application but others were not needed in the
application.
Contents
Testing
Eloquent Models and the Fluent Query Builder
Advanced Routing
Migrations, Seeding, and Model Factories
Dependency and Method Injection
Facades vs. helpers vs. IoC objects
Laravel Elixir
Tinker
Artisan Commands
Events
Form Requests
Blade Template Engine
Flysystem
Queues
Blog Features to Add
Final Recap and Thank You
Testing
Laravel 5.1 was designed with testing in mind. We touched on testing briefly back in
Chapter 6 - Testing and even provided a fairly complete list of testing methods
[email protected]
available. But the subject of testing in Laravel 5.1 could easily fill its own book.
Those are topics just off the top of my head. If I thought about it more, this list could
double in size.
The best place to start learning about testing in Laravel 5.1 is the official documentation
at laravel.com.
We used Eloquent Models throughout this entire book when we created the Tag and
Post models, and when we made use of the User model. We even set up a many-to-
many relationship between these two models using a pivot table.
The query builder was used when we looked for the next or previous posts. Or in the
BlogIndexData job when we paginated across a group of posts.
The database documentation at laravel.com was re-written for Laravel 5 and provides
an excellent primer on what can be achieved in Laravel 5.1.
Advanced Routing
You can’t really write a Laravel 5.1 web application without doing some routing. It’s
the glue that ties web requests to the code to execute. We did some basic routing, used
the auth middleware, and even grouped some of our routes together in the l5beauty
project.
[email protected]
But there’s much more you can do with routing. Here’s a partial list.
We used migrations to create the database for the blog. We also made use of Model
Factories in the Database seeds in order to create some fake data for the blog.
But we only touched on the few of the methods provided by the Faker library.
Let’s say you have a controller that looks like the snippet below:
class MyController extends Controller
{
public function __construct(MyRepository $repository)
{
...
}
When Laravel 5.1 needs to construct MyController it will see the $repository
[email protected]
argument and if it can construct a MyRepository class it will and pass the newly
constructed instance to the MyController constructor.
If you were to try it using straight PHP code like below you’ll get an error.
$controller = new App\Http\Controllers\MyController;
But, if you let Laravel 5.1 construct it, this will work.
$controller = app('App\Http\Controllers\MyController');
Likewise, Laravel 5.1 will examine the controller methods and automatically inject
classes it can figure out how to construct.
So, if you have a route pointing to the someMethod() method of the example above, and
Laravel can determine how to construct MyCoolClass, it’ll inject an instance to the
method when it calls it.
My take on this is that it’s all on the table. Use whichever is quickest and easiest.
Often, I’ll just use Auth::user() until I discover that my class is using Auth::user()
in many places. Only then will I inject the Guard instance in my class’s constructor and
change the usage.
Laravel Elixir
We used Laravel Elixir to compile LESS files into CSS files and to combine multiple
JavaScript files into single files. We also added a Gulp task to copy Bower assets into
the resources/assets directory of the l5beauty project.
Tinker
We used artisan tinker a couple times during the development of the l5beauty
project. First to set up the user, and then to send a test email.
Tinker uses the powerful Psy Shell. It’s a REPL shell for PHP and the great thing about
Tinker is that it bootstraps your application. It’s like you’re running a shell inside your
application’s environment.
Before Tinker this used to be my “debugging” process when I was testing something:
But now I use Tinker all the time for quick tests and checks.
Artisan Commands
This book barely touched Artisan commands. We used a few of the built-in commands
such as:
But there are many Artisan commands we didn’t need to use during creation of the
[email protected]
l5beauty project. Execute php artisan without any options to see a list.
And we didn’t create our own Artisan commands. Laravel makes the process quick and
easy and now with Laravel 5.1 it’s even easier using the new Signature format of
artisan commands.
With Laravel 5.1 you can even have your Artisan commands output progress bars.
Check out the official Laravel 5.1 documentation at laravel.com for all the details.
Events
One area of Laravel 5.1 which the l5beauty project did not use at all are Events.
Events provide a simple observer pattern, allowing you to fire events within your
application and not worry about who is listening for the events. You could have code
like this sprinkled through your code.
event(new UserLoggedOn($user));
// or
event(new NewsletterWasMailed());
// or even
event(new ProductWasPurchased($orderDetails));
Other classes can be set up to listen for these events and perform some action when the
event occurs.
Really, that’s the basics of Laravel 5.1 Events. Yes, you can have Listeners queued.
And a really neat feature in Laravel 5.1 is that you can have Events broadcast to your
application (the web page the user is currently on) over a web socket connection.
Find out more about Events in the Laravel 5.1 official documentation.
Form Requests
[email protected]
Form Requests are great. They allow the validation of form input to be separated from
the Controller and wrapped in a nice little class.
See, Blade uses fast substitution, replacing it’s Blade-syntax with PHP equivalents. The
resulting file is pure PHP + HTML. Laravel even caches these files.
Every “view” in the l5beauty project uses a Blade template. Even so, there are some
blade directives we didn’t use. What follows is a fairly complete list of all Blade
directives:
I especially like the last one, @inject(). It’s new in Laravel 5.1.
Flysystem
Laravel 5.1’s filesystem is built upon the Flysystem PHP package by Frank de Jonge.
We used it in the l5beauty project when we developed the Upload Manager. First we
used the local file system, then (and optionally) we used the Amazon S3 driver to store
uploads in the cloud.
Besides local and Amazon S3 there’s a couple other drivers you can use.
1. The FTP Driver - Using this driver you can connect to any standard FTP server.
2. The Rackspace Driver - Like the Amazon S3 driver, this driver allows you to
manage files in the cloud.
Queues
We used Laravel 5.1 queuing system in the l5beauty project to queue emails from the
contact form.
And with Laravel 5.1’s Database Queue Driver, there’s no reason not to use them when
needed.
A Newsletter System
You could add a Newsletter Sign-up Form, and even a Newsletter system. For this I
[email protected]
suggest you use Mail Chimp.
I’ve created several such systems for projects in the past. It’s easy to do and would be
an excellent addition to the L5 Beauty Blog.
Dropzone Upload
With the Upload Manager of the blog I focused on the management of the files and only
implemented a basic file upload.
Comments
Some people don’t like Disqus. If you don’t, why not create your own comment system?
Page Caching
How about caching the pages of the blog? This would be a great feature to add.
$response = $next($request);
return $response;
}
I sincerely hope you’ve found this book helpful. That’s been my #1 goal in writing this,
[email protected]
to help others come into the world of Laravel and discover what a great place it is.
Stop by my blog at LaravelCoding.com and see what I’m up to. I’m a very inconsistent
blogger, but occasionally I do update things.