Sakila- Dagger 2 Dependency Injection

REST web server and dependency injection

The code from this article is available here.

I described basic usage of Dagger 2 already in this article, now we need to implement dependency injection mechanism in the web application. Typical web application would have at least two layers, one for the web server itself and another for client request processing.

Spark Java web server internally use embedded Jetty web server. To setup and start the server we provide some central services like configurations, authentication, statistics etc. Those services are usually instantiated once per whole application.

For each client request a new thread is allocated for the whole duration until the request is processed. Each client request trigger a method defined in the router.

Depend on the design decisions but we usually want that each request will be processed by new object instance (an example is ActorResource in the picture). We will probably have multi threaded problems or simply data leaks from one user to another if we don’t create new instance each time the request is received.

Some objects needed in the typical request processing scenario have different lifecycle requirements, for example database transaction object must be the same for the whole duration of the started transaction but different for each user. There are times when we need two independent transactions in one service request for example. Transactions are span over multiple service objects for example.

On the other side when we do not require transaction (read only processing) we would be better of if we use first available connection allocated to us from the connection pool for the smallest amount of time possible.

As we see from the use cases above there are very different lifecycle scenarios and dependency injection must support them all.

Scopes

We will need at least two scopes for our web application. First scope is application level scope. In the dagger this scope is on by default. Each time we tag a class as a “@Singleton”, the object will be instantiated on the application level and all subsequent requests for this object will return the same instance. So the singleton representing an “application scope” by default. No need for specific scope definitions.

Classes without any scope annotations (no @Singleton or any other scope) are always provided with a new instance.

To manage injection on the application level we create ApplicationComponent interface and ApplicationModule class.

package com.bisaga.sakila.dagger;

import com.bisaga.sakila.Application;
import com.bisaga.sakila.server.ConfigProperties;
import com.bisaga.sakila.server.RequestSession;

import dagger.Component;
import javax.inject.Singleton;

@Singleton
@Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {

    // we first need to create instance of Application on which we will inject dagger into
    Application application();

    // Parent component is obliged to declare sub components getters inside its interface (RequestScope component)
    RequestComponent requestComponent();

    // We will need new instance of RequestSession for each user requests (save instance to the request attributes)
    String REQUEST_SESSION_ATTR_NAME = "requestSession";
    RequestSession requestSession();

    ConfigProperties configProperties();

}

Application module class:

package com.bisaga.sakila.dagger;

import com.bisaga.sakila.server.ConfigProperties;
import com.bisaga.sakila.server.HikariProperties;
import com.bisaga.sakila.server.RuntimeEnvironment;
import com.bisaga.sakila.server.GsonTransformer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import dagger.Module;
import dagger.Provides;
import spark.ResponseTransformer;

import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Properties;


@Module
public class ApplicationModule {
    // this is a parameter member variable
    private RuntimeEnvironment runtimeEnvironment;

    // we need an constructor to take parameter in
    public ApplicationModule(RuntimeEnvironment runtimeEnvironment) {
        this.runtimeEnvironment = runtimeEnvironment;
    }


    // This provide method at the creation of the instance of ConfigProperties object take the parameter from the local member variable.
    @Provides
    @Singleton
    public ConfigProperties provideConfigProperties() {
        return new ConfigProperties(runtimeEnvironment);
    }

    // All provide methods should be static, generated dagger code is much simpler and smaller,
    // except when module member variables are needed to create new instances
    // Gson constructor is not available to be annotated with inject (not our source), instead we write provider method
    // the method is static, because we do not need any module member variable to build new instance
    @Provides
    @Singleton
    public static Gson provideGson(){
        return new GsonBuilder().setPrettyPrinting().create();
    }


    // Here we provide an instance of the ResponseTransformer interface. Because we didn't define which implementation
    // we need at injecting place we write provide method with the injected parameter expressed with a required interface type
    // If we have more then one possible implementation, we put on "Named" annotation and associate the same name at
    // injected constructor variable
    @Provides
    @Singleton
    public static ResponseTransformer provideResponseTransformer(GsonTransformer gsonTransformer){
        return gsonTransformer;
    }

