Week 11: PHP The Right Way

by Andres Baravalle

PHP The Right Way

  • Namespaces
  • Basic coding standards
  • Dependency management
  • Frameworks

Before we start

This sessions borrows the name from the material collected by Josh Lockhart et al on phptherightway.com. It is your final PHP class and will focus on good practice in your PHP applications.

Some of the tecniques showed in the next slides require shell access and some familiarity with the shell. You will need to be able to use the shell to change directories and run files.

Some of this week's readings suggest to use curl or wget but they are not strictly required (as all the tools we have seen in the past weeks, both are Open Source and available for Windows, Mac and Linux).

As this is the final lecture, there is no set of activities for labs.

Namespaces

Namespaces

Namespaces are designed to solve two problems that developers find when creating or using re-usable code elements:

  1. Name collisions between code you create, and internal PHP classes/functions/constants or third-party classes/functions/constants
  2. Ability to shorten Extra_Long_Names in classes/functions/constants, improving readability of source code

PHP Namespaces provide a way in which to group related classes, interfaces, functions and constants. 

Namespacces: limitations

In PHP, namespaces apply only to classes, interfaces, functions and constants (not to global variables).

No non-PHP code before the namespace declaration!

Namespace example: flat namespace

<?php
namespace baravalle;

const CONNECT_OK = 1;

class Connection { /* ... */ }

function connect() { /* ... */  }
?>            

Namespace example: using hierarchy

<?php 
namespace baravalle\im2801; 
?>           

Using namespaces: aliasing/importing

You can alias (also called import) a namespace:


<?php
// this will be our root namespace
namespace uel;

use baravalle\im2801\Classname as Another;
// class baravalle\im2801\Classname is known as Another
// class baravalle\im2801\Classname is also accessible as \uel\Another 

use baravalle\im2801 as module;
// class baravalle\im2801\Classname is now known as module\Classname
?>

When to use namespaces?

Use namespaces to isolate your classes, functions and constants from other libraries that you might be using.

Basic coding standards

PHP Framework Interop Group

The PHP Framework Interop Group (FIG) is a group of PHP developers working on the most disparate projects, including:

The group is dedicated to interoperability and as such as published a number of style guides on coding in PHP

FIG Coding standards

The group produced a set of standards covering different aspects of PHP programming.

  • Autoloading Standard: PSR-0
  • Basic Coding Standard: PSR-1
  • Coding Style Guide: PSR-2
  • Logger Interface: PSR-3

We willl cover only PSR-0 to PSR-2 (and not in its entirety). For the full standard, please see the FIG website.

Namespaces and autoloading (PSR-0)

  • A fully-qualified namespace and class must have the following structure: \<Vendor Name>\(<Namespace>\)*<Class Name>
  • Each namespace must have a top-level namespace ("Vendor Name")
  • Each namespace can have as many sub-namespaces as it wishes
  • Each namespace separator is converted to a DIRECTORY_SEPARATOR when loading from the file system
  • Each _ character in the CLASS NAME is converted to a DIRECTORY_SEPARATOR. The _ character has no special meaning in the namespace

Basic Coding Standard (PSR-1)

  • Files SHOULD either declare symbols (classes, functions, constants, etc.) or cause side-effects (e.g. generate output, change .ini settings, etc.) but SHOULD NOT do both
  • Class names MUST be declared in $StudlyCaps
  • Class constants MUST be declared in all upper case with underscore separators
  • Method names MUST be declared in camelCase
  • Property names CAN be declared either in $StudlyCaps, $camelCase, or $under_score 

Basic Coding Standard (PSR-2)

  • Opening braces for classes MUST go on the next line, and closing braces MUST go on the next line after the body
  • Opening braces for methods MUST go on the next line, and closing braces MUST go on the next line after the body
  • Visibility MUST be declared on all properties and methods

Example:

<?php  
namespace Vendor\Package;    

