Close JetBrains Sign up for beta
Tina4Stack  v1.0.1
The Tina4 Stack
Tina4 Stack


Looking for a development stack?

Tina is the CEO of the Tina4 Corporation, as anyone knows a successful company needs a strong leader. Her skills lie in the organization of each of the girls and making sure each person collaborates properly on the task at hand. Tina is passionate about providing solutions to her customers and it is very important to her that each person is doing the job they were assigned. In a nutshell we have the following:

  • Routing
  • Migrations
  • Database Abstraction
  • Code Simplification
  • Templating
  • UI Testing with Selenium
  • WYSIWYG Report Generation
  • Email Handling
  • Object Abstraction


The Tina4Stack started with a need to bring some uniformity to the development I was doing in PHP and especially where I was working with other developers on the same project. I have often wondered if I should start using one of the popular frameworks out there but have decided against it for various reasons. Instead I have decided to share with you my workbench so to speak where you can also benefit from the work that has gone into this tool.

Tina4 simply means "This is NOT another Framework!"

Download the Latest Release

Download Latest Release


Installation is pretty straight forward. Download from the link above and unzip in your favourite place, the desktop will do fine just for a test. From there double click on the tina4.exe and you will see the following:


What is in the Stack?

The Stack is built up from the following technologies which hopefully will get your development up and running in minutes. Incidentally we do not deploy any database engines for you and this should be done separately which is something we prefer.

  • Nginx
  • PHP 5.6
    1. Curl is enabled
    2. GD2 is enabled
    3. MySQLi is enabled
    4. SQLite3 is enabled
    5. XDebug is enabled on port 9000
    6. XCache is enabled
    7. Exif is enabled
    8. FileInfo is enabled
    9. Mbstring is enabled
    10. OpenSSL is enabled
    11. Soap is enabled
    12. XSL is enabled
  • Doxygen - both Windows & Linux scripts - you are reading generated documentation now!
  • Swagger UI
  • Selenium Client
  • XCache UI
On my todo list are the Tina4 clients for Linux & MacOS

What is Tina4

Tina4 is a collection of tools which will help you with your PHP development. In order to make the tools easy to remember and use we have given them names so that you can identify with each of the personalities when you are working. For example Ruth is responsible for Routing, Maggy is responsible for migrations.

Now it may seem daunting to make use of tools you did not assist in building but you can use as little or as much of the tools as you want. After going over the core functionality of each of the girls in the stack I will explain the basics of how everything is supposed to work.



Ruth as we have said before is responsible for routing. Routing as you may be familiar with is the ability to handle URL requests to your website. You may be quite comfortable with writing routing tables in Apache or NGINX. If you decide to use Ruth you immediately have an application which will run on NGINX and Apache equally well. Ruth also handles security and sessions which are explained in her section.

Routing Example

A quick example of Ruth in action, you would get this link at http://localhost:12345/testing when developing. The output of this route would be Hello Tina4!