    // here we provide a String instance. We need the provide method, because many "String" objects are possible.
    // We use "Named" annotation to be able to differentiate this one from other possible provide methods.
    // At injecting place we also define same "Named" annotation to connect the proper one
    // The method could be static, because is independent of local member variables
    @Provides
    @Singleton
    @Named("api_key")
    public static String provideApiKey(){
        return "01657172-ecc0-4cb6-8486-5e7e05a0876f";
    }


    // Here we provide external library class as singleton with our extended properties class as constructor parameter
    @Provides
    @Singleton
    public static HikariConfig provideHikariConfig(HikariProperties properties) {
        return new HikariConfig(properties);
    }

    // Here we provide external library class as singleton (Hikari Connection Pool Data source, holder of pooled connections)
    @Provides
    @Singleton
    public static HikariDataSource provideHikariDataSource(HikariConfig config) {
        return new HikariDataSource(config);
    }


}

At the application level example we present next use cases:

  • creating an instance with the supplied constructor parameter (ConfigProperties service)
  • instantiate objects  from the external dependencies with provide method (Gson)
  • instantiate specific implementation for an interface (ResponseTransformer interface)
  • instantiate an String object with named annotation (using name as differentiator)

Request scope

To create “request scope” we write one annotation interface (“@interface”) one component (RequestComponent) and at least one module class (RequestModule). The component must have sub-component annotation.

To manage DI on the request scope level we create annotation type interface RequestScope , RequestComponent interface and RequestModule class.

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestScope {
}

Just to be clear I want to emphasize that each class annotated with the “@RequestScope”  annotation will be instantiated exactly once per created instance of RequestComponent class.  It means that scope annotations represent local singletons.

package com.bisaga.sakila.dagger;

import com.bisaga.sakila.resource.ActorResource;
import com.bisaga.sakila.server.*;
import dagger.Subcomponent;

import java.sql.Connection;

@RequestScope
@Subcomponent(modules = {RequestModule.class})
public interface RequestComponent {

    ActorResource actorResource();
}

Module class:

package com.bisaga.sakila.dagger;

import com.bisaga.sakila.dbmodel.tables.daos.ActorDao;
import com.bisaga.sakila.server.JooqConfigurationBuilder;
import com.bisaga.sakila.server.Transaction;
import com.bisaga.sakila.server.TransactionBuilder;
import dagger.Module;
import dagger.Provides;
import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.impl.DSL;

import javax.inject.Named;

@Module
public class RequestModule {

    // Provides primary transaction/connection for the whole time request ran (loaded at the first component needs)
    // Because it is request scoped, it will be called only once, any other time the request scoped instance will return
    @Provides
    @RequestScope
    public static Transaction provideTransaction(TransactionBuilder transactionBuilder) {
        return transactionBuilder.create(false);    // default autoCommit=False, commit/rollback must be called manually
    }

    // Default configuration with transactions enabled, this is RequestScoped default database connection
    @Provides
    @RequestScope
    public static Configuration provideConfiguration(JooqConfigurationBuilder jooqConfigurationBuilder) {
        return jooqConfigurationBuilder.build();
    }

    // Default DSLContext with default configuration
    @Provides
    @RequestScope
    public static DSLContext provideDSLContext(Configuration configuration) {
        return DSL.using(configuration);
    }

    @Provides
    public static ActorDao provideActorDao(Configuration configuration){
        return new ActorDao(configuration);
    }

}

We use localized singletons especially for the transaction and jooq data access support.

Provide methods are optional, we can decorate classes in the source code with the corresponding annotations.

Service classes

If we analyze the code in the consumer classes, it become ridiculously simple.  All externalized requirements are created by the dagger code almost hassle free.

In the ActorResource class for example we analyze received request,  extract parameters and start business logic. The transaction object is created on the request scope and pass down to all service objects in need to collaborate in the same database transaction.

