Laravel JWT Auth with Vue.js

This post documents using Laravel to build JSON Web Token based authentication with a Vue.js 1.0 user interface. UPDATE: vue.js 2.0 version published.

A fresh install of Laravel 5.2 for local development is required. Head over to the Laravel 5.2 docs to get setup with Composer and Laravel as needed. This post contains some info on how I installed Laravel 5.2 on Windows. Once Laravel 5.2 is installed, the database created and the .env file updated to connect to it, run the migration to create the users table.

php artisan migrate

User Model

One of the first things I like to do is create a Models folder and move the User model into it. To organize models within their own directory, start by creating a folder named Models in the app directory and move the User model into it.

mkdir app/Models

mv app/User.php app/Models

Edit app/Models/User.php, change the App namespace to App\Models.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    ...

  • Throughout this tutorial you will encounter an ellipsis … in the code examples. These are not a part of the code and are there only to denote code that is being skipped and not applicable to the example. To view the entire file, examine the source code.

Edit, config/auth.php. Update the authentication drivers user provider for the App\Models\User namespace change as follows.

auth.php
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],

    ...

Laravel Elixir Vueify

Using NPM, install the dependencies including laravel-elixer and gulp that should already be defined in the package.json file.

npm install

Install the Laravel Elixir and the Browserify Vueify plugin wrapper, laravel-elixir-vueify. This node package also includes vue.js.

For Laravel Elixer 5
npm install laravel-elixir-vueify@1.0.6 --save-dev

Laravel Elixir is a build tool wrapper for Gulp. To use laravel-elixir-vueify, require laravel-elixir-vueify and update the elixir call with mix.browserify in the gulpfile.js as follows.

REPLACE
elixir(function(mix) {
    mix.sass('app.scss');
});
WITH
require('laravel-elixir-vueify');

elixir(function(mix) {
    mix.browserify('app.js');
    mix.sass('app.scss');
});

Create a resources/assets/js folder for the JavaScript.

mkdir resources/assets/js

Create a resources/assets/js/app.js JavaScript file.

touch resources/assets/js/app.js

Test the gulp build. A browserified app.js file that includes an external source map file should get compiled and written to public/js/app.js. The pre-existing app.scss Sass file should also get compiled.

gulp
Default Gulp task output
Default Gulp task output

Vue Router and Components

Install the router for Vue.js.

npm install vue-router@0.7.13 --save-dev
  • The npm install vue-router command above has been updated to install the version supported by the 1.x version of vue.js. This is the vue.js version installed with laravel-elixir-vueify@1.0.6.

Create a resources/assets/components folder and add an empty App.vue, Dashboard.vue, Home.vue, Register.vue and Signin.vue component.

mkdir resources/assets/components

touch resources/assets/components/App.vue

touch resources/assets/components/Dashboard.vue

touch resources/assets/components/Home.vue

touch resources/assets/components/Register.vue

touch resources/assets/components/Signin.vue

Add the following code to the empty App.vue file which will be the base layout component.

App.vue
<template>
    <div class="panel panel-default">
        <div class="panel-heading">
            <nav>
                <ul class="list-inline">
                    <li><a v-link="{ name: 'home' }">Home</a></li>
                    <li class="pull-right">
                        <a v-link="{ name: 'register' }">Register</a>
                    </li>
                </ul>
            </nav>
        </div>
        <div class="panel-body">
            <router-view></router-view>
        </div>
    </div>
</template>

Add the following code to the empty Home.vue file. The content in this template will populate the App.vue router-view element by default.

Home.vue
<template>
    <h1>Laravel 5</h1>
</template>

Add the following code to the empty Register.vue file for the user account creation form.

Register.vue
<template>
    <div class="alert alert-danger" v-if="error && !success">
        <p>There was an error, unable to complete registration.</p>
    </div>
    <div class="alert alert-success" v-if="success">
        <p>Registration completed. You can now sign in.</p>
    </div>
    <form autocomplete="off" v-on:submit="register" v-if="!success">
        <div class="form-group" v-bind:class="{ 'has-error': error && response.username }">
            <label for="name">Name</label>
            <input type="text" id="name" class="form-control" v-model="name" required>
            <span class="help-block" v-if="error && response.name">{{ response.name }}</span>
        </div>
        <div class="form-group" v-bind:class="{ 'has-error': error && response.email }">
            <label for="email">E-mail</label>
            <input type="email" id="email" class="form-control" placeholder="gavin.belson@hooli.com" v-model="email" required>
            <span class="help-block" v-if="error && response.email">{{ response.email }}</span>
        </div>
        <div class="form-group" v-bind:class="{ 'has-error': error && response.password }">
            <label for="password">Password</label>
            <input type="password" id="password" class="form-control" v-model="password" required>
            <span class="help-block" v-if="error && response.password">{{ response.password }}</span>
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
    </form>
</template>

Add the following code to the empty app.js file to require vue, the vue-router, import vue components, setup the router. Export Vue and the router to allow modules to import them.

app.js
var Vue = require('vue');
var VueRouter = require('vue-router');

import App from '../components/App.vue';
import Dashboard from '../components/Dashboard.vue';
import Home from '../components/Home.vue';
import Register from '../components/Register.vue';
import Signin from '../components/Signin.vue';

Vue.use(VueRouter);

export default Vue;
export var router = new VueRouter

router.map({
    '/': {
        name: 'home',
        component: Home
    },
    '/register': {
        name: 'register',
        component: Register
    }
});

router.start(App, '#app');

Web Page

Edit the resources/views/welcome.blade.php template.

Replace the entire contents of the file with this markup.

welcome.blade.php
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="csrf-token" content="{!! csrf_token() !!}">
    <title>Laravel</title>

    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

</head>
<body>
    <div class="container">
        <div id="app"></div>
    </div>
    <script src="/js/app.js"></script>
</body>
</html>

Now is a good time to test that everything builds and you have a Bootstrap styled page with with links to Home and Register in a horizontal navigation bar and a content panel under it with “Laravel 5” heading text. Select the Register link to diplay the form in the content panel.

gulp
Laravel JWT Auth Vuejs User Registration Form
New User Registration Form

The next page covers Vue Resource for request handling, Form Request Validation, API authorization with JWT Auth, User endpoint and Sign in.


Laravel Install on Windows IIS

This post documents installing PHP 7, PHP Manager and Laravel 5.3 on Internet Information Services (IIS) Manager version 10 which ships with Windows 10 Pro. This Laravel Installation will also be configured to connect to a SQLite database.

Internet Information Services (IIS) Manager

IIS needs to be installed, open the Windows Features dialog to check the installation. One way to do this is by selecting the start button and type Windows Features to bring up a list where “Turn Windows features on or off” can be selected. Another way to get to this Control Panel app is Windows + R key combination and run appwiz.cpl. Turn Windows features on or off link should be in the upper left panel.

Windows Features - IIS and .NET Framework 3.5 enabled
Windows Features

Notice that under .NET Framework 3.5 that both Windows Communication Foundation features are enabled. This may be needed in order for a successful install of PHP Manager. Installation of PHP Manager for IIS requires .NET 3.5 to work properly.

PHP and PHP Manager

Install PHP 7 for Windows using the Microsoft Web Platform Installer. It’s an easy way to get both PHP and the PHP Manager installed and takes some of the guess work out of getting PHP up and running on Windows and IIS.

  • If PHP Manager Fails to install after confirming that .NET 3.5 is installed and enabled, this is likely due to the installer throwing an error when checking the IIS version. Change the W3SVC MajorVersion value to 7 (decimal) in the registry. After the install is completed, change the value back to 10. More info is available at the PHP Manager – Refuses to install for WTP10 view issue page.

Registry Editor - HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters]

* YMMV, I am not responsible if you hose your system when editing the registry.

PHP Extensions

In the Internet Information Services (IIS) Manager, Open PHP Manager and select the Enable or disable an extension link. Ensure that the extensions needed for Laravel are enabled. If you want to be able to connect to a SQLite database, enable the php_sqlite3.dll PHP extension. The figure below shows the PHP extensions I have enabled for a working Laravel 5 installation.

PHP Extensions provide additional functionality to PHP runtime, such as database connectivity, caching, debugging and others. Use this feature to enable or disable PHP extensions and to configure PHP extensions settings.
PHP Manager – PHP Extensions

Composer for Windows

Download and run Composer-Setup.exe Windows Installer. The installer will download Composer and set up the PATH environment variable.

