Worklog Part 2: Creating navigation with bootstrap

Creating navigation with bootstrap

We were start from already prepared bootstrap theme sample and we will try to take as much as possible out of it.


Navigation bar will be at the top of each application page and should be always visible. All shared elements which are on all pages,  are going to the base layout, this is base.html.twig in our application.

If you need some specific information about bootstrap you can probably found it on this site. site is one of my primary sources for web and SQL related questions.

After some experimentation I create something like that:

2015-12-06 12_18_10-Dashboard - Bisaga Worklog

Main navigation bar at the top is always visible even when we scroll down beyond visible space (navbar-fixed-top). This require little additional CSS settings that our pages on the top has some default margin.


In our worklog.css we define default top margin as:

body {

Because we define “body” element as selector, this will be valid for every web page in our web site.

Navigation tag with bootstrap  settings

One part of template looks like:

    <div id="main-panel">
        <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
            <div class="navbar-header">
                <a class="navbar-brand" href="{{app.request.basepath}}/index.php">
                    <img alt="Bisaga" src="{{app.request.basepath}}/images/w24x24.png"/>
                <ul class="nav navbar-top-links navbar-right">
                        <a href="{{app.request.basepath}}/index.php/workday">
                            <i class="fa fa-calendar-o fa-fw fa-lg"></i>

We create navigation bar with brand icon on the left and some icons with two direct links (calendar, alerts) and two drop-down menus for some additional actions.

You can download the whole file from here.


Default page on the web site will be used as dashboard, so we create dashboard.html.twig template file and controller class in  DashboardController.php . We also connect default ‘/’ route to this controller (Application.php).

    private function createRoutes() 
        $this->get('/', 'Bisaga\Controller\DashboardController::show');


For links we use direct path navigation with little help of Silex Application constants, so links are assembled like this:


In some point I will probably study assetic component from Symfony but for now I will stick to this simplest semi-direct references.


Icons are actually characters from the font Font-Awesome. Images on the navigation bar are slightly larger (fa-lg) then those in drop-down menus.

<a href="{{app.request.basepath}}/index.php/workday">
    <i class="fa fa-calendar-o fa-fw fa-lg"></i>

You can learn more about font awesome usage here.

This sample application is published on github.

Worklog Part 1: Building web application with Bootstrap

Integrating Start Bootstrap template

We will implement bootstrap based web application theme in Silex application. The best way to start out is with already available theme.

To install it into our application just execute bower install command in application web folder:

H:\Ampps\www\worklog\web>bower install startbootstrap-sb-admin-2

After installation of all required components (including jquery, bootstrap  and a few others) we will need some help how to use them. You can download sample implementation and start learn directly from included sample application.

After unzipping sample into local www folder we can navigate to it and start inspecting how it is constructed.

2015-11-26 19_08_40-SB Admin 2 - Bootstrap Admin Theme

This beautiful sample contain almost everything what is needed to build very powerful web application !

Next step is converting part of this sample code into our application.

Integrating template in silex application

We need to analyze index.html file from the sample and transfer definitions into our base.html.twig file.  As you can see in the content of twig file, we only copy “link” and “script” nodes to our file and adjust locations of used resources.

After you have everything in place the response after loading index.php should be something like this (Firefox/Developer tools/Network screen):

2015-11-26 19_46_09-Network - http___localhost_worklog_web_index.php

All resources should be loaded successfully .

The content of twig base file:

{# Base page template #}
    {% block head %}
        <!-- Standard HTML head -->
        <meta charset="UtF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>{% block title %}{% endblock %} - Bisaga Worklog</title>
        {% block stylesheets %}{% endblock %} 
        <!-- FAVICON -->
        <link rel="icon" type="image/x-icon" href="{{app.request.basepath}}/favicon.ico" />
        <!-- Bootstrap Core CSS -->
        <link rel="stylesheet" href="{{app.request.basepath}}/bower_components/bootstrap/dist/css/bootstrap.min.css" />

        <!-- MetisMenu CSS -->
        <link href="{{app.request.basepath}}/bower_components/metisMenu/dist/metisMenu.min.css" rel="stylesheet">

        <!-- Timeline CSS -->
        <link href="{{app.request.basepath}}/bower_components/startbootstrap-sb-admin-2/dist/css/timeline.css" rel="stylesheet">

        <!-- SB-Admin CSS -->
        <link href="{{app.request.basepath}}/bower_components/startbootstrap-sb-admin-2/dist/css/sb-admin-2.css" rel="stylesheet">

        <!-- Morris Charts CSS -->
        <link href="{{app.request.basepath}}/bower_components/morrisjs/morris.css" rel="stylesheet">

        <!-- Custom Fonts -->
        <link href="{{app.request.basepath}}/bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">

        <!-- Worklog custom CSS -->
        <link rel="stylesheet" href="{{app.request.basepath}}/css/worklog.css" />
        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
          <script src=""></script>
          <script src=""></script>
    {% endblock %}    
    <div id="content">{% block content %}{% endblock %}</div>
    <div id="footer">
        {% block footer %}
            &copy; Copyright 2015 by <a href="">Bisaga</a>.
        {% endblock %}
    {% block javascripts %}{% endblock %}
    <!-- jQuery -->
    <script src="{{app.request.basepath}}/bower_components/jquery/dist/jquery.min.js"></script>
    <!-- Bootstrap Core JavaScript -->
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="{{app.request.basepath}}/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>    

    <!-- Metis Menu Plugin JavaScript -->
    <script src="{{app.request.basepath}}/bower_components/metisMenu/dist/metisMenu.min.js"></script>

    <!-- Morris Charts JavaScript -->
    <script src="{{app.request.basepath}}/bower_components/raphael/raphael-min.js"></script>
    <script src="{{app.request.basepath}}/bower_components/morrisjs/morris.min.js"></script>

    <!-- Custom Theme JavaScript -->
    <script src="{{app.request.basepath}}/bower_components/startbootstrap-sb-admin-2/dist/js/sb-admin-2.js"></script>

Worklog application folder now grow little larger because of new front-end components in bower_components folder.

2015-11-26 20_03_12-Programmer's Notepad - [index.html]

At this point our web application is almost empty, but we have all  necessary components ready to use.

This example application is available on the Github.

How to add data migration support to Silex

Add Doctrine data migrations to Silex application

There are quite a few blog articles about using doctrine migrations in different project types and this article was closest to what we need in Silex project.

Install doctrine migration module

We add a few required components to composer.json and execute update command.

     "doctrine/dbal": "~2.2",
     "symfony/console": "^2.7",
     "doctrine/migrations": "^1.1"

We need to have symfony/console module already installed and prepared for extending with new commands, as I wrote in this article.

Add migration commands to console.php program.  You can find them in vendor folder under:  “/lib/Doctrine/DBAL/Migrations/Tools/Console/Command” path.

// application.php

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

use Symfony\Component\Console\Application;

$application = new Application();

//Migrations commands 
$application->add(new \Doctrine\DBAL\Migrations\Tools\Console\Command\DiffCommand());
$application->add(new \Doctrine\DBAL\Migrations\Tools\Console\Command\ExecuteCommand());
$application->add(new \Doctrine\DBAL\Migrations\Tools\Console\Command\GenerateCommand());
$application->add(new \Doctrine\DBAL\Migrations\Tools\Console\Command\MigrateCommand());
$application->add(new \Doctrine\DBAL\Migrations\Tools\Console\Command\StatusCommand());
$application->add(new \Doctrine\DBAL\Migrations\Tools\Console\Command\VersionCommand());


Add migrations configuration files:

Migrations configuration define base settings for migration generation and execution environment.

name: Doctrine Migrations
migrations_namespace: Bisaga\Migrations
table_name: doctrine_migration_versions
migrations_directory: ./app/Migrations

Database configuration is needed that migrations can really do the job.

return array(
        'host'      => 'localhost',
        'port'      => '3306',
        'driver'    => 'pdo_mysql',
        'charset'   => 'utf8mb4',
        'dbname'    => 'bisagasamples',
        'user'      => 'dbuser',
        'password'  => 'dbpassword',
        'defaultTableOptions' => [ 'charset'=> 'utf8mb4', 'collate' => 'utf8mb4_slovenian_ci'],

Default table options

How to set default create options for tables and columns such as default collation sequence and character set ?

‘defaultTableOptions’ => [
‘charset’=> ‘utf8mb4’,
‘collate’ => ‘utf8mb4_slovenian_ci’

To set custom character set and collation just add “defaultTableOptions” array with your specific  default settings in connection configuration array (file: migrations-db.php).

Console command interface

You can test it immediately with console command:

>php console.php list migration

2015-11-18 21_47_07-Command Prompt

With data migrations you can easily update your project database with a programmatic interface with version support.  Your database schema can grow without risk of loosing control over it.

Sample script

Empty class is generated with “migrations:generate” command. After initial generation you code migration changes manually.


namespace Bisaga\Migrations;

use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;

 * Auto-generated Migration: Please modify to your needs!
class Version20151117184031 extends AbstractMigration
     * @param Schema $schema
    public function up(Schema $schema)
        $worklogtable = $schema->createTable('worklogtable');
        $worklogtable->addColumn('id', 'integer', ['unsigned' => true, 'autoincrement'=>true]);
        $worklogtable->addColumn('workdate', 'date', ['notnull'=>true]);
        $worklogtable->addColumn('location', 'string', ['length' => 60]);
        $worklogtable->addColumn('milage', 'decimal', ['precision' => 8, 'scale'=>2]);
        $worklogtable->addColumn('starttime', 'time');
        $worklogtable->addColumn('status', 'string', ['length' => 30]);
        $worklogline = $schema->createTable('worklogline');
        $worklogline->addColumn('id', 'integer', ['unsigned' => true, 'autoincrement'=>true]);
        $worklogline->addColumn('fromtime', 'time');
        $worklogline->addColumn('totime', 'time');
        $worklogline->addColumn('logtime', 'time');
        $worklogline->addColumn('description', 'string', ['length' => 250]);
        $worklogline->addColumn('taskcode', 'string', ['length' => 150]);
        $worklogline->addColumn('worklogid', 'integer', ['unsigned'=>true]);
        $worklogline->addForeignKeyConstraint($worklogtable, array('worklogid'), 
                        array('id'), array('onUpdate'=>'CASCADE', 'onDelete'=>'CASCADE'));

     * @param Schema $schema
    public function down(Schema $schema)
        // this down() migration is auto-generated, please modify it to your needs

After generating first migration file the file is saved in configured migrations folder (./app/Migrations/).

2015-11-19 00_53_25-silex03 - NetBeans IDE 8.0.2

Run migrations

Your migration scripts will not running automatically, you need to do it by yourself.  To execute open migrations you need to run command:  “migrations:migrate” .

>console migrations:migrate


Adding symfony console support to silex application

Adding console support to Silex application

Silex is a micro-framework and as such doesn’t force you to use so many components included in full stack symfony framework. But, if we want to use some of them we need to do some extra work.

Install console component

Installing with composer is simple, just add additional requirements for “symfony/console”  to composer.json file. The full content of my composer.json file as currently is :

    "name": "igor.babic/bisaga",
    "description": "Sample silex application",
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=5.5.9",
        "silex/silex": "~1.3",
        "doctrine/dbal": "~2.2",
        "twig/twig": "^1.23",
        "symfony/twig-bridge": "^2.7",
        "symfony/console": "^2.7",
        "doctrine/migrations": "^1.1"     
    "autoload": {
        "psr-4" : {
            "Bisaga\\" : "src/"

After changing composer.json file just don’t forget to execute update command.

H:\Ampps\www\silex03>composer update

This way all required components for console support are installed. We can always check “vendor” folder after update, there will be everything required by composer definitions.

Creating main console program

To be able to use “console” command, we need console.php program first. We have very easy job here, because everything is already written, we just need to get it from symfony site .

We create console.php file with following content:

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

use Bisaga\Infrastructure\Console\GreetCommand;
use Symfony\Component\Console\Application;

$application = new Application();
$application->add(new GreetCommand());

As you can see, we already initialized one sample command here, “GreetCommand”, we need to create the command too.

Creating sample command in php

My silex application is as simple as it can be. The best in using micro-framework is that there is no forced structure from framework. We can create our own folder structure, so my is something like that:

2015-11-15 20_22_50-Programmer's Notepad - [console]

My  folder structure is not yet complete, it will evolve as application grows.

We place GreetCommand.php file from symfony web guide to our Infrastructure \ Console folder and change namespace to reflect our folder structure.


namespace Bisaga\Infrastructure\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends Command
    protected function configure()
            ->setDescription('Greet someone')
                'Who do you want to greet?'
               'If set, the task will yell in uppercase letters'

    protected function execute(InputInterface $input, OutputInterface $output)
        $name = $input->getArgument('name');
        if ($name) {
            $text = 'Hello '.$name;
        } else {
            $text = 'Hello';

        if ($input->getOption('yell')) {
            $text = strtoupper($text);


Now we can test new command line program simply by calling console.php program with demo:greet parameter:

H:\Ampps\www\silex03>php console.php demo:greet

We put console.php file directly to the root folder of our application, so we call console command from that folder.

If you wish to simplify call to the console.php program, you can write simple  batch program and put it somewhere in the system path:

ECHO Command: php console.php %*
call php %cd\%console.php %*

Now, you can call any console command as :

H:\Ampps\www\silex03>console demo:greet
Command: php console.php demo:greet

For checking which commands are already initialized in the application, we execute “list” command:

H:\Ampps\www\silex03>console list

2015-11-15 21_00_19-Command Prompt

That’s it !

We are ready to add any commands to our silex application !




Cron job – running PHP in the background

Background jobs

If you wish to write responsive web applications, you will need to  push some operations in the background. That way you can just register request for some long running task and immediately return to the client.

If you search the web, there are many ways how to achieve this, but not so many implementation are ready to do it in constraint environment of simple web hosting.

My web page is hosted by GoDaddy with so called “Linux hosting with cPanel”. I have PHP and MySql, but not much beside this. GoDaddy luckily allows cron jobs.  We simply register some command as “cron job” to run unattended at specified frequency.

For proof of concept I will need simple php program and run it as cron job.  At each cron job iteration we will insert one record into database table. We just want to proof that php program can run in the background as  cron job.

Create some database and add “tasklog” table.

create table tasklog (
    id int not null auto_increment,
    created datetime,
    primary key (id)

Our simple PHP program is :

try {
    $host = "localhost";
    $dbname = "your_database";
    $user = "your_db_user";
    $pass = "your_password";

    # MySQL with PDO_MYSQL
    $db = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);

    $stmt = $db->prepare("INSERT INTO tasklog(`created`) VALUES(NOW())");
    $db = null;
catch(PDOException $e) {
    echo $e->getMessage();

To test it, just put the “taskrun.php” in your “public_html” folder and navigate to it. If something will go wrong in the program, the settings for exceptions are set to report it to the client. Please test the program until everything not running smoothly.

Register cron job

You can put program file to any folder. If folder is not under “public_html” folder, it will be inaccessible from the public web and that way will be much more secure. We create new “jobs” folder under our root home folder and move “taskrun.php” program there.

In the “cPanel” locate “Advanced” section and select “Cron Jobs”:

2015-10-22 22_42_41-cPanel - MainCreate new job with a one minute frequency as:

/usr/local/bin/php "$HOME/jobs/taskrun.php" > /dev/null 2>&1

To prevent email to be sent for each iteration, we put redirection into the command ( > /dev/null 2>&1 ).

2015-10-22 22_45_26-cPanel - Cron JobsWait a minute and check if there are some records in the “tasklog” table. You will see something like this:

2015-10-23 _ localhost _ bisagasamples _ tasklog _

Success !

Of course, this is only proof of concept for running something unattended in the background. But I think there are already some open source job-task runner out there.