Wednesday, April 1, 2015

Setting up our Silex Application

Setting up the application 

When I first started with Silex, it was very easy. Just a few files, a few routes, and then I was good to go. What I realized is that eventually my index.php page started to look like this:

error_reporting(-1);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
date_default_timezone_set('America/Phoenix');
// web/index.php
require_once __DIR__.'/vendor/autoload.php';


use Whoops\Provider\Silex\WhoopsServiceProvider;
use Symfony\Component\HttpFoundation\Request;
use Mailgun\Mailgun;

$app = new Silex\Application();
$app['debug']=true;
if ($app['debug']) {
    $app->register(new WhoopsServiceProvider());
}

$app->register(new Silex\Provider\SwiftmailerServiceProvider());
$app->register(new Silex\Provider\SessionServiceProvider());
$app['swiftmailer.options']=[
    'host'=>'mail.site.com',
    'port'=>'25',
    'username'=>'support@site.com',
    'password'=>'secret',
    'encryption'=>null,
    'auth_mode'=>null,
];

$app->register(new Silex\Provider\TwigServiceProvider(), [
    'twig.path' => [
        __DIR__.'/src/Api/Views/Emails',
    ],
]);


$app->get('/',function(){
    return'hello world';
});
$prefix = '/api';

$app->get($prefix.'auth/check', 'Api\\Auth::check');
$app->post($prefix.'auth/login','Api\\Auth::login');
$app->post($prefix.'auth/logout','Api\\Auth::logout');
$app->post($prefix.'contact/contact','Api\\Contact::us');
$app->post($prefix.'contact/password','Api\\Contact::password');
$app->get($prefix.'site/info','Api\\Site::info');
$app->post($prefix.'signup/individual','Api\\SignUp::individual')->after('Api\\Events\\Emails\\Welcome::individual');
$app->get($prefix.'signup/loginCheck','Api\\SignUp::checkLogin');
$app->get($prefix.'signup/userCheck','Api\\SignUp::checkUser');
$app->post($prefix.'signup/site','Api\\SignUp::site')->after('Api\\Events\\Emails\\Welcome::site');
$app->post($prefix.'user/updateProfile','Api\\User::updateProfile');
$app->post($prefix.'user/addApplication/{appId}','Api\\User::addApplication');
$app->post($prefix.'user/removeApplication/{appId}','Api\\User::removeApplication');
$app->post($prefix.'user/updateProfile','Api\\User::updateProfile');

$app->run();

This was a very simple API that I wrote, and we stopped production on it. What I started to notice was that my index.php was starting to get big. Routes started piling up. Application configuration, which was simple now, was getting more and more complex when I would add new service providers. Basically, it was becoming a cluster, and I knew there had to be a better way. I first looked at a few frameworks and the index.php page they used. Yii (not 2.0) looked like this:

$yii=dirname(__FILE__).'/yii/framework/yii.php';
$config=dirname(__FILE__).'/protected/config/main.php';
require_once($yii);
date_default_timezone_set('America/Phoenix');
Yii::createWebApplication($config)->run();

Laravel looked like this:

/**
 * Laravel - A PHP Framework For Web Artisans
 *
 * @package  Laravel
 * @author   Taylor Otwell 
 */

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/

require __DIR__.'/../bootstrap/autoload.php';

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let's turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight these users.
|
*/

$app = require_once __DIR__.'/../bootstrap/start.php';

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can simply call the run method,
| which will execute the request and send the response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have whipped up for them.
|
*/

$app->run();

To set up my index.php page, I needed to do something like this, and break up all the clutter and organize my application better. To accomplish this, you can do a few different things. First option is to write a class and inject the Silex\Application, the other option is to extend the Silex\Application class. By doing either of these options, you can essentially reduce your index.php down to this:

// public/index.php
require_once __DIR__.'/../vendor/autoload.php';

$app = new MyApp\Application();
$app->bootstrap();
$app->run();

Very simple, very sleak, very easy to read. Broken down, all the index.php does is load the composer autoloader, creates the application, bootstraps the app (does the configuration, routes, etc), and then runs the application.

Folder Structure 

Before we go any further, I wanted to share my folder structure.