Using the Windows Command Prompt, make sure Composer for Windows is installed by running the composer –version command.

composer --version

Git for Windows

Git for Windows is needed so Composer for Windows can download packages. During the installation process, Adjusting your PATH environment, make sure the option, Use Git from the Windows Command Prompt is enabled.

Using the Windows Command Prompt, make sure Git for Windows is installed by running the git –version command.

git --version

Create Laravel Project

Bring up the Adminstrator Command Prompt. One way to do this is by selecting the start button and type command to bring up a list where “Command Prompt” can be selected. Right click on Command Prompt and select Run as administrator .

cd c:/intepub

composer create-project laravel/laravel laravel "5.3.*"

Add Website

Open Internet Information Services (IIS) Manager. Right click on the server and select Add Website. Fill out the form as follows:
Site name: Laravel
Application pool: DefaultAppPool
Physical path: C:\inetpub\laravel\public
Host name: laravel.win

Select “Test Settings” and then “OK” if successful.

Add Website
IIS – Add Website

Hosts Mapping

Since the Host name laravel.win was entered for the website, the hosts file needs to be updated. Open Notepad as an administrator. One way to do this is by selecting the start button and type Notepad to bring up a list where it can be selected. Right click on Notepad and select Run as administrator.

Select File | Open, or Ctrl + O and change the File type from Text Documents (*.txt) to All Files (*.*). Browse to C:\Windows\System32\drivers\etc and select the hosts file. Add an entry to map localhost to laravel.win as follows.

hosts
127.0.0.1   localhost
127.0.0.1   laravel.win

Laravel Storage Permissions

In File Explorer, right click on the storage folder in C:\inetpub\laravel and select Properties. Under the Security tab, grant full control of the storage folder to IUSR as shown in the figure below.

Properties Dialog - Security
Permissions for storage
Laravel web.config

Since IIS does not have an .htaccess file like Apache, create a web.config file in C:\inetpub\laravel\public as follows. *

web.config
<configuration>
    <system.webServer>
        <defaultDocument>
            <files>
                <clear />
                <add value="index.php" />
                <add value="default.aspx" />
                <add value="Default.htm" />
                <add value="Default.asp" />
                <add value="index.htm" />
                <add value="index.html" />
            </files>
        </defaultDocument>
        <rewrite>
            <rules>
                <rule name="Imported Rule 1" stopProcessing="true">
                    <match url="^(.*)/$" ignoreCase="false" />
                    <conditions>
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
                    </conditions>
                    <action type="Redirect" redirectType="Permanent" url="/{R:1}" />
                </rule>
                <rule name="Imported Rule 2" stopProcessing="true">
                    <match url="^" ignoreCase="false" />
                    <conditions>
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="index.php" />
                </rule>
            </rules>
        </rewrite>
        <httpErrors errorMode="Detailed" />
    </system.webServer>
</configuration>

* The rewrite rule definitions in the web.config require the URL Rewite 2.0 extension. For easy installation, use the Free Web Platform Installer.

Laravel SQLite Database

Create a SQLite database * in C:\inetpub\laravel\database. Edit C:\inetpub\laravel\.env database values to configure the database connection.

.env
DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=database/laravel.sqlite
DB_USERNAME=
DB_PASSWORD=

* DB Browser for SQLite is an open source, freeware tool used to create, design and edit SQLite database files.

Restart IIS

In an Administrative Command Prompt, restart IIS so all of the changes get applied.

iisreset /restart

In IIS, make sure that the Larvel web site has been started. If the W3SVC service is not running, it can be started with the following command.

net start w3svc

Once all is said and done, load http://laravel.win in a web browser. Laravel 5.3 default page should look similar to the screen shot below.

Laravel 5.3 Default Screen
Laravel 5.3

Laravel User Registration with Email Activation

This post documents how to add an e-mail confirmation to the Laravel User registration that is generated by the Artisan console. In this example, the registered user will not be able to login until the activation link that is sent to the users registered e-mail account has been loaded into the browser.

Requirements

An instance of Laravel 5.2 setup for local development with authentication scaffolding is required. Refer to the first page of my recent post on Laravel User Authentication with Ajax Validation for more information on installing Laravel 5.2 using Composer and generating the authentication scaffolding with artisan.

User Model

I like placing model classes in their own directory. This can be done with just a few minor changes after moving the User class into a new app/Models folder.

mkdir app/Models

mv app/User.php app/Models

Edit app/Models/User.php, update the App namespace to App\Models.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    ...

  • Throughout this tutorial you will encounter an ellipsis … in the code examples. These are not a part of the code and are there only to denote code that is being skipped and not applicable to the example. To view the entire file, checkout the email-activation branch in the source code.

Edit app/Http/Controllers/Auth/AuthController.php, update use App\User to use App\Models\User.

AuthController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    ...

Edit, config/auth.php, update the authentication drivers user provider.

'users' => [
    'driver' => 'eloquent',
    'model' => App\User::class,
],
REPLACE
'model' => App\User::class,
WITH
'model' => App\Models\User::class,

Database Migration Files

Generate a database migration file for creating the user_activations table. This table stores the activation codes until they are used.

create_user_activations_table
php artisan make:migration create_user_activations_table --create=user_activations

Edit the migration file up function to create these three fields in the user_activations table.

public function up()
{
    Schema::create('user_activations', function (Blueprint $table) {
        $table->integer('user_id')->unsigned();
        $table->string('token')->index();
        $table->timestamp('created_at');
    });
}
alter_users_table_add_activated_col

Generate a database migration file for adding a column to the users table.

php artisan make:migration alter_users_table_add_activated_col

Edit the migration file up function to add the activated field to the users table.

public function up()
{
    Schema::table('users', function($table) {
        $table->boolean('activated')->default(false);
    });
}

Run the migration

php artisan migrate

ActivationRepository Class

Create a Repositories folder in the app directory

mkdir app/Repositories

Create the ActivationRepository class file in app/Repositories. This class contains functions for generating activation tokens and updating the tables in the database.

ActivationRepository.php
<?php

namespace App\Repositories;

use Carbon\Carbon;
use Illuminate\Database\Connection;

class ActivationRepository
{
    protected $db;
    protected $table = 'user_activations';

    public function __construct(Connection $db)
    {
        $this->db = $db;
    }

    public function getActivation($user)
    {
        return $this->db->table($this->table)->where('user_id', $user->id)->first();
    }

    public function getActivationByToken($token)
    {
        return $this->db->table($this->table)->where('token', $token)->first();
    }

    public function deleteActivation($token)
    {
        $this->db->table($this->table)->where('token', $token)->delete();
    }

    public function createActivation($user)
    {
        $activation = $this->getActivation($user);

        if (!$activation) {
            return $this->createToken($user);
        }
        return $this->regenerateToken($user);
    }

    protected function getToken()
    {
        return hash_hmac('sha256', str_random(40), config('app.key'));
    }

    private function regenerateToken($user)
    {
        $token = $this->getToken();
        $this->db->table($this->table)->where('user_id', $user->id)->update([
            'token' => $token,
            'created_at' => new Carbon()
        ]);
        return $token;
    }

    private function createToken($user)
    {
        $token = $this->getToken();
        $this->db->table($this->table)->insert([
            'user_id' => $user->id,
            'token' => $token,
            'created_at' => new Carbon()
        ]);
        return $token;
    }
}

ActivationFactory Class

Create a Factories folder in the app directory

mkdir app/Factories

Create the ActivationFactory class file in app/Factories.

ActivationFactory.php
<?php

namespace App\Factories;

use App\Models\User;
use App\Repositories\ActivationRepository;
use Illuminate\Mail\Mailer;
use Illuminate\Mail\Message;

class ActivationFactory
{
    protected $activationRepo;
    protected $mailer;
    protected $resendAfter = 24;

    public function __construct(ActivationRepository $activationRepo, Mailer $mailer)
    {
        $this->activationRepo = $activationRepo;
        $this->mailer = $mailer;
    }

    public function sendActivationMail($user)
    {
        if ($user->activated || !$this->shouldSend($user)) {
            return;
        }

        $token = $this->activationRepo->createActivation($user);

        $link = route('user.activate', $token);
        $message = sprintf('Activate account %s', $link, $link);

        $this->mailer->raw($message, function (Message $m) use ($user) {
            $m->to($user->email)->subject('Activation mail');
        });
    }