package com.bisaga.sakila.resource;

import com.bisaga.sakila.dagger.RequestScope;
import com.bisaga.sakila.dbmodel.tables.pojos.Actor;
import com.bisaga.sakila.server.Transaction;
import com.bisaga.sakila.service.ActorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.Request;
import spark.Response;

import javax.inject.Inject;
import java.util.List;

@RequestScope
public class ActorResource {
    private static final Logger LOG = LoggerFactory.getLogger(ActorResource.class);
    private final Transaction transaction;
    private final ActorService actorService;

    @Inject
    public ActorResource(
            Transaction transaction,
            ActorService actorService
    ) {
        this.transaction = transaction;
        this.actorService = actorService;
    };

    public List<Actor> getActors(Request request, Response response) {
        try {
            // Analyse & extract request parameters

            // multiple SQL statements within single transaction, first we need to start the transaction
            transaction.begin();

            // Call services with parameters (if any)
            List<Actor> actors = actorService.getActors();

            // Commit transaction and release the underline connection to the pool
            transaction.commit();

            // Return list, it will be transformed by GsonTransformer and returned to the browser as Json
            return actors;

        // Catch expected business exceptions and throw them with meaningful messages and present them to the customer
        } catch (Exception e) {
            // Rollback transaction and release the underline connection to the pool
            transaction.rollback();
            // rethrow all exceptions to master exception handler which create response for the customer
            throw e;
        }
    }
}

In the ActorService class we receive all constructor parameters from the dagger automatically.

package com.bisaga.sakila.service;

import com.bisaga.sakila.dagger.RequestScope;
import com.bisaga.sakila.dbmodel.tables.daos.ActorDao;
import com.bisaga.sakila.dbmodel.tables.pojos.Actor;
import org.jooq.DSLContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.util.List;

@RequestScope
public class ActorService {
    private static final Logger LOG = LoggerFactory.getLogger(ActorService.class);

    private final DSLContext db;
    private final ActorDao actorDao;

    @Inject
    public ActorService(DSLContext db, ActorDao actorDao) {
        this.db = db;
        this.actorDao = actorDao;
    }

    public List<Actor> getActors() {
        return actorDao.findAll();
    }

    public Actor getActor(int id) {
        return actorDao.fetchOneByActorId(id);
    }
}

The class ActorService require two objects at the constructor: jooq DSLContext  class and ActorDao class.

DSLContext class is part of the Jooq data access library and is instantiated with the provider method “provideDSLContext”. It is annotated as @RequestScope it means the RequestComponent will keep single instance of it for the duration of one request cycle.

ActorDao object is also generated by Jooq library so we couldn’t tag it with scope annotation in the source (so we wrote the provideActorDao method in the request module).

Summary

Dagger calculate all dependencies in the compile time and generate the required code for the whole graph of dependencies and is able to instantiate appropriate objects at appropriate times really fast.

The code from this article is available here.

Other resources:

More about dagger scopes, sub-components .

Sakila: Sample app project setup

This sample application will integrate quite a few very nice open source tools available to every developer:

  • Postgresql – database
  • Flyway – database migration tool
  • Jooq – Java object oriented querying + HikariCP connection pool
  • Dagger 2 – dependency injection
  • SparkJava – fast and simple web server + GSON Json serializer
  • JavaScript Polymer SPA application framework
  • Vaadin Elements components

The application will consist of many modules :

Postgresql – database

Initialize sample database

For start we will install sample sakila database  in the local Postgresql database. Restore downloaded file into locally created database.

C:\Program Files\PostgreSQL\9.6\bin\pg_restore.exe --host "localhost" --port "5432" --username "postgres" --no-password --dbname "sakila" --no-owner --no-tablespaces --verbose "C:\\DVDRENTAL.TAR"

Sakila sample database is open source demo database and represent database model of DVD rental store. It  consist of  15 relational tables, 7 views and few other database details and it’s full of data.