MySilexApp
----node_modules
----public
--------app
--------assets
--------dist
--------lib
--------.htaccess
--------index.php
----src
--------MyApp
------------Config
----------------Routing
----------------dev.json
----------------prod.json
----------------testing.json
------------Controllers
------------Middlewares
------------Repositories
------------Services
------------Views
----------------Main
--------------------index.twig
------------Application.php
----tests
--------MyApp
----vendor
----.bowerrc
----bower.json
----composer.json
----composer.lock
----gulpfile.js
----package.json
----phpunit.xml

I hope this all makes sense, but essentially my PHP application is inside of the /src/MyApp folder.  Tests are in the tests folder.  The Javascript application is in the public folder.  Composer packages in the vendor folder.  The reason I have the application structure set up this way just because it is simple.  It makes sense.  We can expose just the public folder to the user and hide the PHP application.  Tests are separated from our application, but will conform to the same folder structure as the application when tests are written.

Inside the application (src/MyApp) folder there are several sub-folders.

Config 

This is where your configuration files will go. Right now I have different config files for different environments. Inside the folder, there is a place to add the Route files, which we will go over later. The reason I added it here is because it is a flat file, no logic needed. The Routes files are simply files that specify the paths, controllers, and middlewares to be used.

Controllers 

This folder is just a folder full of controllers. Following best practices, your controllers should be slim and simple, so the main logic for your application will be done in a the other folders. 

Middlewares 

Name says it all. It just contains the middlewares that will be used by the various controllers. 

Repositories 

If you are using a database, this would be where you handle your data. If you read my previous posts, you know that I am using Soap right now to interact with an external API server. So the Repository folder houses all my connections and data retreival methods.

Services

This is a generic term for miscellaneous classes. One instance would be a class that handles data transformations (take a database object and return a smaller array or combines multiple arrays), or maybe a wrapper for another class/package you use. I have a wrapper for Zend/Soap/Client that is specific to my application. It just helps make the soap calls a little easier. I could potentially use it for another Soap server, but it is designed with the options hard coded for my application. If I wanted, I could move the options (Soap version, compression settings, WSDL url, etc) into a config file, but right now there is no need since I will most likely be moving to a database in the future and not additional soap servers. </rant>

Views

The folder that contains the twig files, or any templating file if you are using another templating system.

 One folder I haven't included, but will probably add in the future is a Validation folder. The folder would essentially handle validation for the application data submitted. When a request is received, the controller would load a Validator and data would be passed to it. Validated data would be returned and then, if valid, it could be submitted to the database.

The Application.php file

namespace MyApp;

use Whoops\Provider\Silex\WhoopsServiceProvider;
use Symfony\Component\HttpFoundation\Request;
use Silex\Provider;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Routing\RouteCollection;

class Application extends \Silex\Application{
    public function bootstrap(){
        $this->loadProviders();
        $this->loadRoutes();
        $this->loadConfig();
        $this->connectMemcached();
    }
    private function loadProviders(){
        // load session service provider
        $this->register(new \Silex\Provider\SessionServiceProvider());
        $this['session.storage.handler']=null; // since using memcache as session handler, don't use silex to manage anything.

        // load twig service provider
        $this->register(new Provider\TwigServiceProvider(),['twig.path' => __DIR__.'/Views']);

        // the following providers are required by the WebProfiler
        $this->register(new Provider\HttpFragmentServiceProvider());
        $this->register(new Provider\ServiceControllerServiceProvider());
        $this->register(new Provider\UrlGeneratorServiceProvider());

        // get the application environment
        $env = getenv('APP_ENV') ?: 'prod';

        // load a configuration service provider and add configuration options
        $this->register(new \Igorw\Silex\ConfigServiceProvider(__DIR__."/Config/$env.json"));

        // load debug only providers (whoops)
        if ($this['debug']) {
            \Symfony\Component\Debug\Debug::enable(E_ALL, true);
            $this->register(new WhoopsServiceProvider());

            // level 2 debug or localDebug enable the WebProfiler
            if($this['localDebug']){
                $this->register($p = new Provider\WebProfilerServiceProvider(), [
                    'profiler.cache_dir' => __DIR__.'/../../cache/profiler',
                ]);
                $this->mount("_profiler", $p);
            }
            
        }
    }