    public function activateUser($token)
    {
        $activation = $this->activationRepo->getActivationByToken($token);

        if ($activation === null) {
            return null;
        }

        $user = User::find($activation->user_id);

        $user->activated = true;

        $user->save();

        $this->activationRepo->deleteActivation($token);

        return $user;
    }

    private function shouldSend($user)
    {
        $activation = $this->activationRepo->getActivation($user);
        return $activation === null || strtotime($activation->created_at) + 60 * 60 * $this->resendAfter < time();
    }
}

Route

Create a route for the e-mail activation link in app/Http/routes.php. This user.activate route should be added after Route::auth()

routes.php
<?php
...
Route::auth();

Route::get('user/activation/{token}', 'Auth\AuthController@activateUser')->name('user.activate');

AuthController

Make the following updates to app/Http/Controllers/Auth/AuthController.php

REPLACE
use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
WITH
use App\Factories\ActivationFactory;
use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Illuminate\Http\Request;
REPLACE
public function __construct()
{
    $this->middleware($this->guestMiddleware(), ['except' => 'logout']);
}
WITH
protected $activationFactory;

public function __construct(ActivationFactory $activationFactory)
{
    $this->middleware($this->guestMiddleware(), ['except' => 'logout']);
    $this->activationFactory = $activationFactory;
}

At the bottom of the controller, add these three functions after the create function. The register function we are adding will override the register method inherited from the AuthenticatesAndRegistersUsers trait.

...

public function register(Request $request)
{
    $validator = $this->validator($request->all());

    if ($validator->fails()) {
        $this->throwValidationException(
            $request, $validator
        );
    }

    $user = $this->create($request->all());

    $this->activationFactory->sendActivationMail($user);

    return redirect('/login')->with('activationStatus', true);
}

public function activateUser($token)
{
    if ($user = $this->activationFactory->activateUser($token)) {
        auth()->login($user);
        return redirect($this->redirectPath());
    }
    abort(404);
}

public function authenticated(Request $request, $user)
{
    if (!$user->activated) {
        $this->activationFactory->sendActivationMail($user);
        auth()->logout();
        return back()->with('activationWarning', true);
    }
    return redirect()->intended($this->redirectPath());
}

Authentication Language Lines

For the activation status messages that we need to display to the user, update the resources/lang/en/auth.php file.

en/auth.php
<?php

return [

...

    'activationStatus' => 'We sent you an activation code. Please check your e-mail.',
    'activationWarning' => 'You need to confirm your account. We have sent you an activation code, please check your e-mail.',

...

];

Add the following inside the form at resources/views/auth/login.blade.php Blade view to render the activation messages.

login.blade.php
...

@if (session('activationStatus'))
    <div class="alert alert-success">
        {{ trans('auth.activationStatus') }}
    </div>
@endif

@if (session('activationWarning'))
    <div class="alert alert-warning">
        {{ trans('auth.activationWarning') }}
    </div>
@endif

Source Code

Laravel User Authentication with Ajax Validation

This post documents how to add Ajax form validation to Laravel User Authentication that is generated by the Artisan console.

Requirements

An instance of Laravel 5.2 setup for local development is required. Head over to the Laravel 5.2 docs to get setup as needed. For my setup on Windows 10, I am using XAMPP 5.6.12 which meets all of the requirements. Note that your document root will need to be the laravel/public directory. Here is the virtual hosts configuration I am using for Laravel.

httpd-vhosts.conf
<VirtualHost *:8080>
    DocumentRoot "C:/xampp/htdocs/laravel/public"
    ServerName laravel.dev
    ServerAlias www.laravel.dev
    SetEnv APPLICATION_ENV development
    <Directory "C:/xampp/htdocs/laravel">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>

More info on setting up XAMPP for Windows

New Laravel Site

Using Composer, install Laravel 5.2 into htdocs/laravel

cd c:/xampp/htdocs

composer create-project laravel/laravel laravel "5.2.*"
Generate Authentication Scaffolding
cd laravel

php artisan make:auth

Now the Laravel site has routes and views to allow a user to register, login, logout, and reset their password. A HomeController has also been added to allow authenticated users to enter its view.

User Model

I prefer to organize models within their own directory. Therfore, let’s create a folder named Models in the app directory and move the User model into it.

mkdir app/Models

mv app/User.php app/Models

Edit app/Models/User.php, update the App namespace to App\Models.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    ...

  • Throughout this tutorial you will encounter an ellipsis … in the code examples. These are not a part of the code and are there only to denote code that is being skipped and not applicable to the example. To view the entire file, examine the source code.

Edit app/Http/Controllers/Auth/AuthController.php, update use App\User to use App\Models\User.

AuthController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    ...

Edit, config/auth.php, update the authentication drivers user provider.

'users' => [
    'driver' => 'eloquent',
    'model' => App\User::class,
],
REPLACE
'model' => App\User::class,
WITH
'model' => App\Models\User::class,

Database

Edit the .env configuration file to access the database. I have already created a database named laravel on the MySQL Server that is part of XAMPP.

.env
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

The settings above are the only ones I needed to update for my setup, I do not have a password set for MySQL Server and will just use the root account for local development. Now run php artisan migrate to create the tables for users.

Run Migration
php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table

Validation Rules

The validation rules are currently located in the AuthController, I would like to be able to share them with the new controller we will create later for ajax validation. Since these rules apply to the User, and that model is already being used by the AuthController, it seeems to be the best place for them.