use FooInterface;  
use BarClass as Bar; 
use OtherVendor\OtherPackage\BazClass;    

class Foo extends Bar implements FooInterface  
{      
	public function sampleFunction($a, $b = null) {          
	if ($a === $b) {
		bar();
   } 
}
?> 

Dependency management

Composer and Packagist

ComposerComposer is a dependency manager for PHP. List your project’s dependencies in a composer.json file and, with a few simple commands, Composer will automatically download your project’s dependencies and setup autoloading for you.

There are already a lot of PHP libraries that are compatible with Composer, ready to be used in your project. These “packages” are listed on Packagist, the official repository for Composer-compatible PHP libraries.

Installation

The installation requires two steps: step 1 to download the installer; step 2 to download composer:

Folder structure

Applications using Composer will normally have the following folder structure:

  • /vendor will include all the Composer packages
  • /web will be the (public) root of your web site; public files will be subfolders of /web

Selecting, installing and loading packages

  • Use Packagist to select packages which may be useful for your application.
  • Use Composer to install the selected packages in \<Vendor Name>\<Namespace>\ (Composer is compliant with FIG-0)
  • To load the packages, just include /vendor/autoload.php

In the next slides we are going to look at the process step by step, starting from the installation.

Editing composer.json

Edit (or create) the file composer.json in the root of your application, including the packages that you want to load in json format:

{
"require": {
		"silex/silex": "~1.1",
		"symfony/expression-language": "*",
		"symfony/form": "*",
		"oryzone/boilerplate-bundle": "*"
	}
}                

Each line is normally a different package. In "symfony/form": "*", we are asking to install the latest version available of the package form from vendor symfony.

Installing the packages

To install the packages, you need command line access.

In the command line, go to the root folder of your application and run php composer.phar update

Replace php with the path of your PHP executable (varies according to OS - in recent versions of Windows is in c:\Program Files (x86)\php\php.exe).

Loading packages

To load the packages, include the file /vendor/autoload.php in your application.

What packages should I use?

GIYF - possible packages include:

  • silex/silex: a lightweight framework
  • symfony/form: the form processing component in symfony
  • components/html5-boilerplate: the famous HTML 5 boilerplate
  • doctrine/orm, doctrine/doctrine-bundle, doctrine/mongodb-odm and doctrine/mongodb-odm-bundle: object-relational mapping and abstraction layer. Includes a MongoDB driver
  • phpdocumentor/phpdocumentor: to create human-readable documentation from your code & comments
  • monolog/monolog: for logging
  • swiftmailer/swiftmailer: for feature-rich email
  • doctrine/cache: for caching
  • twig/twig: a template engine

PEAR

Beside Packagist, the other large PHP components repository is pear.php.net.

Selected PEAR components are often installed by default; many components are available both in PEAR and in Packagist.

Frameworks

What is a framework?

"A software framework is a universal, reusable software platform to develop applications, products and solutions."

Wikipedia, 2013

Using frameworks

While using frameworks is good practice, in the short term, writing code using a framework can take longer than writing custom code.

Using a frameworks improves software reuse and allows to integrate more easily reusable components.

Popularity

The most popular PHP frameworks are:

  • CodeIgniter
  • Symfony
  • Laravel
  • Yii
  • CakePHP

They are all based on the Model-View-Controller (MVC) design pattern. Their popularity varies according to the different countries; in the last year, the most popular framework wordwide was CodeIgniter.

MVC

From the Yii documentation:

MVC aims to separate business logic from user interface considerations, so that developers can more easily change each part without affecting the other. In MVC:

  • the model represents the information (the data) and the business rules;
  • the view contains elements of the user interface such as text, form inputs;
  • and the controller manages the communication between the model and the view.

Using Silex

If you implement the MVC design patter in your applications it is a good start (+ ideally a front controller or router).

In the next slides we will see how to use a lightweight framework, Silex. Silex is developed by the same team developing Synfony, but targetting a different type of projects.

What will we do

We are going to develop a simple application (based on week 9 activities) that:

  • Presents a form to input student marks
  • Saves the data on MongoDB
  • Returns the students results

The application is visible here and the code is available here for inspection.

Step 1: create your folders:

  • /src: here will reside your app
    • /src/pages: here will reside your views
  • /vendor: here will reside your components
  • /web: here will reside your front controller
    • /web/subfolders: place in subfolders of web all theresources (e.g. js, images, css)

Step 2: create your composer.json

This assumes that you have already installed Composer.

Use this as a starting point (and add extra components as needed)t:

{
	"require": {
		"silex/silex": "~1.1",
		"symfony/expression-language": "*",
		"symfony/form": "*",
		"components/html5-boilerplate": "*",
	}
}

Step 3: composer.phar

Download the components:

php composer.phar update

Step 4: write your index.php & app.php

Your index.php will just include your app.php file.

Your app.php file will:

  • Connect to the db
  • Instantiate the objects needed in the app
  • Act as a front router

Step 4: analysing the code in app.php

<?php
// starts output buffering
ob_start();

// autoload the classes
require_once __DIR__ . '/../vendor/autoload.php'; 

// create the framework object
$app = new Silex\Application(); 

// now connect to the database
$connection = new MongoClient("mongodb://andres:*******@localhost/andres");

// use your own database name instead of andres (your student number) 
$collection = $connection->andres->marks; 

// router section
// this construct is called "anonymous function"
// or closure 
$app->get('/', function() {
	$title = "Home";
	include_once(__DIR__ . "/../src/pages/home.php");
   // stopping output buffering and capturing the include 
	$body = ob_get_clean();
	
	// starting output buffering again  
	ob_start();
	include_once(__DIR__ . "/../src/pages/template.php");
	
	// stopping output buffering and returning
	return ob_get_clean();
});

	// another router
$app->get('/save-module', function() use ($collection) {
	$title = "Saving record";
    
	// doing sanity checks on $_GET
	$student_number = filter_var($_GET["student_number"], FILTER_SANITIZE_STRING);
	$module_number = filter_var($_GET["module_number"], FILTER_SANITIZE_STRING);
	$mark = filter_var($_GET["mark"], FILTER_SANITIZE_NUMBER_INT);    
	
	// storing the data in an array
	$result = array("student_number" => $student_number, "module_number" => $module_number, "mark" => $mark); 
	
	// and saving it in MongoDB
	if($collection->insert($result)) {
		$body = "<p>Insertion correct</p>"; 
	}
	else {
		$body = "<p>Could not insert correctly.</p>";
	} 
	include_once(__DIR__ . "/../src/pages/template.php");

	// stopping output buffering and returning
	return ob_get_clean();
});

$app->get('/list-modules', function() use ($collection) {
	$title = "List modules";
	$cursor = $collection->find();
	$body = "<table>\n";
	foreach ($cursor as $key => $val) {
		$body .= "\t<tr>\n";
		$body .= "\t\t<td>${val['student_number']}</td>\n";
		$body .= "\t\t<td>${val['module_number']}</td>\n";
		$body .= "\t\t<td>${val['mark']}</td>\n";
		$body .= "\t</tr>\n"; 
	}
	$body .= "</table>";
	include_once(__DIR__ . "/../src/pages/template.php");
	return ob_get_clean();
});

// Run
$app->run();

Source and caveats

Please note that the solution is simplified. Limitations include the basic template implementation and no .htaccess support.

Core readings

These are the core readings for this week:

Lockhart, J. et al. (2013). PHP The Right Way. Available from http://www.phptherightway.com

Adermann, N. and Boggiano, J. (2013). Getting started. Available from http://getcomposer.org/doc/00-intro.md (ready the sections "Getting started", "Basic usage" and "Libraries")

SensioLabs.org (2013). Silex. Available from http://silex.sensiolabs.org/doc/usage.html

That's all folks