    private function loadRoutes(){
        // use the Yaml File Loader to load up the routes in the routing folder
        $loader = new YamlFileLoader(new FileLocator(__DIR__ . '/Config/Routing'));
        $collection = $loader->load('routes.yml');

        // add the routes to the application
        $this['routes']->addCollection($collection);
    }
    private function loadConfig(){
        // the other options that you can set go here
        date_default_timezone_set('America/Phoenix');

        // add a simple middleware that looks for application/json requests and adds the paramters to the request object
        $this->before(function (Request $request) {
            if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
                $data = json_decode($request->getContent(), true);
                $request->request->replace(is_array($data) ? $data : array());
            }
        });
    }
    private function connectMemcached(){
        $this->register(new \SilexMemcache\MemcacheExtension(), [
            'memcache.library' => $this['memcacheLibrary'],
            'memcache.server' => [
                ['127.0.0.1', 11211]
            ],
        ]);
    }
}

What I have done is simply extend the Silex\Application and added a few more methods. Again, I could have written a wrapper instead, but felt this was a little easier at the time.

If you recall my index.php, it had the line "$app->bootstrap();" All that line does is loads all the service providers, routes, and configuration for the application.

The service providers are straight forward. The one thing I would like to point out is the application environment. One awesome thing you can do in Apache is set environment variables. In the vhosts file, you can add a line like this:

    DocumentRoot "path\to\htdocs"
    ServerName localhost
    SetEnv APP_ENV dev

On your production server, you can either leave that line out, or you can change the vhosts file to prod (for production). Then in PHP you can use the
getenv('APP_ENV')
to get the information that you set in the Apache file. You can do this in Nginx as well, but I don't have that code off hand. Bringing this full circle, I can have a different configuration that is automatically selected based on this environment variable and then I can use those options in my application. The ConfigServiceProvider is a 3rd party provider and will allow me to set configuration keys and values fairly easily. I can then access the variables like this:
$value = $app['key'];

Simple. In the configuration file I can specify debugging options. On a production server, you don't want to show Whoops error pages when there is a bug (hopefully there aren't any). You definitely don't want to display the webprofiler. On a local machine, you would want to see both of these things. Routes The routes are now being pulled from a file! How cool is that. Because Silex is using Symfony components (Routes), you can load routes using the RouteCollection provided by Symfony. I put the routes into the configuration folder in YAML files. Here is my routes.yml:

# config/routes.yml
home:
    path: /
    defaults: { _controller: MyApp\Controllers\IndexController::actionIndex }

hello:
    path: /hello/{name}
    defaults: { _controller: MyApp\Controllers\IndexController::actionHello }

auth:
    prefix: /auth
    resource: auth.yml

user:
    prefix: /user
    resource: user.yml

One thing to notice is that my routes have subroute resources (auth and user) that reference additional YAML files. The YAML file loader will load those routes as well.

Auth looks like this:

# config/auth.yml
auth.check:
    path: /check
    defaults: { _controller: MyApp\Controllers\AuthController::actionCheck}
    methods: [get]

auth.getLogin:
    path: /login
    defaults: { _controller: MyApp\Controllers\AuthController::actionGetLogin}
    methods: [get]

auth.postLogin:
    path: /login
    defaults: { _controller: MyApp\Controllers\AuthController::actionPostLogin}
    methods: [post]

auth.logout:
    path: /logout
    defaults: { _controller: MyApp\Controllers\AuthController::actionLogout}

And the User routes look like this:

# config/auth.yml
user.apps:
    path: /apps
    defaults: {_controller: MyApp\Controllers\UserController::actionGetApps}
    methods: [get]
    options: {_before_middlewares: [ MyApp\Middlewares\AuthMiddleware::beforeAuth ]}