Edit app/Models/User.php, adding the public function rules() to the class.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    /**
     * Get the validation rules that apply to the user.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6|confirmed',
        ];
    }

    ...

Edit app/Http/Controllers/Auth/AuthController.php. Locate the validator function and replace the array of rules being passed to the Validator make method with a call to the newly created rules method in our User model.

REPLACE
return Validator::make($data, [
    'name' => 'required|max:255',
    'email' => 'required|email|max:255|unique:users',
    'password' => 'required|min:6|confirmed',
]);
WITH
return Validator::make($data, (new User)->rules());

Before going further, now would be a good time to test registering a new user, login, logout etc.. to make sure everyting works. All we have done so far aside from the code that artisan generated is move the User model and move the validation rules from the AuthController into it.

The next page covers using the api middleware in routes, creating the Validation\UserController for handling the XHR request and finally updating the templates and adding the JavaScript for Ajax.


Slim 3 MVC

This post documents a Slim 3 PHP micro framework application with models, views and controllers (MVC) using Twig templates to display data from a database.

Requirements

* I am using XAMPP on Windows as my web server with URL rewriting.

Setup

Clone or download the source code from GitHub.

Source Code

After Composer had been installed, using a command shell, navigate to the directory where the source code has been cloned or extracted to. In the project root is a composer.json package file. This file defines the project requirements. Run the composer require command to install the project dependencies read from the composer.json package configuration into the project.

# install slim and it's dependencies
composer require slim/slim "^3.0"

The included composer.json package file also contains an autoload property for PSR-4 autoloading support in Composer. This will map the App namespace to the relative path specified for the classes.

composer.json
{
    "require": {
        "slim/slim": "^3.0",
        "slim/twig-view": "^2.1",
        "php-di/slim-bridge": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src\\classes"
        }
    }
}
# update and optimize the autoloader
composer dump-autoload -o

There are two versions of this starter application:

  1. tag oobdic – Out of the box dependency injection configuration, uses Slim’s built-in dependency container.
  2. tag phpdiPHP-DI/Slim-Bridge dependency injection into the controller. This is the latest version of the starter app.
  • To install PHP-DI/Slim-Bridge in an existing project, require php-di/slim-bridge using composer.
composer require php-di/slim-bridge

Changing the Database Connection String

The connection string is currently set to connect to the SQLite demo.db database which makes it easy to include a database with the project.

App.php

\App\Database\DatabaseInterface::class => function (ContainerInterface $container) {
    return new \App\Database\PDODatabase('sqlite:../data/demo.db');
}

Here is the container definition with the connection string changed for a MySQL database.


\App\Database\DatabaseInterface::class => function (ContainerInterface $container) {
    return new \App\Database\PDODatabase('mysql:host=localhost;dbname=demo', 'username', 'password');
}

Visual Studio Code

My favorite feature so far is in the form of an extension. The vscode-php-debug extension works better than any free PHP debugging solution I have tried, such as Eclipse, Netbeans or SublimeTextXdebug. In order to use the debugger, the Xdebug PHP extension will need to be installed for your version of PHP. Here are some Xdebug installation resources:

User Settings

My VS Code user preferences – I am not thrilled with the existing VS Code Git integration, I prefer the command line tools for this, so I have it disabled in my settings. Another setting I changed from the default, "workbench.editor.enablePreview": true – When this is set to true, a subsequent file open will close the previous file if it has not been double clicked or edited. I wanted to keep the files open, so I set this to false.

~\AppData\Roaming\Code\User\settings.json
// Place your settings in this file to overwrite the default settings
{
    "editor.renderWhitespace": true,
    "editor.fontFamily": "Fira Code",
    "editor.fontLigatures": true,
    "editor.fontSize": 13,
    "editor.lineHeight": 22,

    "files.trimTrailingWhitespace": true,

    // Is git enabled
    "git.enabled": false,

    "window.zoomLevel": 0,

    "workbench.editor.enablePreview": false
}

Extensions

Here is a list of my favorite VS Code extensions


Resources

Xdebug for XAMPP on OS X

After discovering how nice the vscode-php-debug extension works in Visual Studio Code on my Windows 10 laptop, I wanted this same setup for a PHP project on my work Mac Book Pro running OS X Yosemite (10.10). The version of XAMPP I currently have installed on the Mac is 5.6.11-0 and for this tutorial, there are a few requirements to consider:

Requirements

Use homebrew to install the autoconf utility for producing configure scripts.

# install autoconf
$ brew install autoconf

phpize

XAMPP comes with phpize which we will want to utilize to prepare the build environment for the Xdebug PHP extension. If another version of phpize is installed, it will need to be renamed. Check to see if phpize is installed in /usr/bin:

$ cd /usr/bin
$ ls -al | grep phpize
-rwxr-xr-x     1 root   wheel      4508 Sep  9  2014 phpize

If the grep search returns phpize, similar to what is shown above, then rename it to phpize_bak.

# rename / backup phpize
$ sudo mv phpize phpize_bak

Create a new symbolic link in /usr/bin to target the XAMPP version of phpize.

# navigate to the /usr/bin directory
$ cd /usr/bin

# create symbolic link to XAMPP phpize
$ sudo ln -s /Applications/XAMPP/bin/phpize-5.6.11 phpize

# check phpize version
$ cd /
$ phpize -v
Configuring for:
PHP Api Version:         20131106
Zend Module Api No:      20131226
Zend Extension Api No:   220131226

phpize -v command output should be similar to what is shown above.

Xdebug Installation

Open the XAMPP phpinfo.php file in a web browser, for example, http://localhost/dashboard/phpinfo.php. In another browser window or tab, open https://xdebug.org/wizard.php and copy the phpinfo page content in the first window or tab and paste it into the textbox on the xdebug.org page. Then submit for analysis to determine the Xdebug source to download. The response should be a Summary and Tailored Installation Instructions.

Example Summary
  • Xdebug installed: no
  • Server API: Apache 2.0 Handler
  • Windows: no
  • Zend Server: no
  • PHP Version: 5.6.11
  • Zend API nr: 220131226
  • PHP API nr: 20131226
  • Debug Build: no
  • Thread Safe Build: no
  • Configuration File Path: /Applications/XAMPP/xamppfiles/etc
  • Configuration File: /Applications/XAMPP/xamppfiles/etc/php.ini
  • Extensions directory: /Applications/XAMPP/xamppfiles/lib/php/extensions/no-debug-non-zts-20131226
Example Instructions
  1. Download xdebug-2.4.0.tgz
    continued…

You can follow the Tailored Installation Instructions from xdebug.org or you can follow the instructions here that I have found to work well.

  1. Unpack the downloaded file
    # navigate to the downloaded file
    $ cd ~/Downloads
    
    $ tar -xvzf xdebug-2.4.0.tgz
    $ cd xdebug-2.4.0
    
  2. run phpize
    $ phpize
    
    # example output
    Configuring for:
    PHP Api Version:         20131106
    Zend Module Api No:      20131226
    Zend Extension Api No:   220131226
    
  3. Configure with XAMPP php-config.
    $ ./configure --enable-xdebug --with-php-config=/Applications/XAMPP/xamppfiles/bin/php-config
    
  4. run make
    $ make
  5. In the Tailored Installation Instructions from xdebug.org, locate the step after make that contains the cp command and paths for copying the xdebug.so extension file into XAMPP, for example,
    $ cp modules/xdebug.so /Applications/XAMPP/xamppfiles/lib/php/extensions/no-debug-non-zts-20131226
    
  6. Edit /Applications/XAMPP/xamppfiles/etc/php.ini
    After “;zend_extension”
    Add the zend_extension setting from the xdebug.org instructions, for example:
    zend_extension=/Applications/XAMPP/xamppfiles/lib/php/extensions/no-debug-non-zts-20131226/xdebug.so
    
  7. Additionally, add these xdebug configuration settings in php.ini after the zend_extension setting:
    xdebug.remote_enable=1
    xdebug.remote_handler=dbgp
    xdebug.remote_host=localhost
    xdebug.remote_autostart = 1
    xdebug.remote_port=9000
    xdebug.show_local_vars=1
    xdebug.remote_log=/Applications/XAMPP/logs/xdebug.log
    
    ;---------------------------------
    ; uncomment these dev environment
    ; specific settings as needed
    ;---------------------------------
    ;xdebug.idekey = "netbeans-xdebug"
    ;xdebug.idekey = "sublime.xdebug"
    
    ;some pages in your Drupal site will not work default = 100
    ;xdebug.max_nesting_level=256
    
    Optional – for development only
    Find “max_execution_time” and set to unlimited:
    max_execution_time=0
    
  8. Restart Apache using XAMPP’s manager-osx
  9. Reload http://localhost/dashboard/phpinfo.php and verify that xdebug section exists. Another verification method is to copy and paste the phpinfo into the form at https://xdebug.org/wizard.php and resubmit it for analysis.

WordPress Page Specific Styles or Scripts

I wanted an efficient way to load page or post specific stylesheets and or scripts which lead me to see if I could utilize custom fields for this. Using custom fields combined with wp_enqueue_style and wp_enqueue_script worked perfectly. This solution allows me to link css or javascript files for only the posts or pages I want.

1. Add a couple of custom fields, one for styles and one for scripts to a page using the editor:

Add bn_styles custom field
WordPress editor – Add bn_styles custom field
bn_styles and bn_scripts custom fields added
WordPress editor – bn_styles and bn_scripts custom fields added

Loading WordPress theme stylesheets and scripts is typically done within the functions.php file using wp_enqueue_style and wp_enqueue_script respectively. Using the Twenty Fifteen functions.php file as an example, take a look at the twentyfifteen_scripts function:

function twentyfifteen_scripts() {
    // Add custom fonts, used in the main stylesheet.
    wp_enqueue_style( 'twentyfifteen-fonts', twentyfifteen_fonts_url(), array(), null );

    // Add Genericons, used in the main stylesheet.
    wp_enqueue_style( 'genericons', get_template_directory_uri() . '/genericons/genericons.css', array(), '3.2' );

    // Load our main stylesheet.
    wp_enqueue_style( 'twentyfifteen-style', get_stylesheet_uri() );

    // Load the Internet Explorer specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie', get_template_directory_uri() . '/css/ie.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie', 'conditional', 'lt IE 9' );

    // Load the Internet Explorer 7 specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie7', get_template_directory_uri() . '/css/ie7.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie7', 'conditional', 'lt IE 8' );

    wp_enqueue_script( 'twentyfifteen-skip-link-focus-fix', get_template_directory_uri() . '/js/skip-link-focus-fix.js', array(), '20141010', true );

    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }

    if ( is_singular() && wp_attachment_is_image() ) {
        wp_enqueue_script( 'twentyfifteen-keyboard-image-navigation', get_template_directory_uri() . '/js/keyboard-image-navigation.js', array( 'jquery' ), '20141010' );
    }

    wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/functions.js', array( 'jquery' ), '20150330', true );
    //wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/main.js', array( 'jquery' ), '20150527', true );
    wp_localize_script( 'twentyfifteen-script', 'screenReaderText', array(
        'expand'   => '' . __( 'expand child menu', 'twentyfifteen' ) . '',
        'collapse' => '' . __( 'collapse child menu', 'twentyfifteen' ) . '',
    ) );
}
add_action( 'wp_enqueue_scripts', 'twentyfifteen_scripts' );

Edit functions.php

Add some code to the twentyfifteen_scripts function to get the custom field data and use it to load stylesheets and scripts.

2. At the top of the twentyfifteen_scripts function, add the get_post_custom function to retrieve the custom field data:

function twentyfifteen_scripts() {

    // retrieve custom fields from the current post
    $custom_fields = get_post_custom();

3. After the last stylesheet is queued for loading, add the code that checks for and loads our styles custom_field stylesheets. In this example, add the code after wp_style_add_data( ‘twentyfifteen-ie7’, ‘conditional’, ‘lt IE 8’ ); as shown here:

    wp_style_add_data( 'twentyfifteen-ie7', 'conditional', 'lt IE 8' );

    // styles custom_field
    if (!empty($custom_fields['bn_styles'])) {
        foreach ( $custom_fields['bn_styles'] as $key => $value ) {
            wp_enqueue_style('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/css/' . $value, false, null);
        }
    }

4. At the end of the twentyfifteen_scripts function, add the code that checks for and loads our scripts custom_field javascript as shown here:

    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, [], null, true);
        }
    }
}
add_action( 'wp_enqueue_scripts', 'twentyfifteen_scripts' );

5. Here is the entire twentyfifteen_scripts function after the modifications above:

function twentyfifteen_scripts() {

    // retrieve custom fields from the current post
    $custom_fields = get_post_custom();

    // Add custom fonts, used in the main stylesheet.
    wp_enqueue_style( 'twentyfifteen-fonts', twentyfifteen_fonts_url(), array(), null );

    // Add Genericons, used in the main stylesheet.
    wp_enqueue_style( 'genericons', get_template_directory_uri() . '/genericons/genericons.css', array(), '3.2' );

    // Load our main stylesheet.
    wp_enqueue_style( 'twentyfifteen-style', get_stylesheet_uri() );

    // Load the Internet Explorer specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie', get_template_directory_uri() . '/css/ie.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie', 'conditional', 'lt IE 9' );

    // Load the Internet Explorer 7 specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie7', get_template_directory_uri() . '/css/ie7.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie7', 'conditional', 'lt IE 8' );

    // styles custom_field
    if (!empty($custom_fields['bn_styles'])) {
        foreach ( $custom_fields['bn_styles'] as $key => $value ) {
            wp_enqueue_style('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/css/' . $value, false, null);
        }
    }

    wp_enqueue_script( 'twentyfifteen-skip-link-focus-fix', get_template_directory_uri() . '/js/skip-link-focus-fix.js', array(), '20141010', true );

    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }

    if ( is_singular() && wp_attachment_is_image() ) {
        wp_enqueue_script( 'twentyfifteen-keyboard-image-navigation', get_template_directory_uri() . '/js/keyboard-image-navigation.js', array( 'jquery' ), '20141010' );
    }

    wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/functions.js', array( 'jquery' ), '20150330', true );
    //wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/main.js', array( 'jquery' ), '20150527', true );
    wp_localize_script( 'twentyfifteen-script', 'screenReaderText', array(
        'expand'   => '' . __( 'expand child menu', 'twentyfifteen' ) . '',
        'collapse' => '' . __( 'collapse child menu', 'twentyfifteen' ) . '',
    ) );

    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, [], null, true);
        }
    }
}
add_action( 'wp_enqueue_scripts', 'twentyfifteen_scripts' );

Handle Script Dependencies

A nice feature of the wp_enqueue_script function is that it allows us to pass in dependencies as a parameter. The optional dependencies parameter consists of an array of handles that map to javascript files. To apply this feature to our scripts custom field code, we need to create a $deps array that will retrieve optional handles added to the custom field value. Replace the code block at the bottom of function twentyfifteen_scripts() as follows:

REPLACE
    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, [], null, true);
        }
    }
WITH
    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            $script = explode(',', $value);
            $deps = array();
            foreach ( $script as $i => $v ) {
                if ($i > 0) { // values after $script[0]
                    $deps[] = trim($v);
                }
            }
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, $deps, null, true);
        }
    }

For a detailed list of handle names that can be used, see Handles and Their Script Paths Registered by WordPress.

6. Here is the entire twentyfifteen_scripts function after updating the scripts custom field parser to handle script dependencies:

function twentyfifteen_scripts() {

    // retrieve custom fields from the current post
    $custom_fields = get_post_custom();

    // Add custom fonts, used in the main stylesheet.
    wp_enqueue_style( 'twentyfifteen-fonts', twentyfifteen_fonts_url(), array(), null );

    // Add Genericons, used in the main stylesheet.
    wp_enqueue_style( 'genericons', get_template_directory_uri() . '/genericons/genericons.css', array(), '3.2' );

    // Load our main stylesheet.
    wp_enqueue_style( 'twentyfifteen-style', get_stylesheet_uri() );

    // Load the Internet Explorer specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie', get_template_directory_uri() . '/css/ie.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie', 'conditional', 'lt IE 9' );

    // Load the Internet Explorer 7 specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie7', get_template_directory_uri() . '/css/ie7.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie7', 'conditional', 'lt IE 8' );

    // styles custom_field
    if (!empty($custom_fields['bn_styles'])) {
        foreach ( $custom_fields['bn_styles'] as $key => $value ) {
            wp_enqueue_style('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/css/' . $value, false, null);
        }
    }

    wp_enqueue_script( 'twentyfifteen-skip-link-focus-fix', get_template_directory_uri() . '/js/skip-link-focus-fix.js', array(), '20141010', true );

    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }

    if ( is_singular() && wp_attachment_is_image() ) {
        wp_enqueue_script( 'twentyfifteen-keyboard-image-navigation', get_template_directory_uri() . '/js/keyboard-image-navigation.js', array( 'jquery' ), '20141010' );
    }

    wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/functions.js', array( 'jquery' ), '20150330', true );
    //wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/main.js', array( 'jquery' ), '20150527', true );
    wp_localize_script( 'twentyfifteen-script', 'screenReaderText', array(
        'expand'   => '' . __( 'expand child menu', 'twentyfifteen' ) . '',
        'collapse' => '' . __( 'collapse child menu', 'twentyfifteen' ) . '',
    ) );

    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            $script = explode(',', $value);
            $deps = array();
            foreach ( $script as $i => $v ) {
                if ($i > 0) { // values after $script[0]
                    $deps[] = trim($v);
                }
            }
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, $deps, null, true);
        }
    }
}
add_action( 'wp_enqueue_scripts', 'twentyfifteen_scripts' );
This code is also available at gist.github.com

7. Now the bn_scripts custom field can be updated to include dependencies. List the dependency handles after the script separated by a comma as shown below.

bn_styles and bn_scripts custom fields added
WordPress editor – bn_scripts custom field dependencies added

More often than not jQuery will already be loaded. However, if your script is dependent upon jQuery you should add its handle to the dependencies comma separated list in the bn_scripts custom field value. wp_enqueue_script prevents double loading of scripts and their dependencies.

Given the default theme location, if you view source of the page that has the custom fields added and search for tree.css or tree.js, they should be linked to /wp-content/themes/twentyfifteen/css/tree.css and /wp-content/themes/twentyfifteen/js/tree.js. Any dependencies will also be linked.


Bourboneat version

If you would like to explore using this code with the bourboneat WordPress starter theme, here is the modified /functions/asset.php assets function that is used to enqueue stylesheets and scripts:

function assets() {
    wp_enqueue_style('bn_css', asset_path('styles/main.css'), false, null);

    $custom_fields = get_post_custom();

    // styles custom_field
    if (!empty($custom_fields['bn_styles'])) {
        foreach ( $custom_fields['bn_styles'] as $key => $value ) {
            wp_enqueue_style('bn_custom_field_' . $key, asset_path('styles/' . $value), false, null);           
        }
    }

    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }

    //wp_enqueue_script('modernizr', asset_path('scripts/modernizr.js'), [], null, true);
    wp_enqueue_script('bn_js', asset_path('scripts/main.js'), ['jquery'], null, true);

    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            $script = explode(',', $value);
            $deps = array();
            foreach ( $script as $i => $v ) {
                if ($i > 0) { // values after $script[0]
                    $deps[] = trim($v);
                }
            }
            wp_enqueue_script('bn_custom_field_' . $key, asset_path('scripts/' . $script[0] ), $deps, null, true);
        }
    }
}

XAMPP Windows Setup

Documenting my XAMPP for Windows setup for myself and anyone else who finds it useful. For those of you that do not know, XAMPP is a completely free, easy to install Apache distribution containing MySQL, PHP, and Perl. I still have my Virtual Machine for LAMP Development which I will use for test deployments of my localhost LAMP development prior to production deployment. This local XAMPP setup will be better for debugging, working with node modules and grunt tasks for preparing the deployment build.

XAMPP Configuration

Since my Windows 8.1 system is already using port 80 for IIS, I decided to setup XAMMP to use port 8080.

C:\xampp\apache\conf\httpd.conf
# on or near line 58
# change listen to 8080
Listen 8080

# on or near line 219
ServerName localhost:8080
C:\xampp\apache\conf\extra\httpd-vhosts.conf
# virtual hosts
NameVirtualHost *:8080

<VirtualHost *:8080>
  DocumentRoot C:/xampp/htdocs
  ServerName localhost
</VirtualHost>

# For htaccess rewrites to work AllowOverride
# needs to be set to All instead of None
<VirtualHost *:8080>
  DocumentRoot "C:/xampp/htdocs/mysite"
  ServerName mysite.dev
  ServerAlias www.mysite.dev
  SetEnv APPLICATION_ENV development
  <Directory "C:/xampp/htdocs/mysite">
    Options Indexes MultiViews FollowSymLinks
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>
</VirtualHost>

If you want to use a localhost tunneling solution such as pagekite to access your virtual host, change the ServerAlias to the URL your localhost tunneling provider has enabled. For example mysite.pagekite.me

C:\xampp\apache\conf\extra\httpd-vhosts.conf
# pagekite.me server alias
<VirtualHost *:8080>
  DocumentRoot "C:/xampp/htdocs/mysite"
  ServerName mysite.dev
  ServerAlias mysite.pagekite.me
  SetEnv APPLICATION_ENV development
  <Directory "C:/xampp/htdocs/mysite">
    Options Indexes MultiViews FollowSymLinks
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>
</VirtualHost>
C:\xampp\xampp-control.ini
# add to bottom so when
# control panel starts there
# are not Apache port errors
[ServicePorts]
Apache=8080
C:\Windows\System32\drivers\etc\hosts
# port numbers not allowed
127.0.0.1   localhost
127.0.0.1   mysite.dev

PHP Configuration

C:\xampp\php\php.ini
# enable PHP short open tags
short_open_tag=On

# development only
max_execution_time = 0

# XDebug
# uncomment these lines
zend_extension = "C:\xampp\php\ext\php_xdebug.dll"
xdebug.remote_enable = 1
xdebug.remote_handler = "dbgp"
xdebug.remote_host = "127.0.0.1"

I use NetBeans for PHP development and prefer not having XDEBUG_SESSION_START=netbeans-xdebug querystring tacked onto my URL’s in order to debug.

C:\xampp\php\php.ini
# more xdebug settings
xdebug.remote_autostart = 1
xdebug.idekey = "netbeans-xdebug"

After starting up Apache in the XAMPP Control Panel, loading the site in the browser is now as easy as http://mysite.dev:8080/

XAMPP Windows Update

If you want to install a new version of XAMPP, the installer will not allow you to install over an existing copy. Follow these steps to:

  • backup the existing XAMPP installation
  • install the new version of XAMPP
  • copy config and mysql data into the new version
  1. Launch the XAMPP control panel and Stop all services.
  2. Quit the control panel
  3. Rename the installation folder, for example, change it from C:\xampp to C:\xampp_orig
  4. Create a new folder with the same name as the original installation. For example C:\xampp
  5. Install the new version of XAMPP
  6. Copy your website folders from C:\xampp_orig\htdocs\ to C:\xampp\htdocs\. Do not copy xampp and other folders that are installed with xampp, such as dashboard and webalizer.
  7. Copy any changes you made to C:\xampp_orig\apache\conf\httpd.conf and C:\xampp_orig\apache\conf\extra\httpd-vhosts.conf into the new versions of these apache configuration files.
  8. Create databases with the same names as before. A new folder for each database will be created along with its respective db.opt file.
  9. Copy C:\xampp_orig\mysql\data\ibdata1 to C:\xampp\mysql\data\ibdata1
  10. Copy all of the files except for db.opt from each database folder in C:\xampp_orig\mysql\data\*\ to C:\xampp\mysql\data\*\. Make sure you do not copy the db.opt file(s).
  11. Launch XAMPP control panel and start apache and mysql

PHP

This page is a collection of various PHP code snippets.

Base64 Functions

/**
 * Decode base64 data
 *
 * @param  string  $data
 *
 */