Well that’s database with full of test data for development environment. If we need empty database (for production for example) we need to start initialization DDL script to build the database.

To create the script from the existing database use the command pg_dump which is capable of exporting database in the form of sql commands :

pg_dump -s -U postgres sakila > V1.0.1__sakila_init.sql

To export database without any data , only schema definitions we use “schema only” (-s) option.

Flyway migrations

Create flyway config file and “migrations” folder under the project root.

flyway.url=jdbc:postgresql://localhost:5432/sakila
flyway.user=postgres
flyway.password=postgres
flyway.schemas=public
flyway.locations=filesystem:./migrations

Add “fw” command somewhere on the path.

#!/bin/sh
/c/Programs/flyway-4.2.0/flyway $@

Put the “V1.0.1__sakila_init.sql” file in the migrations folder. If everything works as expected the “info” command should report the pending migration.

Flyway migration and initial database state after database restore

After restoring the database with test data in it we need to “baseline” initial migration. Initial sql script to create empty database was bypassed with restore. The V1.0.1__sakila_init.sql migration script was still pending.

With the baseline command we agree that the migration is not needed and you mark the migration version as migrated.

 

Setup java server project

In the IDE (IntelliJ IDEA Community 2017.2) create new console type project “sakilaweb/server”.

Setup git-bash terminal as default intellij terminal

Jooq – object oriented querying

Create jooq config file and add jooq command somewhere on the path.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration xmlns="http://www.jooq.org/xsd/jooq-codegen-3.10.0.xsd">
    <!-- Configure the database connection here -->
    <!-- TRACE, DEBUG, INFO, WARN, ERROR, FATAL -->
    <logging>INFO</logging>
    <jdbc>
        <driver>org.postgresql.Driver</driver>
        <url>jdbc:postgresql://localhost:5432/sakila</url>
        <user>postgres</user>
        <password>postgres</password>
    </jdbc>

    <generator>
        <!-- The default code generator. You can override this one, to generate your own code style.
             Supported generators:
             - org.jooq.util.JavaGenerator
             - org.jooq.util.ScalaGenerator
             Defaults to org.jooq.util.JavaGenerator -->
        <name>org.jooq.util.JavaGenerator</name>

        <database>
            <!-- The database type. The format here is:
                 org.util.[database].[database]Database -->
            <name>org.jooq.util.postgres.PostgresDatabase</name>

            <!-- The database schema (or in the absence of schema support, in your RDBMS this
                 can be the owner, user, database name) to be generated -->

            <inputSchema>public</inputSchema>

            <!-- All elements that are generated from your schema
                 (A Java regular expression. Use the pipe to separate several expressions)
                 Watch out for case-sensitivity. Depending on your database, this might be important! -->
            <includes>.*</includes>

            <!-- All elements that are excluded from your schema
                 (A Java regular expression. Use the pipe to separate several expressions).
                 Excludes match before includes -->
            <excludes></excludes>
        </database>

        <generate>
            <pojos>true</pojos>
            <daos>true</daos>
        </generate>

        <target>
            <!-- The destination package of your generated classes (within the destination directory) -->
            <packageName>com.bisaga.sakila.dbmodel</packageName>

            <!-- The destination directory of your generated classes. Using Maven directory layout here -->
            <directory>./database</directory>
            <encoding>UTF-8</encoding>
        </target>
    </generator>
</configuration>

Bash command:

#!/bin/sh
DIR="C:/Programs/jOOQ-3.10.1/jOOQ-lib/"
DRIVER="C:/Programs/drivers/postgresql-42.1.4.jar"
VER="3.10.1"
CP=$DIR"jooq-"$VER".jar"
CP=$DIR"jooq-meta-"$VER".jar;"$CP
CP=$DIR"jooq-codegen-"$VER".jar;"$CP
CP=$DRIVER";"$CP
java -classpath $CP org.jooq.util.GenerationTool jooq.xml

Add “jooq-3.10.1.jar” library to project dependencies. Add “postgresql-42.1.4.jar” if you use the same database.