Now to explain this. The routes.yml file has two defined routes: home and hello. Those are the route names and can be accessed that way anytime when doing redirects. The controller being used is in the defaults: object with the key "_controller". The two subroutes are auth and user. The auth routes are all named with an "auth." prefix and can again be accessed that way as well. The routes are all prefixed with "/auth" so auth.check has a path like "/auth/check". The other thing in the auth.yml file is that you can specify route methods. If you leave it blank, that route can be called via any method. In the auth file I have two separate routes, one for posting to /auth/login and another for getting the /auth/login. The call different controller methods and make it easier than using
$_SERVER['REQUEST_METHOD']
to see how things are being accessed. The user.yml routes have one additional parameter: options. Here you can specify middlewares that the route will use. The middleware object keys are "_before_middlewares" and "_after_middlewares", and both are arrays, so you can pass several middlewares to a single route.

 The routes are all converted to an array object from the YAML files, and are then loaded into the application using a RouteCollection. You can have multiple RouteCollections, and adding them will continue pushing the routes onto the $app['routes'], not overwriting them.

Wrap up 

I think this is long enough for now. We covered your Application.php setup and routing for your application. We have packed in a ton of routes using YAML files and the RouteCollection. We have configured our application for a specific development environment. I think tomorrow I am going to talk about caching and benefits using cache over a file system.

Needed Silex Packages

I am just going to throw out a list of packages/service providers I am using and explain why.

This is what my composer.json file looks like:


{
    "authors": [
        {
            "name": "Tim Withers",
            "email": "tim@opticip.com"
        }
    ],
    "require": {
        "silex/silex": "1.2.*",
        "silex/web-profiler":"1.0.*",
        "symfony/yaml": "2.7.*@dev",
        "filp/whoops": "1.1.*",
        "zendframework/zend-soap": "2.3.*@dev",
        "symfony/console": "2.5.*",
        "symfony/config":"v2.2.0",
        "mheap/Silex-Memcache": "@dev",
        "igorw/config-service-provider":"1.2.*"
    },
    "require-dev": {
        "phpunit/phpunit": "4.5.*"
    },
    "autoload": {
        "psr-0": { "TeacherManager": "src/" }
    }
}


Silex

Of course, how can you have a Silex app without Silex.

Silex Web Profiler

This is awesome.  It is actually a Symfony plugin.  I saw it and used it when developing in Symfony and wondered if there was a service provider for it.  Sure enough, there is.

When using the Web Profiler it will only work when you send a response to the browser.  Using "exit()" or returning a string only will not allow the web profiler to work.  You must return a response using Symfony Response or the Silex Application.

Yaml

This is just another way to write config files.  Similar to JSON, but easier as there are no quotes.

Remember, quotes not needed.  Avoid tabs at all costs.

Whoops!

Laravel showed me the greatness of Whoops and Silex has a provider for it.  After configuring it, every error will run through Whoops and display on your screen with incredible amounts of information.  It will provide all the request information, server information, session information, as well a stack trace with the line of code in questions highlighted.  If you haven't used it, use it.  It will make debugging a hell of a lot easier.

Zend Soap

Everyone hates SOAP.  But this made it usable.  Because of my application, I have to communicate with a 3rd party API server to store and retrieve information.  Don't hate.

Console

Although I haven't used it yet in my application, the Symfony Console will allow me to write quick commands and run them in shell/command prompt very easily.  I have written a Soap tester service that will allow me to plug in a WSDL url and pull up all the commands and make Soap calls.  It was my first experience with it, but it worked well.

If you are familiar with writing console commands in Laravel (Artisan commands), this will feel fairly natural.  Of course there is some configuration, either including the application, or just components, you will be able to get your job done quickly.

Config/Config service provider

This service provider allows you to specify config files and load up the configurations into your application.  I wrote dev.yml and prod.yml files and inject them into my application to load up settings differently.

Memcache

I don't use Redis, but I use Memcache fairly extensively.  I have move to storing PHP sessions in cache rather than the file system.  I saw huge performance increases by this move alone.  Memcache is not persistent, and will be deleted when the server is restarted, but it is blazingly fast.  A monitor on one of my servers shows that it takes an average of 0.00015 seconds to connect to the Memcache service and retrieve a key.  Since we are executing Soap requests to a remote server which take 0.01 seconds to get a result, utilizing Memcache to store those responses significantly speeds up the application.


My next article will be going over the initial configuration of the Silex application, and the tweaks I made and my reasons behind it.