function decode_base64($data) {

    $data = explode(',', $data, 2);

    $decoded = base64_decode($data[1]);

    return $decoded;
}

/**
 * Validate base64 data is image/jpeg
 *
 * @param  string  $data
 * @param  string  $mimetype
 *
 */
function validate_base64($data, $mimetype) {

    if ('image/jpeg' == $mimetype)
    {
        $img = imagecreatefromstring($data);
        if (!$img) {
            return false;
        }

        imagejpeg($img, 'tmp.jpg');
        $info = getimagesize('tmp.jpg');

        unlink('tmp.jpg');

        if ($info[0] > 0 && $info[1] > 0 && $info['mime']) {
            return true;
        }

        return false;
    }
}
Example of how to use the base64 functions above:
$file = [];
$file['content'] = decode_base64($data);

// validate the decoded base64 string
if (!validate_base64($file['content'], 'image/jpeg'))
{
    // invalid base64 'image/jpeg'
    $error =  'INVALID_DATA';

    return false;
}

Request

/**
 * use the request path
 * to create an array of URL segments
 */
$request_path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', $request_path);

echo '<pre>';
print_r($request_path);
echo '</pre>';

/**
 * get the remaining characters after the last forward slash
 *
 * @param  string  $path
 *
 * @return string
 *
 */
