/ STUFF, ALEXA

So many frameworks, oh my!

With all the Alexa work I have planned, I decided to create a project called Argyle, named from Tobago’s largest waterfall. I had looked at Laravel and Phalcon but I got the white-screen-of-nothing and no packages to install respectively after trying to install these frameworks. So, I decided to take a step back and worry about the front end and whole framework thing later. Instead I would focus on creating a nice JSON based API that I can use for whatever front end (web, android app, alexa etc.) I decided to use.

This led me to a nice slim php framework named, you guessed it, Slim! I had managed to install Composer while trying to get the other frameworks to work, so, following the instructions on the home page, I created argyle-api using the slim-skeleton.

workstation@workstation-upstairs:/var/www/html$ composer create-project slim/slim-skeleton argyle-api

Installing slim/slim-skeleton (3.1.6)
  - Installing slim/slim-skeleton (3.1.6): Downloading (100%)
Created project in argyll-api
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 35 installs, 0 updates, 0 removals
  - Installing psr/container (1.0.0): Loading from cache
  - Installing container-interop/container-interop (1.2.0): Downloading (100%)
  - Installing nikic/fast-route (v1.3.0): Downloading (100%)
  - Installing psr/http-message (1.0.1): Downloading (100%)
  - Installing pimple/pimple (v3.2.3): Downloading (100%)
  - Installing slim/slim (3.11.0): Downloading (100%)
  - Installing slim/php-view (2.2.0): Downloading (100%)
  - Installing psr/log (1.0.2): Loading from cache
  - Installing monolog/monolog (1.23.0): Loading from cache
  - Installing symfony/polyfill-ctype (v1.9.0): Loading from cache
  - Installing symfony/yaml (v4.1.6): Downloading (100%)
  - Installing sebastian/version (2.0.1): Loading from cache
  - Installing sebastian/resource-operations (1.0.0): Loading from cache
  - Installing sebastian/recursion-context (2.0.0): Downloading (100%)
  - Installing sebastian/object-enumerator (2.0.1): Downloading (100%)
  - Installing sebastian/global-state (1.1.1): Downloading (100%)
  - Installing sebastian/exporter (2.0.0): Downloading (100%)
  - Installing sebastian/environment (2.0.0): Downloading (100%)
  - Installing sebastian/diff (1.4.3): Downloading (100%)
  - Installing sebastian/comparator (1.2.4): Downloading (100%)
  - Installing doctrine/instantiator (1.1.0): Loading from cache
  - Installing phpunit/php-text-template (1.2.1): Loading from cache
  - Installing phpunit/phpunit-mock-objects (3.4.4): Downloading (100%)
  - Installing phpunit/php-timer (1.0.9): Downloading (100%)
  - Installing phpunit/php-file-iterator (1.4.5): Downloading (100%)
  - Installing sebastian/code-unit-reverse-lookup (1.0.1): Loading from cache
  - Installing phpunit/php-token-stream (2.0.2): Downloading (100%)
  - Installing phpunit/php-code-coverage (4.0.8): Downloading (100%)
  - Installing webmozart/assert (1.3.0): Loading from cache
  - Installing phpdocumentor/reflection-common (1.0.1): Loading from cache
  - Installing phpdocumentor/type-resolver (0.4.0): Loading from cache
  - Installing phpdocumentor/reflection-docblock (4.3.0): Loading from cache
  - Installing phpspec/prophecy (1.8.0): Loading from cache
  - Installing myclabs/deep-copy (1.8.1): Loading from cache
  - Installing phpunit/phpunit (5.7.27): Downloading (100%)
monolog/monolog suggests installing aws/aws-sdk-php (Allow sending log messages to AWS services like DynamoDB)
monolog/monolog suggests installing doctrine/couchdb (Allow sending log messages to a CouchDB server)
monolog/monolog suggests installing ext-amqp (Allow sending log messages to an AMQP server (1.0+ required))
monolog/monolog suggests installing ext-mongo (Allow sending log messages to a MongoDB server)
monolog/monolog suggests installing graylog2/gelf-php (Allow sending log messages to a GrayLog2 server)
monolog/monolog suggests installing mongodb/mongodb (Allow sending log messages to a MongoDB server via PHP Driver)
monolog/monolog suggests installing php-amqplib/php-amqplib (Allow sending log messages to an AMQP server using php-amqplib)
monolog/monolog suggests installing php-console/php-console (Allow sending log messages to Google Chrome)
monolog/monolog suggests installing rollbar/rollbar (Allow sending log messages to Rollbar)
monolog/monolog suggests installing ruflin/elastica (Allow sending log messages to an Elastic Search server)
monolog/monolog suggests installing sentry/sentry (Allow sending log messages to a Sentry server)
symfony/yaml suggests installing symfony/console (For validating YAML files using the lint command)
sebastian/global-state suggests installing ext-uopz (*)
phpunit/phpunit-mock-objects suggests installing ext-soap (*)
phpunit/php-code-coverage suggests installing ext-xdebug (^2.5.1)
phpunit/phpunit suggests installing phpunit/php-invoker (~1.1)
phpunit/phpunit suggests installing ext-xdebug (*)
Writing lock file
Generating autoload files
workstation@workstation-upstairs:/var/www/html$

Nice! I refuse to use PHP’s webserver (as seen on the Slim Framework website) as I want to make sure that what I’m writing can work with Apache. A quick visit to the website and all seems well. I can see a slim page and a nice link to try the framework.