Run code generation tool with “jooq” command in the terminal at the project root.

After code was successfully generated in the “./database” folder you will get a bunch of database related code ready made ( database schema, POJOs, and DAOs).

The project with generated code will now look like :

Setup Dagger 2

Configure IDEA for annotations processor.

Add dagger dependencies (dagger-compiler only as “Provided” because it is used only for code generation ).

Setup SparkJava web server

Add few references to the project dependencies and setup “hello-world” web sample just to be sure everything is setup us expected before start real coding.

com.sparkjava:spark-core:2.0.0
org.slf4j:slf4j-simple:1.7.25

com.google.code.gson:gson:2.8.2
com.google.dagger:dagger:2.12
com.google.dagger:dagger-compiler:2.12

Create main procedure as :

package com.bisaga.sakila;

import static spark.Spark.*;

public class Main {

    public static void main(String[] args) {
        get("/hello", (req, res)-> "Hello !");
    }
}

Now if you run the application you should already get the first page:

Publish to the github

First enable VSC support in the local project and add .gitignore file to the project. Next we add files to the local git repository created in the project.

If we want  to push code to the remote repository we need to create it to have repository to commit to. Login to the github and create new empty repository.

The code for the server side project is available here.

 

 

Next : In the next installment I will put the generated database layer into the use and expose first REST service.

 

 

 

 

 

Data migrations in Node applications

Setup db-migrate tool

As your application grow your database model evolve too.  The changes from one version to another is made by migrations. Each migration in the db-migrate tool is in essence  a small javascript program. You can code your migration changes manually in the javascript syntax or generate small javascript program (with –sql-file option) to execute your SQL script files. One for the UP and one for the DOWN function of migration.

up: you upgrade your database schema to the new version
down: you reverse latest changes to the previous version
create: create new migration, with an –sql-file option create a SQL file runner

Installation

We install db-migrate module and corresponding database driver with npm.

npm install db-migrate
npm install db-migrate-pg

Depends where you install it the command is available as:

$ ./node_modules/.bin/db-migrate
or
$ db-migrate

It is possible to add local ./node_module/.bin folder to the local path variable as I described in this article and call it the same way as you would if you install it in to the global space.

Configuration

Minimal config file (database.json) to work with postgres database in the development environment:

{
  "dev": "postgresql://postgres:postgres@localhost:5432/dbsam"  
}

The file should be in the main project folder. More about configuration.

Migrations with SQL files

To create new empty migration use the command:

$ db-migrate create new_mig_name --sql-file

By default your migration javascript files reside in the “./migrations” folder and sql files in the “./migrations/sqls”  subfolder.

Three new files are created, all files are prefixed with the timestamp which represent order of execution.

Additional two files with the suffix sql are prepared for your upgrade script and downgrade script. If you wish to have ability to downgrade database to the previous level make sure you write down script to.

Example of upgrade script (it is just for the sake of example, you basically write DDL statements with the familiar SQL syntax):

/*
* Table: Currency  
*
* Contains currencies 
*/
CREATE TABLE currency (
	code                varchar(60) not null,               -- code 
	abbreviation        varchar(60),						-- ex. EUR, USD
	description         text,								
	row_id              serial primary key,                 -- row identifier, autonumber, pk 
	org_id				smallint not null,					-- multitenant identifier 
	co_id				int NOT NULL,						-- company identifier  
	created_at          timestamptz not null default now(),
	created_by          varchar(60) not null,
	modified_at         timestamptz,
	modified_by         varchar(60),
	CONSTRAINT currency_sys_org_fk FOREIGN KEY (org_id) REFERENCES sys_org (row_id),
	CONSTRAINT currency_sys_co_fk FOREIGN KEY (co_id) REFERENCES sys_co (row_id)
) WITHOUT OIDS;                                               -- don't create object identifier field 
CREATE INDEX currency_org_id_fkix ON currency (org_id);
CREATE INDEX currency_co_id_fkix ON currency (co_id);
CREATE INDEX currency_created_at ON currency (created_at DESC) ;
CREATE INDEX currency_modified_at ON currency (modified_at DESC) ;