function last_segment($path) {
    return substr($path, strrpos($path, "/")+1);
}

Date/Time Functions

/** 
 * to default date object to GMT, add this
 * either inside the function or outer scope
 */
date_default_timezone_set("GMT");

//return mysql format datetime string
/**
 * @param  object  $date
 *
 * @return string  mysql format datetime
 * @return bool (false) if error
 *
 */
function mysql_datetime($date) {
    return $date->format('Y-m-d H:i:s');
}

//return gmt date object
function strtodate($str) {
    $date = date_create();        
    return date_timestamp_set($date, strtotime($str));
}

String Functions

/**
 * is string null or empty
 */
function null_or_empty($str){
    return (!isset($str) || trim($str)==='');
}

/**
 * does string start with sub-string
 *
 * @param string $haystack a string
 * @param string $needle a sub-string to search for
 * @return bool
 *
 */
function startsWith($haystack, $needle)
{
    return !strncmp($haystack, $needle, strlen($needle));
}
/**
 * does string end with sub-string
 */
function endsWith($haystack, $needle)
{
    $length = strlen($needle);
    if ($length == 0) {
        return true;
    }
    return (substr($haystack, -$length) === $needle);
}

/**
 * replace first occurrence of sub-string  
 *
 * @param string $search a sub-string to search for
 * @param string $replace a sub-string to replace sub-string with
 * @param string $str a string
 * @return string
 * 
 */
function str_replace_first($search, $replace, $str) {
    $pos = strpos($str, $search);
    if ($pos !== false) {
    	$str = substr_replace($str, $replace, $pos, strlen($search));
    }
    return $str;
}

/**
 * replace last occurrence of sub-string  
 * same as str_replace_first above with words reversed
 */
function str_replace_last($search, $replace, $str) {
    //reverse the words
    $str = implode(' ',array_reverse(explode(' ',$str)));
    $pos = strpos($str, $search);
    if ($pos !== false) {
    	$str = substr_replace($str, $replace, $pos, strlen($search));
    }
    return $str;
}

//remove adjacent hyphens, return single hyphens
function rm_adj_hyphen($str) {
    trim(preg_replace('/-+/', '-', $str), '-');
}
    
//remove adjacent spaces, return single spaces
function rm_adj_space($str) {    
    preg_replace('!\s+!', ' ', $str);
}

//remove numbers from a string    
function rm_number($str) {
    return preg_replace('/[0-9]+/', '', $str);
}

//truncate to word limit and append ellipsis by default
//if no trailing ellipsis desired, pass empty string for last parameter
function limit_words($str, $max_words, $append = '…') {
   $str_array = explode(' ',$str);
   if(count($str_array) > $max_words && $max_words > 0)
      $str = implode(' ',array_slice($str_array, 0, $max_words)).$append;
   return $str;
}