Slim Framework Default Index Page

So it turns out that the project is created in argyle-api, but the actual web part is in the public folder. This is understandable as you don’t necessarily want all your files in the webserver directory, due to permissions etc. This makes for a horrible URL though, as I don’t want to have to type in the /public/ part to get to my API and web service. Luckily(?) I’m running Apache and there’s a conf file for that. Specifically I can create a conf file named argyle-api.conf with the contents below in /etc/apache2/conf-available:

Alias /argyle-api /var/www/html/argyle-api/public

<Directory /var/www/html/argyle-api/public>
    Options FollowSymLinks
    DirectoryIndex index.php
</Directory>

What this conf file is telling apache is that when someone goes to the /argyle-api URL, serve them content from the /var/www/html/argyle-api/public folder (that’s the alias part). Then we want to make sure we set the directory options for the folder so we can control how it behaves (to follow any symbolic links in the folder and if no file is specified, serve up index.php).

Putting the file in the /etc/apache2/conf-available folder does not automagically make it work. To get it working we need to enable it using a2enconf (a command meaning Apache 2 enable configuration).

workstation@workstation-upstairs:/etc/apache2/conf-available$ sudo a2enconf argyle-api
Enabling conf argyle-api.
To activate the new configuration, you need to run:
  service apache2 reload
workstation@workstation-upstairs:/etc/apache2/conf-available$ sudo service apache2 reload
workstation@workstation-upstairs:/etc/apache2/conf-available$

Great, now we can get to our public index.php page without using the /public/ URL.

Slim Framework with alias URL

If something went wrong we could always disable our configuration using a2disconf. Using this alias and conf technique we can store the whole project outside the apache www folder if we wanted to and still serve content (noting that the public folder must be at least readable by the apache user depending on what you are doing).

So now that we have that working, lets try modifying the routes so we can do some custom stuff. I use a small hello world example from the Slim Framework Homepage.

$app->get('/hello/{name}', function (Request $request, Response $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name");

    return $response;
});

I find the routes.php file in the argyle-api/src folder and modify it to say hello with the name being an optional parameter.

<?php

use Slim\Http\Request;
use Slim\Http\Response;

// Routes

$app->get('/[{name}]', function (Request $request, Response $response, array $args) {
    // Sample log message
    $this->logger->info("Slim-Skeleton '/' route");

    // Render index view
    return $this->renderer->render($response, 'index.phtml', $args);
});

$app->get('/hello/[{name}]', function (Request $request, Response $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name");

    return $response;
});

One quick browse to my new argyle-api/hello/world URL and… 404. Hmmm.

404

Given that the paths are essentially rewritten aliases going through index.php to generate the responses, to get this to work with apache, we would need mod_rewrite enabled, otherwise the URLs would be expected to be existing directories. We can enable mod_rewrite using a2enmod.

workstation@workstation-upstairs:~$ sudo a2enmod rewrite
Enabling module rewrite.
To activate the new configuration, you need to run:
  service apache2 restart
workstation@workstation-upstairs:~$ sudo service apache2 restart
workstation@workstation-upstairs:~$

A quick phpinfo() check later and we can confirm that mod_rewrite is enabled.

Found mod_rewite

We try the hello world url again and… Internal server error. sigh

Internal server error

Ok, well at least mod_rewite is working (if it wasn’t, we would probably need to add AllowOverride All to the argyle-api.conf right before Options FollowSymLinks so the .htaccess would be processed). Anyway, let’s have a look at what’s in the .htaccess for the public folder as part of this skeleton app.


  RewriteEngine On

  # Some hosts may require you to use the `RewriteBase` directive.
  # Determine the RewriteBase automatically and set it as environment variable.
  # If you are using Apache aliases to do mass virtual hosting or installed the
  # project in a subdirectory, the base path will be prepended to allow proper
  # resolution of the index.php file and to redirect to the correct URI. It will
  # work in environments without path prefix as well, providing a safe, one-size
  # fits all solution. But as you do not need it in this case, you can comment
  # the following 2 lines to eliminate the overhead.
  RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
  RewriteRule ^(.*) - [E=BASE:%1]

  # If the above doesn't work you might need to set the `RewriteBase` directive manually, it should be the
  # absolute physical path to the directory that contains this htaccess file.
  # RewriteBase /

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^ index.php [QSA,L]

Looks like they were trying to do something fancy .htaccess stuff when installed in a subdirectory or using an alias (both of which I am doing), but it clearly isn’t working for my environment. Let’s simplify and try again.

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

Still internal server error. Looks like we may need to set that RewriteBase directive manually. The comment from the previous version of the .htaccess from the skeleton project says to set it to the absolute physical path to the directory, but it makes more sense to me to set it to the root relative directory for the alias.

RewriteEngine On
RewriteBase /argyle-api/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

And there we go, hello world!

Hello world

In this post I have covered:

  1. How to setup the Slim Framework and create a project from the skeleton code
  2. Get the Slim Framework to work using an Apache alias
  3. Check that mod_rewite is enabled (and enable it if it isn’t)
  4. Get additional routes to work by modifying the .htaccess to include the RewriteBase directive

That’s quite a bit for one post so I think I’ll cover the creation of the JSON API in a subsequent posting. Thanks for reading, see you next time!