Personal Movie Database Part 2 - Service (PHP)

January 15, 2016 by PHP   Architecture  

First of all, happy new year and may the force be with you ;)

Last year in the first part of this series of posts we had a quick look at the technologies that makes the frontend tick, in this post we're going to have a look at the backend service used for saving movie metadata.

If you were to go through the change history for this repository, you will notice quite a few changes made to the
backend service. This is greatly influenced by a rather frustrating interview I had with a company in Germany last year.

wnn48

Admittedly I stunningly dropped the ball, I am also clearly out of touch with the happenings in the PHP community (been focussing more on C#, Objective-C etc the last few years), the frustration however comes in where one of the developers were tasked to review a code assignment and spent a mere five minutes rush one-sided reviewing my mediocre code with a very dogmatical understanding (and a false understanding at that in my opinion) of the application of S.O.L.I.D principles (perhaps as a result of their rush).

But nonetheless, I decided to treat it as a learning opportunity and though discouraged still hopeful that someone, somewhere might see some potential in me and give me a chance to prove myself and escape from South Africa.


Changes


The first change is the introduction of composer into the codebase, over the last few years composer became the de facto dependency manager for PHP (think of it as npm for PHP).

Composer will download the required dependencies (as defined in the composer.json file) and setup auto loading of classes (vendor/autoload.php).

As part of the exercise, I analysed the project to identify reusable code and ended up moving code into three additional projects (each containing its own composer.json file), namely:

  • CSTruter/Service
    Project containing classes for creating a PHP Web Service.

  • CSTruter/Facebook
    Project containing classes for accessing the Facebook API.

  • CSTruter/Misc
    Basically a little orphan project containing functionality that doesn't have a proper home yet.

Note that these projects are still in their infancy, I only implemented the functionality I needed.

The second notable change is the introduction of doctrine orm into the codebase, apparently which has become the de facto ORM (object relational mapping) for PHP.

I simply added some annotations to my existing models, instantiated an EntityManager connecting to my existing database and implemented the appropriate repository interfaces.

<?php

namespace Models;

use Doctrine\ORM\Mapping as ORM;
/**
 * @ORM\Entity
 * @ORM\Table(name="movies")
 * @ORM\Entity(repositoryClass="Repositories\Movies")
 */
class Movie
{
	/**
     * @ORM\Column(type="integer", name="movieId")
     * @ORM\Id
	 * @ORM\GeneratedValue(strategy="AUTO")
     */
	public $movieId;
	
	/** 
	 * @ORM\Column(type="string", name="imdbId", length=50)
	 */
	public $imdbID;


There are various other doctrine mapping options available as well, like XML, YAML and writing PHP code to map entities, its even possible to auto generate code for your entities.

I must confess though, I am not a huge fan of the ORM technique - discussion for another day...

Build Process

With the introduction of composer and doctrine into the equation, line 5 - 7 was added to the build batch file seen below.

cd app
call npm install
xcopy /S /Y /I ".\node_modules\jquery-mobile\dist\images\*.*" "..\css\images"
call gulp
cd ../Service
call composer install 
call vendor/bin/doctrine orm:schema-tool:create
pause


On line 5 the "composer install" command parses / runs the contents of the composer.json file(s).

The interesting part is line 6, this doctrine command will create the required MySQL tables according to the defined annotations, but it won't create the database itself.

I spent a few hours searching the web to find a doctrine specific way to auto create the database as well (seeing that I wanted to get rid of requiring to execute a SQL dump file all together). Unfortunately I couldn't find a doctrine specific way yet, so I ended up doing the following:

<?php
include 'bootstrap.php';
use Doctrine\ORM\Tools\Console\ConsoleRunner,
	Doctrine\ORM\EntityManager;
	
try {
	$entityManager->getConnection()->connect();
}
catch(Exception $ex) {
	$connection = EntityManager::create([
		'driver'   => 'pdo_mysql',
		'host' 	   => MYSQL_HOST,
		'user'     => MYSQL_USERNAME,
		'password' => MYSQL_PASSWORD,
		'charset' => 'UTF8'
	], $config)->getConnection();
	$connection->executeUpdate('CREATE DATABASE '.MYSQL_DATABASE.' CHARACTER SET utf8 COLLATE utf8_general_ci');
}
return ConsoleRunner::createHelperSet($entityManager);
?>


Note: The doctrine command requires the presence of a cli-config.php file, contents as seen above.

Basically the script attempts to connect to the specified database, if that fails it attempts to create the database (assuming that the user is allowed to perform database creation that is - might be prudent to delete this file
after deployment)
.

Code overview

Lets jump into some code, observe the snippet below:

<?php

use Models\Movie,
	Repositories\Interfaces\IMovieRepository,
	CSTruter\Service\Results\JsonResult,
	CSTruter\Service\Results\BinaryResult,
	CSTruter\Service\Results\ErrorResult,
	CSTruter\Facebook\Service\FacebookServiceBase,
	CSTruter\Misc\Request;
	
class MovieService extends FacebookServiceBase
{
	private $repository;
	
	public function __construct(IMovieRepository $repository, $appId, $secret) {
		$this->repository = $repository;
		parent::__construct($appId, $secret);
	}
protected function registerMethods() { $this->registerMethod('POST', 'addMovie') ->requireJson() ->requireValidation(); $this->registerMethod('DELETE', 'removeMovie') ->requiredArguments('movieId') ->requireValidation();

The composer auto generated autoloader (included in my bootstrap file), makes use of the defined namespaces to load classes (side note, personally coming from languages like C#, Java etc, Namespaces in PHP doesn't quite feel right)

The MovieService class inherits from the FacebookServiceBase class, thereby authenticating secure calls to the service against Facebook (in future this will be the perfect place to implement the strategy design pattern and provide alternative forms of authentication to users, e.g. Twitter or Google).

The constructor uses a classic dependency injection technique, thereby relying on the passed abstraction (IMovieRepository) instead of using the concrete class itself, making it loosely coupled.

abstract class FacebookServiceBase extends ServiceBase implements ISecureService
{
	private $api;
	
	public function __construct($appId, $secret) { 
		$this->api = new FacebookApi($appId, $secret);
		parent::__construct(); 
	}

Since the FacebookServiceBase class inherits from ServiceBase and doesn't implement the abstract function registerMethods, we're required to implement it in the MovieService class (this is where I register the service entry points).

You will notice a DIP violation in the preceding snippet - we're tightly coupled with the FacebookApi class, nonetheless in context of how the class is / will be used I don't feel the urge to decouple (YAGNI).

So yes to the reviewer, I do in fact, known about DIP and its application, I do however not agree with the apply it everywhere, every time as fast as you can approach.

Lets have a quick look at the validate method (one of the ISecureService implementations) and call it a night.

	public function validate($signed_request = NULL)
	{
		try
		{
			$json = $this->api->parseSignedRequest($signed_request);
			if ((!isset($_SESSION['signed_request'])) || 
($_SESSION['signed_request'] != $signed_request)) { $this->api->validateSignedRequest($json['code']); $_SESSION['signed_request'] = $signed_request; } $_SESSION['id'] = $json['user_id'];

Initially when I wrote these methods, I tried a variety of approaches, e.g. I passed the Facebook token to the service via the frontend and validated the token on the backend against Facebook every time. Obviously not the ideal approach, as it will cause a major performance hit while being totally unnecessary, nevermind the Facebook API Rate limit that you will ultimately run into.

I also considered doing one time validation on the token and simply use the session in subsequent calls, but this opened the project to potential security risks (tokens can be reused).

I ended up using the signed request itself, the nice thing about this is, the signed request can only be used once, making it useless to an attacker the second time around. You can have a look at how it practically works in the FacebookApi class.


Leave a Comment



Related Posts

Personal Movie Database Part 1 - UI Overview (JQuery Mobile AngularJS)

November 10, 2015


Related Downloads

Simple Facebook Api

Miscellaneous Classes

Web Service Base Classes

Personal Movie Database