Virtual Machine for LAMP Development

This blog post addresses a common scenario — a local LAMP development environment on your Windows or OS X computer. You could use XAMPP on either Windows or OS X, MAMP on OS X or native Apache in OS X. Here, we will create a Linux virtual machine with Samba configured to share the Virtual Machine file system with the host computer. We will also create virtual hosts, install and configure WordPress and Xdebug.

First thing you will need is Virtual Machine software. For my Virtual Machine, I chose to install the free VMware Player. If your host operating system is OS X, you can install VirtualBox if you do not want to purchase VMware Fusion or Parallels. You could also use VirtualBox on a Windows host instead of VMware if you prefer.

Next, you will need to create a Linux virtual machine. I decided to Download Ubuntu Server 32 bit since it is compatible with the intel processor of my host computer.

Install SSH

After creating a new VM and installing Linux, time to get our dev environment setup. Install SSH so we can run commands from the host. Later we will use ssh to to tunnel the Xdebug connection from the VM back to the host.

# update the distro
$ apt-get update
$ apt-get upgrade -y

# install SSH
$ sudo apt-get -y install openssh-client openssh-server

Get the IP address of the Linux virtual machine.

# lookup inet addr ip 
$ ifconfig eth0

From the host, open a terminal and SSH into the virtual machine. In a Windows host, you can use Cygwin, PuTTY or a Git Bash.

$ ssh jim@192.168.59.129
...
jim@192.168.59.129's password:
Welcome to Ubuntu 12.04.3 LTS (GNU/Linux 3.8.0-29-generic i686)

 * Documentation:  https://help.ubuntu.com/
Last login: Sat Nov  2 12:55:45 2013
jim@ubuntu:~$

Install LAMP server

Read my blog post for more on how to install the LAMP server and phpMyAdmin: Mint LAMP Development Environment

# install LAMP server 
$ sudo apt-get install lamp-server^

Restart Apache and test the web server root URL from a host browser using the virtual machines IP address, e.g., http://192.168.59.129/.

$ sudo /etc/init.d/apache2 restart

Virtual Hosts

# edit the Apache config
$ sudo nano /etc/apache2/apache2.conf

# in apache2.conf, add these virtual host settings below the files node
NameVirtualHost *:80

<VirtualHost *:80>
   DocumentRoot /var/www
   ServerName www.ubuntu.vm
   ServerAlias ubuntu.vm
</VirtualHost>

<VirtualHost *:80>
   DocumentRoot /var/www/wordpress
   ServerName www.wordpress.vm
   ServerAlias wordpress.vm
</VirtualHost>

Edit your hosts file in your host operating system to map to the virtual hosts you specified in the Apache config of the Linux virtual machine.

192.168.59.129  ubuntu.vm
192.168.59.129  wordpress.vm

Since we have set the hosts file to map 192.168.59.129 to ubuntu.vm, once you disconnect your current ssh session (Ctrl+D), you will need to use the host alias you specified in the hosts file to reconnect.

ssh jim@ubuntu.vm

Samba Network Share

You can use Samba to share the /var folder in you virtual machine.

# install Samba 
$ sudo apt-get install samba

Linux system permissions take precedence over Samba permissions. For example if a directory does not have Linux write permission, setting samba writeable = Yes will not allow to write to shared directory / share.

# edit Samba config 
$ sudo nano /etc/samba/smb.conf

####### Authentication #######
  security = user

# bottom of smb.conf
[etc]
    path = /etc
    browsable = yes 
    read only = no 
    create mask = 0755 
    admin users = jim

[home]
    path = /home
    browsable = yes 
    read only = no 
    create mask = 0755 
    admin users = jim

[var]
    path = /var
    browsable = yes 
    read only = no 
    create mask = 0755 
    admin users = jim 

Ctrl+C to exit nano, press Y to save and return to write your changes to the file.

Install the PAM authentication module for Samba which will sync the system users to the Samba user database.

# Install libpam-smbpass package
$ sudo apt-get install libpam-smbpass

# restart Samba for the new settings to take effect
$ sudo restart smbd
$ sudo restart nmbd

# set user jim as the owner of share directory
$ sudo chown -R jim /var/www

For OS X, open Finder, select Go > Connect to Server (command + K). Enter the server address: smb://ubuntu.vm and select Connect

OS X - Connect to Server Dialog
OS X – Connect to Server Dialog

For Windows, select Run from the Start menu or open Windows Explorer and enter:

\\ubuntu.vm\www
Windows Run Dialog
Windows Run Dialog

Server Time Synchronisation

Server time is likely to become inaccurate in our development scenario. We can use Ubuntu’s NTP server and NTP’s server pool to keep the server datetime accurate.

# install ntp
$ sudo apt-get install ntp

# edit /etc/ntp.conf
$ sudo nano /etc/ntp.conf
# add these two lines
server ntp.ubuntu.com
server pool.ntp.org

# change server time zone
$ sudo dpkg-reconfigure tzdata

Enabling Module Rewrite

The mod_rewrite Apache module is a rule-based rewriting engine to rewrite requested URLs. It is very useful for genarating SEO friendly URLs.

# enable the module 
$ sudo a2enmod rewrite

# restart apache to activate the configuration change
$ sudo service apache2 restart

Install WordPress

# download the latest WordPress to a downloads directory
$ mkdir ~/downloads
$ cd ~/downloads

$ wget http://wordpress.org/latest.tar.gz

# -z : Uncompress the resulting archive with gzip command.
# -x : Extract to disk from the archive.
# -v : Produce verbose output i.e. show progress and file names while extracting files.
# -f latest.tar.gz : Read the archive from the specified file.
# -C : To change the directory (current directory is default)

# this will create /var/www/wordpress since the archive root directory is wordpress 
$ sudo tar -zxvf latest.tar.gz -C /var/www

# make yourself the owner
sudo chown jim /var/www/wordpress

Create Database

Create MySql database, user and grant permissions in one line.
base_user is typically root
base_user_pass is password for base_user (no space between -p and base_user_pass is important)

mysql -u base_user -pbase_user_pass -e "create database new_db; GRANT ALL PRIVILEGES ON new_db.* TO new_db_user@localhost IDENTIFIED BY 'new_db_user_pass'"

phpMyAdmin

If phpMyAdmin is installed, you can use it to create the wordpress database as described in this next section. Read my earlier blog post if you need help doing this, Mint LAMP Development Environment

In the browser, goto http://ubuntu.vm/phpmyadmin/. Select the Databases tab and under “Create new database”, enter in a database name such as “wordpress”, and select Create. Collation will automatically be assigned by MySQL when the database tables are created, during the WordPress installation.

# copy WordPress sample config
$ cd /var/www/wordpress
$ cp wp-config-sample.php wp-config.php

In the browser, goto http://wordpress.vm and start the installation. If you need to manually edit the wp-config.php file, you can do so with nano.

$ nano wp-config.php 

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'database_name_here');

/** MySQL database username */
define('DB_USER', 'username_here');

/** MySQL database password */
define('DB_PASSWORD', 'password_here');

/** MySQL hostname */
define('DB_HOST', 'localhost');

After saving wp-config, complete the WordPress installation at http://wordpress.vm

You can backup the MySql database using mysqldump within your ssh session. For example, replace mysql_username with your username and mysql_password with your password:

$ mysqldump -h localhost -u mysql_username -pmysql_password wordpress > /home/wordpress-$(date +"%Y-%m-%d").sql

WordPress Revisions Cleanup

Run this sql query in phpMyAdmin to delete all revisions from each post, including all of its meta data.

DELETE a,b,c
FROM wp_posts a
LEFT JOIN wp_term_relationships b ON (a.ID = b.object_id)
LEFT JOIN wp_postmeta c ON (a.ID = c.post_id)
WHERE a.post_type = 'revision'

Xdebug

$ sudo apt-get install php5-xdebug

Check Xdebug

$ php --version

PHP 5.3.10-1ubuntu3.8 with Suhosin-Patch (cli) (built: Sep  4 2013 20:05:42)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies
    with Xdebug v2.1.0, Copyright (c) 2002-2010, by Derick Rethans

Configure Xdebug

$ sudo nano /etc/php5/conf.d/xdebug.ini

Add the following xdebug config values after the zend_extension path value so it looks like this.