Example of downgrade script:

DROP TABLE currency;

Run “db-migrate up” command and all your migrations which are not yet executed will run.

The migration log is kept in the database migrations table. The table is automatically created at first run.

Other useful commands

reset: will rewind your database to the state before first migration is called

Using db-migrate with typescript

If you write node programs with the typescript you probably wish to use it with migration to. I didn’t go in this direction simply because I write my scripts in SQL language and runners are just perfect already in javascript. Because the migration are already in the javascript (the runner part), you should exclude migrations folder from the typescript compiler path.

Sample tsconfig.json file with excluded migrations folder :

{
    "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "moduleResolution": "node",
        "noEmitOnError": true,
        "noImplicitAny": true,
        "experimentalDecorators": true,
        "sourceMap": true,
        "baseUrl": ".",
        "allowJs": true,
        "paths": {
            "*":[ 
                "src/types/*"
            ]
        },
        "outDir": "dist"
    },
    "typeRoots": [
        "node_module/@types",
        "src/@types"
    ],
    "include": [
        "src/**/*",
        "spec/**/*"    
    ],
    "exclude": [
        "dist",
        "migrations",
        "node_modules"
    ]
}

I didn’t include migration scripts to production deployment code yet, that step would be necessary if you wan’t to upgrade database automatically after deployment.

External links

Database migration tool: Db-migrate.
Gui prototyping and drawing tool: The pencil.

TomEE: Java EE server database configuration

Configure database access

I use Apache TomEE server and therefore I need to configure it for database purpose before first use.

Install database driver

Drop database driver jar file in tomee/lib folder.

Configure datasource

Resources are usually defined in  server configuration file.

Add datasource resource definition in configuration file located in  tomee/conf/tomee.xml.

<?xml version="1.0" encoding="UTF-8"?>
<tomee>  
  <Resource id="jdbc/db" type="javax.sql.DataSource">
    jdbcDriver org.postgresql.Driver
    jdbcUrl jdbc:postgresql://localhost:5432/bsg_taskmgr_db
    userName postgres
    password postgres
  </Resource>
</tomee>

Verify configuration

After server (tomee service) restart, search for your datasource in log (example log file: tomee/logs/catalina.2016-11-26.log). You will find log entry with your resource id there:

Configuring Service(id=jdbc/db, type=Resource, provider-id=Default JDBC Database)

If you restart server from inside netbeans,  just search in output window where log entries are shown.

Inject datasource in java code and use it

To inject instance of datasource where connection is needed you simply add annotation “@Resource” above variable declaration:

@Resource(name = "jdbc/db")
private DataSource ds;

Let’s see whole example with select statement (jOOQ):

package com.bisaga.demo;

import static com.bisaga.demo.generated.tables.Book.BOOK;
import com.bisaga.demo.generated.tables.records.BookRecord;
import java.sql.SQLException;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
/**
 * @author igorb
 */
public class HelloService {

    @Resource(name = "jdbc/db")
    private DataSource ds;    

    public String createHelloMessage(String name) {
        String allBooks = "";
        try {
            DSLContext db = DSL.using(ds, SQLDialect.POSTGRES);
            for(BookRecord b : db.selectFrom(BOOK).fetch() ) {
                allBooks = allBooks.equals("") ? "My books: " : allBooks+";";
                allBooks += b.getTitle();
            }
        } catch (DataAccessException e) {
            System.out.println(e.toString());
        }
        return allBooks;
    }    
}

This example code is called from REST JSON service and result in the browser looks like this:

2016-11-26-16_38_26-localhost_8080_helloworld_webresources_hello

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 :

<?php
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);
    $db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

    $stmt = $db->prepare("INSERT INTO tasklog(`created`) VALUES(NOW())");
    $stmt->execute();
  
    $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 00_10_00-n1plcpnl0026.prod.ams1.secureserver.net _ 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.