//Adding a GET route
Ruth::addRoute (RUTH_GET, "/testing", function () {
echo "Hello Tina4!";
Simply adding a PHP file under the routes folder and adding the above code is sufficient for Ruth to start working.

Routing Example with Variables

Wouldn't it be great if you could pass variables in your path as well, of course it would!

//Adding a PUT route
Ruth::addRoute (RUTH_PUT, "/pet/{id}", function ($someId) {
echo "Updating pet {$someId}";
The variable names in PHP don't need to be the same, the order is what counts.
The setup of the web server routes all URL requests to the tina4.php file which in turn relays them to Ruth. If your webserver has not been setup the stack will attempt to give you a configuration for Nginx or Apache.



Cody was written to automate things that may take up a lot of repetition. At this point in time Cody helps automate Bootstrap functionaflity. Cody has a very neat Ajax handler which automatically reads form variables and passes them to the URL you specify. The Ajax handler of Cody can also handle file uploading via Ajax and when used with Ruth and Debby you have very little coding to do.

Example of Bootstrap Inputf

The following code will create a properly formatted Bootstrap input with all the correct classes. You can see this in action in Maggy http://localhost:12345/maggy/create

echo (new Cody())->bootStrapInput("firstName", "First Name", "Full Names", $defaultValue = "", "text");

Example of Ajax Handler

The following code will create a Javascript function called callAjax which can be used to run Ajax commands to your routing

echo (new Cody())->ajaxHandler();
To include the Ajax handler with Kim the code is {{Cody:ajaxHandler}}!

The ajax Handler in Cody takes on the following params and should be used in Javascript on your page.

callAjax('/testing', //route to do a request to
'myDiv', //The id of the tag or input you wish to target, leave blank if your ajax result is Javascript
{id:10001}, //some JSON to pass with the request, Ruth will make it into request variables
'post', //get,post,delete etc...
true); //true or false, set to false to rewrite the URL in the browser after an Ajax call



Kim likes to work with content, if you need something working quickly in HTML whilst still having access to the power of PHP then you need to see what Kim can do. In order for Kim to work correctly you need to have an assets folder under your document root. In this folder she will look for the following folders:

  • pages
  • snippets
  • forms

Here are some examples of Kim in action, hopefully from the examples you will be creating dynamic pages in a few minutes.

Including HTML snippets

The {{include:snippet/[snippet_name]}} will include a section of a file into the existing file. This is much like the normal require or include of PHP except that it has the ability to render variables in {braces}. The path used is relative to the assets folder, so all your includes should be under assets.

<h1>Some normal html </h1>
Kim adds .html automatically so snippet/footer would parse to /web_root/assets/snippet/footer.html

Parsing Variables

Kim understands that it is cumbersome to reference variables in your HTML files. More so if you need to make sure their scope is correct.

The normal way

echo $someVariable;

Kim's way


Calling built in PHP functions

Kim understands that we need to access some built in functions in our HTML templates, so she made it easy.

The normal way

echo substr("Some really long sentence!",0,4)."...";

Kim's way

<h1>{{call:substr?"Some really long sentence!",0,4}}...</h1>
Kim only needs you to put your params in quotes when you have spaces or commas in between

Calling methods on classes

Kim is able instantiate classes as call their methods which should return either text or arrays

The normal way

$myObject = new MyObject();
echo $myObject->getName();

Kim's way

Kim uses {} to designate variables and {{}} to designate objects or control structures.



Debby got all her experience from using Cross Database Engine for PHP which has been around for 8 years now. Debby took those features and added to the functionality. She is able to connect to the most popular database engines and because she supports ODBC the possibilities are endless. Currently the tested engines are MySQL, Firebird & SQLite3. Support is offered for Oracle, Postgres, CUBRID, MSSQL (Native) & ODBC.

Creating a Database Connection

All database connection files are stored under the connections folder. You can use the interface here : http://localhost:12345/debby/create to make database connections. Each database connection will be registered as an object in Ruth which will then be accessible to your code.

An Example Connection

This connection is an SQLite3 connection

global $DEB;
$DEB = new Debby( "test.db", "", "", "sqlite3", "YYYY-mm-dd" );
Ruth::setOBJECT("DEB", $DEB);

The database path is an expression consisting of the hostname and path to the database itself. This covers a multitude of different database configurations. The hostname is only compulsory if it is not localhost or your database is attached to a specific IP address or is remote from the actual server.

//MySQL :
$dbPath = "";
$dbPath = ""; //or\test\TEST.FDB
$dbPath = "/tmp/database.db";
The connections can be accessed using Ruth::getOBJECT when needed.


Debby has a few methods which communicate with the database, you should however consider using Olga or Cody's REST interface for less effort.

//Get the Debby Object
$DEB = Ruth:getOBJECT("DEB");
//Run some SQL
$DEB->exec ("create table test (id integer, name varchar(200), primary key(id))");
//Insert a record
$DEB->exec ("insert into test (id, name) values (0, 'No One')");
//Insert another record
$DEB->insert("test", ["id" => 1, "name" => "Another Person"]);
//Update a record
$DEB->exec ("update test set name = 'Some One' where id = 0");
//Update another record
$DEB->update("test", ["name" => "Another Someone"], ["id" => 1]);
//Delete a record
$DEB->exec ("delete from test where id = 0");
//Delete another record
$DEB->delete ("test", ["id" => 1]);
//get some records
$rows = $DEB->getRows ("select * from test");
foreach ($rows as $rowId => $row) {
echo $row->NAME; //notice the capitals
$row = $DEB->getRow("select * from test", 1); //get the second row
echo $row->NAME;
Debby makes all field names uppercase so you don't have to worry about what the names are for different databases.

Debby also understands different dialects of SQL so you don't have to worry about select top or select first or limit 10. She will be able to translate those for you. She also takes care of file uploads for you.



Maggy helps you keep your database migrations current. She creates a table in your database where she tracks the changes. You can create migrations easily by going to http://localhost:12345/maggy/create.

Creating a migration

The migrations will be stored under your migrations folder and can be edited there as well. To make the migrations work you can simply click on the browser button of Tina4 and the link to http://localhost:12345/maggy will be run.

How She Does It

Maggy works from the migrations folder in your document root. She looks for files with an sql extension which have been formatted as follows:

YYYYMMDDHHMMSS <Migration description>="">.sql

There are some basic rules to follow when using Maggy and these are based on database limitations with regard to transaction handling:

  • create tables first, add the inserts in a separate migration file
  • stored procedures and views should be in their own migration files
  • indexes can be created with the tables but some database engines need the transaction of the table to be committed first



Emma is used to send emails so you don't need any other libraries. She takes care of embedding rich HTML and saves sent emails to the mailspool for you so you can see what's going on.

Sending an Email

Use the Tina4 interface to see how the email got sent.

(new Emma())->sendMail ( $recipient="Mr Recipient<>", $
$subject="Test Tina4",
$message="Some rich text or HTML",
$attachments=null );

Sending a Text / SMS message

Currently Emma uses BulkSMS to send out text messages. Let us know if you have another preference.

//requires a Bulk SMS username & password
(new Emma($username, $password))->sendText ($mobileno="8087234511", $message="Hello World!", $countryPrefix="01");



Tessa is used for UI testing, she has some simple commands which enable you to link up with Selenium server. By default she uses Firefox todo the testing. You will need to download the standalone Selenium server and run that before trying to run Tessa. Tessa can be run from http://localhost:12345/tessa

Have a look at the code in the test folder in runTessa.php Any functions that are found in files dropped in the test folder will be automatically injected into the runTessa class. That means you can call them from within the runTests methods by using $this->runMyTest().

runTessa Example

IWantTo("Navigate to the extra running instance of my local site http://localhost:12346");
IWantTo("Check the landing page for Tina4");
IExpectToSee("Tina4 Release ".TINA4_RELEASE);
IWantTo("See if there is some other things");
IExpectToSee("Some other things");

Other Examples

//Other examples


Olga enables the developer to use classes as data stores for data, these objects can be mapped back to a database which Olga will use to save or restore data from If you run xcache on your system, Olga will automatically persist these objects in memory making the load on the server very light.

Creating a Single Object

We could create a Pet object, all classes need to have an id for the system to reference it by from memory

class Pet extends Olga {
var $id;
var $name;
var $type;
var $mapping = Array([ "table" => "tbl_pet",
"fields" => [
"id" => ["field" => "pet_id"],
"name" => ["field" => "pet_name"],
"type" => ["field" => "pet_type"]
//Getting a certain pet from the system
$pet = (new Pet())->getBy(["name" => "polly"]); // basically select * from table where name = 'polly';
echo $pet; //this will output a JSON object

Creating a multiple object class

We could then create a collection of pets which would use the Pet object as an array object

class Pets extends Olga {
var $mapping = Array([ "table" => "tbl_pet",
"object" => "Pet"
//load the pets from the database
$pets = new Pets();

Dynamic objects

Olga is also quite useful for creating dynamic objects

$cat = new Olga('{"id":10,"name":"Garfield","type":"cat"}');
echo $cat->getName();
Andre van Zuydam