zend_extension=/usr/lib/php5/20090626+lfs/xdebug.so
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000

When debugging from the host computer, use ssh to tunnel the debug-connection back to it.

# ssh remote port forwarding
ssh -R 9000:localhost:9000 jim@ubuntu.vm

Using the “-R” argument tells ssh to listen on TCP port 9000 on the ubuntu server and forward it back through the ssh connection to the TCP port specified (localhost:9000). So from Xdebug’s perspective on the Ubuntu server, it is talking to localhost on the local computer.

More info: Mint LAMP Development Environment – Part Two | Xdebug

Other PHP debugging options

You could use the FirePHP Extension for Firefox to log to your Firebug Console using a simple PHP method call. There is also a SublimeTextXdebug package for Sublime Text.

If all you have is ssh and nano

<?php var_dump(__FILE__, __LINE__, $_REQUEST); ?>
or
<?php
echo '<pre>';
print_r( $somevariable );
echo '</pre>';
?>

Resources

Mint LAMP Development Environment – Part Two

Linux Mint 15 ‘Olivia’

I decided it was time to ditch my Linux 13 KDE setup and try the improved MATE 1.6 desktop now that Linux Mint 15 released. I find it pretty slick and fast, a nice change from Windows when I decide to do some development in a true LAMP environment. Speaking of which, we need to get that installed. Before doing that, the first thing I like to do is open up a terminal and run:

sudo apt-get update

This will download the package lists from the repositories and update them with information on the latest package versions. If you have not installed LAMP (Linux, Apache, MySQL & PHP) please read my earlier blog post, Mint LAMP Development Environment. Then you can return to this post to add an IDE with debugging capabilities along with some other nice tools.

Xdebug

sudo apt-get install php5-xdebug

Check Xdebug

php --version
PHP 5.4.9-4ubuntu2 (cli) (built: Mar 11 2013 16:09:26) 
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2012 Zend Technologies
    with Xdebug v2.2.1, Copyright (c) 2002-2012, by Derick Rethans

Configure Xdebug

sudo pluma /etc/php5/mods-available/xdebug.ini

Add the following xdebug config values after the zend_extension path value so it looks like this.

zend_extension=/usr/lib/php5/20100525/xdebug.so
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000

NetBeans

The IDE I prefer for Drupal development as of this writing is NetBeans. I found this post, Configuring NetBeans for Drupal development to be very helpful. In the Usage section of the post, the author suggests using the sites folder of your Drupal installation when creating the New Project > PHP Application with Existing Sources. This did not work for me, instead, I selected the root folder of my Drupal install as the Sources Folder for the NetBeans IDE project import. By including the entire Drupal installation in my NetBeans project, I could step through and debug any of the Drupal code including modules in the sites folder.

NetBeans IDE Resources

Eclipse

Download

As of this writing, the latest stable version of Eclipse was Eclipse Juno (4.2). I downloaded the Eclipse Classic 4.2.2 64 bit Linux version from http://www.eclipse.org/downloads/. Make sure you download the appropriate 32 or 64 bit version of Eclipse to match your Linux Mint operating system. To check which version of Linux Mint you are running, in a terminal, enter

uname -m

i686 = 32-bit; x86_64 = 64 bit

Install

Open up a terminal, change to the Downloads directory and and extract the archive.

cd ~/Downloads
tar xzf eclipse-SDK-4.2.2-linux-gtk-x86_64.tar.gz

With superuser permissions, move the extracted eclipse directory and its contents into the /opt/ folder.

sudo mv eclipse /opt/

Create a symbolic link so you can start Eclipse from a terminal

sudo ln -s /opt/eclipse/eclipse /usr/bin/eclipse

Launch eclipse

eclipse

or if working on files directly in /var/www/

sudo eclipse

PHP Development Tools (PDT)

To install latest stable PDT build in Eclipse, select Help > Install New Software and enter following URL in the work with input:
http://download.eclipse.org/tools/pdt/updates/release

Recoll

You could use the terminal to search for files with commands such as find, grep, and awk. However, I like the the Recoll desktop search tool.

Filezilla

When you are ready to deploy your development work to a remote server via FTP or SFTP, this cross-platform FTP client is my goto tool for the job.

sudo apt-get install filezilla

Resources

Mint LAMP Development Environment

OPERATING SYSTEM: Linux Mint 13 Maya

For Drupal development, since I already have a Linux Mint system setup, I decide to focus on it rather than the Windows 8 Acquia Drupal setup I blogged about a few days ago.

Apache, MySQL & PHP

Install all of these applications with a single command. Thanks to Unix System Engineer, Nitin Sookun for posting this on the Linux Mint Community website.

Since you will need root permissions when performing write operations outisde of your home directory, the commands shown below presume that you are logged in as root or are in a root instance of the terminal. If you are not in a root terminal, you can prepend the commands with ‘sudo’, for example listing the contents of the current directory:

sudo ls
Sudo Sandwich
Sandwich

In a root terminal (such as ‘Konsole as root’).

apt-get install lamp-server

If you get this error message (as I did during a Mint Xfce LAMP install)

E: Unable to locate package lamp-server

Then you will need a carat at the end:

apt-get install lamp-server^

Then install phpMyAdmin

apt-get install phpmyadmin

Restart Apache and try loading http://localhost/phpmyadmin.

/etc/init.d/apache2 restart

If phpMyAdmin doesn’t load, try adding an Include to your Apache config with a text editor (such as ‘Kate’). In your terminal, open a new bash shell tab, and launch the editor (gedit, kate, etc.)

kate /etc/apache2/apache2.conf

In the apache configuration file, near the bottom, look for the location of other Include statements and insert this phpmyadmin include as needed.

Include /etc/phpmyadmin/apache.conf
/etc/init.d/apache2 restart

mySQL

If you need to set or change the mySQL root password, use this command syntax when the password has not been set.

$ mysqladmin -u root password NEWPASSWORD

Use this command syntax to change the root password.

$ mysqladmin -u root -p'OLDPASSWORD' password 'NEWPASSWORD'

Drupal

Download the latest stable Drupal core distro from http://drupal.org/download. Extract the downloaded file, for example, drupal-7.19.tar.gz to the host root, /var/www. Then with the mv command, rename the drupal-7.19 directory extracted into www to drupal7

tar -xf drupal-7.19.tar.gz -C /var/www
mv /var/www/drupal-7.19 /var/www/drupal7

Copy the default.settings.php to settings.php. Then apply write permissions to both the dafault directory and the settings.php file so Drupal can modify settings.php as needed during the install.

cd /var/www/drupal7/sites
cp default/default.settings.php default/settings.php
chmod a+w default
chmod a+w default/settings.php

Note, after Drupal is installed, reset settings.php file permissions to read only using a-w

Virtual Hosts

Setting up some Virtual Hosts in our Apache config. In terminal, open the apache cofig file for editing (gedit, kate, etc.)

kate /etc/apache2/apache2.conf

Below the Files node, add this code block.

NameVirtualHost *:80

  ServerName default
  DocumentRoot /var/www/

  ServerName drupal7
  DocumentRoot /var/www/drupal7

  ServerName drupal8
  DocumentRoot /var/www/drupal8

Restart Apache to load our config changes

/etc/init.d/apache2 restart

Hosts file

Add the two named virtual hosts to the hosts file

kate /etc/hosts
127.0.1.7	drupal7
127.0.1.8	drupal8

Restart apache …

/etc/init.d/apache2 restart

Now you can access the new Drupal website at http://drupal7

Next – Mint LAMP Development Environment – Part Two

Resources

Acquia Drupal for Windows

I needed to setup a localhost Drupal development environment to upgrade a Drupal 6 theme and do some testing prior to upgrading a live site to Drupal 7. I decided to give the Microsoft Web Platform Installer a try on my Windows 8 computer.

While installing Acquia Drupal with Web Platform Installer 4.0, at some point the Web Platform Installer asks for MySQL ‘root’ user password. Further along during the install, I received an error and was prompted again for the MySQL ‘root’ password. This error would not go away even after entering the correct password:

The specified password for user account ‘root’ is not valid, or failed to connect to the database server.

To resolve this issue, Launch the Windows Registry Editor and delete the mysql_pwd registry key under, HKCU\Software\Microsoft\WebPlatformInstaller

Download and install MySQL Connector/Net (32 bit).

I restarted Web Platform Installer, searched for Drupal, and this time I left the save ‘root’ password option un-checked during the install.

Resources