Introduction

Installing Tina4 should be very simple and straight forward, if you're finding the process too complicated consider reaching out for some help on our Discord channel here.

First download the docker desktop environment and run the following commands based on your operating system:

Windows

docker run -v %cd%:/app tina4stack/php:latest composer require tina4stack/tina4php
docker run -v %cd%:/app tina4stack/php:latest composer exec tina4 initialize:run
docker run -v %cd%:/app -p7145:7145 tina4stack/php:latest composer start

Linux / MacOS

docker run -v $(pwd):/app tina4stack/php:latest composer require tina4stack/tina4php
docker run -v $(pwd):/app tina4stack/php:latest composer exec tina4 initialize:run
docker run -v $(pwd):/app -p7145:7145 tina4stack/php:latest composer start

The docker environment contains an /app folder which where your application will be served from. The command above is mounting the current working directory into the /app folder and then executing PHP on that folder to run the application. Theoretically you could run any PHP application with the Tina4 docker environment.

What's in the box?

  • Environment Variables - Applications need constant variables or flags which can be used globally in the code
  • Secured routing with JWT tokens - JWT tokens are encrypted tokens with a payload which holds information about whether the tokens are still valid and who they're meant for, essentially securing the transfer of data between third part applications
  • Annotated routes with Open API and built in Swagger UI - External developers who want to work with APIs you've built need an easy place to test your APIs and Open API and Swagger UI are very easy to create in Tina4
  • Database ORM supporting CRUD generation - Store data in well known database engines with support for MySQL, MSSQL, Firebird, Postgres, SQlite3 and ODBC. Quickly build CRUD screens for updating core data
  • Auto-routed Twig templating - Twig is a template engine which works out of the box in Tina4 with useful ways to add your own functionality to twig
  • Shape HTML - HTML DOM as code using object orientation to keep things tidy
  • Built in webserver - Use native PHP for a basic webserver when developing or wrapped with Swoole for a production ready Asynchronous native PHP webserver

Auto-loading

Tina4 is designed to load and build files that are stored in the src folder without you having to include them. How does this work?

  • composer.json - The autoloader section and psr-4 section load files under the /src folder
  • tina4_auto_loader - A method which overwrites the built-in PHP autoloader to load files under the /src folder

Warning

Tina4 is a highly opinionated framework which values simplicity and ease of use over complexity.

Getting Started

Running from Docker

Dockers are a consistent way of running the same software environment on different operating systems.

    Skill sets
    • Docker
    • Bash

Windows

docker run -v %cd%:/app tina4stack/php:latest composer require tina4stack/tina4php
docker run -v %cd%:/app tina4stack/php:latest composer exec tina4 initialize:run
docker run -v %cd%:/app -p7145:7145 tina4stack/php:latest composer start

Linux / MacOS

docker run -v $(pwd):/app tina4stack/php:latest composer require tina4stack/tina4php
docker run -v $(pwd):/app tina4stack/php:latest composer exec tina4 initialize:run
docker run -v $(pwd):/app -p7145:7145 tina4stack/php:latest composer start

  • Handy Insights
    • Download the docker desktop from here
    • On windows you may need to restart your Docker Desktop if your computer sleeps
    • The -v switch mounts the PHP application from your local folder to the /app folder where it runs
    • Change the PHP version of the docker by changing latest to 7.4, 8.1 or 8.2

Basic Website

Tina4 implements TWIG templating out of the box so all your html files should end with the twig extension. Using twig allows you to use includes and therefore simplifies implementing web pages.

    Skill sets
    • HTML
    • Twig

Hello world Example!

Under src/templates create a file called index.twig

<!DOCTYPE html>
<html>
<head>
    <title>Basic Website</title>
</head>
<body>

<h1>Tina4</h1>
<p>This is not another framework.</p>

</body>
</html>
Click on the http://localhost:7145 to see the site, once you have replaced the default site you can get your documentation here http://localhost:7145/documentation

Using a base template with inheritance on twig

Under src/templates create a file called base.twig Notice the use of blocks in Twig, we will use those to inject content

<!DOCTYPE html>
<html>
<head>
    <title>Basic Website</title>
    {% block headers %}
    {% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>

Under src/templates create a file called index.twig The code in this file will inherit the structure of base.twig. Use the blocks to inject your changes.

{%  extends "base.twig" %}

{% block headers %}
    <!-- add a link for bootstrap -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
{% endblock %}

{% block body %}
    <h1>Hello World Base Twig Example</h1>
{% endblock %}

  • Handy Insights
    • Tina4 templates are located under src/templates
    • Routing is created dynamically, for example a file called src/templates/contact-us.twig will be accessible at http://localhost:7145/contact-us
    • Use {% include "filename.twig" %} to include a file, the include path is relative to the src/templates folder
    • The {{ formToken }} Twig variable outputs a form token

PHP with Tina4

Tina4 docker has a well curated PHP environment which has a number of useful modules already built in for you to save time. You can use the Docker environment to run any PHP scripts, not necessarily only the Tina4 stack. For example you can test running the swoole module using the Docker command under windows where it is impossible to run natively.

    Skill sets
    • Docker
    • Bash
    • PHP

Available PHP Modules
The hi-lighted modules are non-standard modules which have been built into the stack

bcmath ctype curl date dom exif fileinfo filter ftp gd hash iconv imagick interbase intl json libxml mbstring mongodb mysqli mysqlnd
openssl openswoole pcntl pcov pcre PDO pdo_pgsql pdo_sqlite pdo_sqlsrv pgsql phar posix readline redis session SimpleXML soap sodium
sqlite3 sqlsrv tidy tokenizer xml xmlreader xmlwriter zip zlib

Some examples:
#Get the PHP version
docker run tina4stack/php -v

#Get a list of PHP modules
docker run tina4stack/php -m

#Run a different PHP version
docker run tina4stack/php:7.4 -v
docker run tina4stack/php:8.1 -v

#Run a script with the PHP docker
docker run tina4stack/php test.php

  • Handy Insights
    • You can switch the PHP versions by adding the version to the docker image eg. docker run tina4stack/php:8.1 or docker run tina4stack/php:7.4
    • On windows you may need to restart your Docker Desktop if your computer sleeps
    • The -v switch can mount any "local" PHP application on your system to the /app folder where it runs
    • The docker image includes wine for running windows executables under linux and MacOS

Debugging with PHP using Xdebug & Debug

Debugging is important in troubleshooting what is going on with your software. There are a number of good methods to figure out what is happening them, in Tina4 we have a Debug class which helps with that. The most efficient way to debug your application is using step by step debugging in an IDE supporting PHP debugging. The most comprehensive debugging extension for PHP is probably xdebug. Remember that Xdebug is trying to connect to a port on your IDE, the most common ports are 9000,9001,9003. If you use PHPStorm or IntelliJ the default is 9003.

    Skill sets
    • Bash
    • Command
    • PHP

Debugging with Docker

The example below shows how you can run Xdebug from the docker passing your ip address in on the commandline so the debugger can connect to your IDE.

docker run -v %cd%:/app -p7145:7145 -eXDEBUG_CONFIG="client_host=192.168.1.100" tina4stack/php composer start

Installing XDebug on PHP

First download or install XDebug by following the instructions here. After you have downloaded / installed Xdebug on your system you need to configure your php.ini file

[Xdebug]
zend_extension=xdebug
xdebug.mode = debug
xdebug.start_with_request=yes

Running XDebug with composer

Running debug mode for PHP can be done easily by prefixing your computer IP before you run composer using client_host=.

XDEBUG_CONFIG="client_host=127.0.0.1" composer start

Using Debugging in Tina4

In your .env file you can turn debugging on or off and you can specify the exact debugging level you wish to use. Valid debug levels are

TINA4_LOG_EMERGENCY
TINA4_LOG_ALERT
TINA4_LOG_CRITICAL
TINA4_LOG_ERROR
TINA4_LOG_WARNING
TINA4_LOG_NOTICE
TINA4_LOG_INFO
TINA4_LOG_DEBUG
TINA4_LOG_ALL

Setting the debug level in .env

Follow the debugging messages in your command line or tail or view the log/debug.log file

Viewing the debugging in the command line

Add to the debug log by using the built in Debug class

\Tina4\Debug::message("This is some debugging", TINA4_NOTICE)

  • Handy Insights
    • Get your ip address in windows by running ipconfig from command line
    • On windows, rename the downloaded dll file to php_xdebug.dll to make it easier to manage
    • Find out where to edit your php.ini file by running php -i | grep php.ini from the command line
    • Make sure you match the Xdebug version you download exactly with the correct version of php you use. php -v
    • Confirm Xdebug is installed by running php -m
    • Use the Tina4-Debug header on your browser to link up calls to the application and the debug log

Environment variables (.env)

Environment variables in Tina4 are defined in the .env file in the base of the project. These variables are instantiated as globals and $_ENV variables.

    Skill sets
    • PHP
    • Globals
    • Standard .env conventions

Typical .env example

The following is an example of an .env file based on the auto-generated one which Tina4 creates when you do an initialize.

[Project Settings]
VERSION=1.0.0
TINA4_DEBUG=true
#The next line shows how an array works
TINA4_DEBUG_LEVEL=[TINA4_LOG_ALL]
[Open API]
SWAGGER_TITLE=Tina4 Project
SWAGGER_DESCRIPTION=Edit your .env file to change this description
SWAGGER_VERSION=1.0.0
[OPEN AI API]
API_KEY=290021ABFEE2233CDEF
[FILES]
FILE_PATH=/home/files
[LISTS]
FRUIT=["apples", "oranges", "pears"]
VEGETABLES=["potatoes", "leeks", "carrots"]

Using the environment variables with code

Use the $_ENV global to reference any value in the .env file.

// You could put this file in the app folder as SaveFile.php
class SaveFile {

    private $filePath;

    final function __construct() {
        $this->filePath = $_ENV["FILE_PATH"]; //in .env declared as FILE_PATH=/home/files
    }

    final function saveFile(string $fileName, string $content)
    {
        file_put_contents($this->filePath.DIRECTORY_SEPARATOR.$fileName, $content);
    }
}

  • Handy Insights
    • The .env files is created with a couple of ready to use variables which Tina4 uses in runtime.
    • Use [brackets] to make sections in the .env file and # on a line to comment it out.
    • TINA4_DEBUG set to true (default) gives you very detailed commandline debugging.
    • Set the ENVIRONMENT variable from command prompt using export or set to change which .env will be loaded.
    • If the ENVIRONMENT variable is dev, the file it will try to load will be .env.dev.
    • Don't commit your production .env files into your respository, rather make a .env.example with dummy values.

Forms with Tina4

One of the most common tasks besides displaying information in a website is capturing data from a form. Let's have a look how we can do this in Tina4.

    Skill sets
    • HTML
    • PHP
    • Twig

Processing a form using Twig

In the code below we have an example of a form in a twig template dumping the variables that have been passed in the inputs, if you save the content below in the src/templates folder as contact.twig you can access it at here.

<!DOCTYPE html>
<html>
<head>
    <title>Form Example</title>
</head>
<body>
<form method="post">
    <input type="text" name="fullName" placeholder="Full Name">
    <input type="text" name="email" placeholder="Email">
    <input type="hidden" name="formToken" value="{{ formToken }}">
    <button>Submit</button>

    <p>
        {{ Tina4.call("Contact", "saveContact", [request.fullName, request.email]) }}
    </p>

    {{ dump(request) }}
</form>
</body>
</html>

In the src/app folder add the content below to a file called Contact.php which will facilitate the processing of your form information

class Contact extends \Tina4\Data
{
    final function saveContact($fullName, $email)
    {
        //do something to save the data
        return "Saved!";
    }
}

Processing a form using a post route

We can use a similar form as before to post to a route, this will involve creating a POST route and we can use the class we already have to process the data. Add the following code to src/templates/contact.twig and access it here.

<!DOCTYPE html>
<html>
<head>
    <title>Form Example</title>
</head>
<body>
<form method="post" action="/submit">
    <input type="text" name="fullName" placeholder="Full Name">
    <input type="text" name="email" placeholder="Email">
    <input type="hidden" name="formToken" value="{{ formToken }}">
    <button>Submit</button>
</form>
</body>
</html>

Add the following code to src/routes/contact.php which will handle the post information from the form. Notice this router does a redirect with some inline params. The formToken is passed with so the request variables can be displayed in the twig template.

/**
* @description Save contact information
*/
\Tina4\Post::add("/submit", function(\Tina4\Response  $response, \Tina4\Request  $request) {
    $message = (new Contact())->saveContact($request->params["fullName"], $request->params["email"]);

    \Tina4\redirect("/contact?message=".$message."&formToken=".\Tina4\getFormToken(""));
});

  • Handy Insights
    • Notice the use of the formToken input, this is required for all forms to be submitted
    • Use {{ "token information" | formToken | raw }} to create the hidden input automatically
    • Tina4.call is a twig function to access your PHP code from twig, it takes on three params, classname, method, array of variables
    • Keeping the form method POST route the same name as the GET route allows for good flow in a browser when the person hits back and forward.

Routing in a nutshell

You may be used to setting up routes from within .htaccess files or just using plain PHP files to render your web pages. Tina4 makes this process easy in the form of a number of static classes to represent the different route types. Unlike other frameworks we are not doing dependency injection on the routing methods, as a standard inline variables are passed first followed by a response and request object. Consider the following examples:

    Skill sets
    • Routing
    • Web Standards
    • Anonymous Methods

The basic GET router

The code below illustrates how to create a basic GET router called /hello/world. Routes are stored in php files in the src/routes folder by default although this is not mandatory but suggested so you can find things easily. You can also put the routing files in sub folders under the routes folder to create more organisation in your project. Put the following code example in src/routes/example.php.

\Tina4\Get::add('/hello/world', function(\Tina4\Response $response) {
    return $response("Hello World");
});
You can now browse to /hello/world in your browser to see the response.

A GET route with inline params

Sometimes part of the route needs to be parameters for example when implementing an API end point: /api/cars/1. The 1 in this example could be substituted by a different number to retrieve a different car. Let's have a look at how we approach this by pasting this code example in your example.php file:

//Notice how we are now returning the HTTP header code and content type
\Tina4\Get::add('/api/cars/{carId}', function($carId, \Tina4\Response $response) {
    return $response("You have chosen car $carId", HTTP_OK, TEXT_HTML);
});
As before you can see the results by clicking on the following links: /api/cars/1 or /api/cars/2

More complex examples, try them out yourself to see how they behave

Here are some examples of different ways of working with routes:

//Get route with multiple inline params, substitute page and topic with any values you want
\Tina4\Get::add('/api/{page}/content/{topic}', function($page, $topic, \Tina4\Response $response) {
    return $response("This route is trying to load page: $page with topic: $topic");
});

//A GET route with inline params passed to the request object
\Tina4\Route::get("/test/{one}", function (\Tina4\Response $response, \Tina4\Request $request ){
    print_r ($request->inlineParams);
});

  • Handy Insights
    • Create sub folders in the src/routes folder to organise your routing better
    • POST routes are always secured by formToken
    • Inline params are enclosed in curly braces and are passed to the route method in the same order they appear in the path
    • You do not have to return a response object, but this does make it more difficult for Tina4 to understand the content you are returning

Twig - How to extend with Tina4

Twig is a popular templating engine which Tina4 uses across all platforms, so even if you're using the Javascript or Python version of Tina4, you will be able to use the same templating engine on all these environments. Let's have a look how we can extend Twig easily using the built in Config functionality in Tina4.

    Skill sets
    • Routing
    • Twig
    • HTML

Using Config to add functionality to Twig

Below is an example of how you can extend Twig with an example of adding a filter, global & function in your index.php file

require_once "vendor/autoload.php";

//Extend the config for Tina4
$config = new \Tina4\Config(function (\Tina4\Config $config) {
    //Filter in Twig: {{ 2 | beep }}
    $config->addTwigFilter("beep", function($times = 1) {
        return str_repeat("beep ", $times);
    });

    //Global in Twig: {{MY_GLOBAL}}
    $config->addTwigGlobal("MY_GLOBAL", "IT WORKS!");


    //Function in Twig: {% set cars = getCars('*') %}
    $config->addTwigFunction("getCars", function($default="*") {
        return (new Store())->getCars($default);
    });
});

//Normal initialize to load libraries
\Tina4\Initialize();
echo new \Tina4\Tina4Php($config);

class Store
{
    /**
     * Gets some cars
     * @param string $default
     * @return string[]
     */
    final public function getCars(string $default="*") : array
    {
        $cars =  ["0" => "BMW", "1" => "Toyota", "2" => "Honda"];

        if ($default === "*") {
            return $cars;
        }

        if (is_numeric($default))  {
            return ["0" => $cars[$default]];
        }

        return [];
    }

}

Using the extended Twig functionality:

{% set cars = getCars('*') %}
{{ dump(cars) }}

{# Result
Array
(
    [0] => BMW
    [1] => Toyota
    [2] => Honda
)
#}

{% set cars = getCars('0') %}
{{ dump(cars) }}
{# Result
Array
(
    [0] => BMW
)
#}

{{ 4 | beep }}

{#  Result
beep beep beep beep
#}

{{ MY_GLOBAL }}

{#  Result
IT WORKS!
#}

  • Handy Insights
    • You don't need to add all your methods inside index.php, you can always pass the $config variable to each class you have and use it to add the needed Twig functionality
    • global $twig is the variable to Twig if you want to use it directly in your code
    • Tina4\renderTemplate() is the method to use to render a twig file
    • All twig templates are stored by default in src/templates and this path can be overwritten if you define a TINA4_TEMPLATE_LOCATIONS array in the index.php, perhaps you need to add more paths?

Securing your Routes

All POST, PUT, PATCH and DELETE routes are secured by default. This means that you need to send a valid JWT CSRF token with your request. The token is normally in the form of a request or form input called formToken. Get routes can be secured by adding the @secure parameter to the comments of the route definition. This will then require a valid JWT CSRF token to be sent with the request.

    Skill sets
    • JWT
    • Annotations
    • Routing
    • HTML Forms

Adding the annotations

Here are some examples of securing the routes using annotations. Annotations are prefixed with an @ sign and are placed above the route definition.

/**
* @secure
*/
\Tina4\Get::add('/hello/world', function(\Tina4\Response $response, \Tina4\Request $request) {
    $array = ["test" => ["one" => "1", "two" => "2"], "test2" => ["one", "two", "three"]];
    return $response($array, HTTP_OK);
});

/**
* @secure
*/
\Tina4\Post::add('/hello/world', function(\Tina4\Response $response, \Tina4\Request $request) {
    $payload = (new \Tina4\Auth())->getPayload($request->data["formToken"]);
    $array = ["test" => ["one" => "1", "two" => "2"], "test2" => ["one", "two", "three"]];
    return $response($array, HTTP_OK);
});
For a browser to hit up either of these routes, it will need to either pass a Basic Authorization header or request parameter called formToken. You can generate a token using the getToken() method on the \Tina4\Auth class.

  • Handy Insights
    • The getToken method takes an array parameter for a payload which will be embedded in the token.
      Example: $tokenWithPayload = (new Tina4Auth())->getToken(["secret" => "I know the answer!"]);
    • You can create your own Authentication mechanism by extending your own class in the app folder from Tina4Auth()
    • Visit https://jwt.io to see how JWT works
    • Change the twig template under src/public/errors/403.twig to customize the error message when a route is forbidden
    • You can get the payload from a formToken by calling (new Tina4Auth())->getPayload($request->params["formToken"])
    • Token life time is set with the TINA4_TOKEN_MINUTES variable in the .env file

Annotating routes for Swagger UI

What is the help about ?

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Main headers

Some text about something

  • Handy Insights
    • Some useful stuff about the help section which may open doors to new things or just clarify obscure things

Connecting to Databases

A web application is not very useful if it can't store data somewhere, to this end we have a number of database drivers available to use. Tina4 uses a standard way to connect to each database called a database abstraction, each database abstraction has a specific driver which it uses. Below is a table of the composer installation commands for each database driver.

Database NameCommandConnection String
Sqlite3composer require tina4stack/tina4php-sqlite3$DBA = new \Tina4\DataSQLite3("dbname", "username", "password", "dateformat");
ODBCcomposer require tina4stack/tina4php-odbc$DBA = new \Tina4\DataODBC("connection string", "username", "password", "dateformat");
MySQLcomposer require tina4stack/tina4php-mysql$DBA = new \Tina4\DataMySQL("host:dbname", "username", "password", "dateformat");
Firebirdcomposer require tina4stack/tina4php-firebird$DBA = new \Tina4\DataFirebird("host:dbpath", "username", "password", "dateformat");
MongoDBcomposer require tina4stack/tina4php-mongodb$DBA = new \Tina4\DataMongoDb("host:dbname", "username", "password", "dateformat");
PostgreSQLcomposer require tina4stack/tina4php-postgresql$DBA = new \Tina4\DataPostgresql("host:dbname", "username", "password", "dateformat");
MSSQLcomposer require tina4stack/tina4php-mssql$DBA = new \Tina4\DataMSSQL("host:dbname", "username", "password", "dateformat");
PDOcomposer require tina4stack/tina4php-pdo$DBA = \Tina4\DataPDO("dblib:host=hostname:port;dbname=dbname","username","password", "dateformat")

    Skill sets
    • SQL
    • Databases
    • Connection Strings

Connecting to and querying a database

In the examples below we have an example of how to connect to a database, execute SQL commands on it and querying it.

//Connect to an SQLite3 database, the default global variable for the database connection is $DBA and declared in the index.php file

require_once "vendor/autoload.php";
\Tina4\Initialize();

//DBA can be declared here or in the config section
global $DBA;

$DBA = new \Tina4\DataSQLite3("test.db");

$config = new \Tina4\Config(function (\Tina4\Config $config) {
    //Another database connection, this connection is only established after static elements have been served
    global $DBA2;

    $DBA2 = new \Tina4\DataSQLite3("test2.db");

});

echo new \Tina4\Tina4Php($config);

//Executing queries on the database connection
$DBA->exec("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)");

//Query with parameters to prevent SQL injection
$DBA->exec("INSERT INTO test (name, age) VALUES (?, ?)", "Tina4", 2);

//Fetching a single row from the database
$record = $DBA->fetchOne("SELECT * FROM test WHERE id = 1");

//Fetching multiple rows from the database, remember the limit will be 10 which is the default
$records = $DBA->fetch("SELECT * FROM test");

//Fetching multiple rows from the database, with a limit of 20 and offset of 10
$records = $DBA->fetch("SELECT * FROM test", 20, 10);

//Fetching multiple rows from the database and returning as an array
$records = $DBA->fetch("SELECT * FROM test")->asArray();

//Fetching multiple rows from the database and returning as an object
$records = $DBA->fetch("SELECT * FROM test")->asObject();

  • Handy Insights
    • Although it is possible to use the database abstraction directly, it is recommended to use the ORM to access the database

Querying a database

Tina4 offers a number of ways to query a database once a connection has been established. It is important to understand the thoughts behind how the data is stored. If your data does not conform there are ways to get around this, but it is best to follow the conventions.

  • We assume that the database tables are in singular form, person vs persons
  • We assume that the database field naming convention is snake case and lowercase
  • We assume that the data should be presented in such a way that it can be used in PHP in the camel case form

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Main headers

Some text about something

  • Handy Insights
    • All queries to a database are automatically paginated, the default pagination size is 10 records per page
    • Results keys are returned in the original field form and camel case to conform to coding requirements in PHP

API Requests

What is the help about ?

    Skill sets
    • curl
    • api keys
    • api documentation

Main headers

Some text about something

  • Handy Insights
    • Some useful stuff about the help section which may open doors to new things or just clarify obscure things

Database Migrations

What is the help about ?

    Skill sets
    • SQL
    • Database Connection
    • Connection Strings

How to create a migration file
  • Consider the following SQlite3 database table stored inside a database called test.db
  • Go to your-url/migrate/create in your browser
  • You should see a text editor with an input above for the name of your file. File name example create table car
  • In the text editor enter in your sql query. Example query below
  • Once you are done with your sql query click on the create migraion button

        create table car (
    id integer primary key,
    car_name varchar(100)
);
    

  • That should have created a migrations folder in your work directory with your file name inside the folder.
  • You can always change the sql query in the file before you run the migration. Not recommended to change sql query after migration.
  • Only run this if the database connection exists - Once you are happy with your sql query. In your browser go to your-url/migrate. This will run all your migration files.
  • After that you should see a table called tina4_migration in your database. This keeps track of all your migrations.

  • Handy Insights
    • your-url/migrate - runs all migrations
    • your-url/migrate/create - create a new migration file under the migrations folder

TITLE of HELP

What is the help about ?

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Main headers

Some text about something

  • Handy Insights
    • Some useful stuff about the help section which may open doors to new things or just clarify obscure things

ORM - Object Relational Mapping

ORM - Object Relational Mapping - is a technique that lets you query and manipulate data from a database using an object-oriented paradigm. When talking about ORM, most people are referring to a library that implements the Object-Relational Mapping technique, hence the phrase "an ORM".

    Skill sets
    • Database
    • Objects

Getting Started with ORM

Consider the following SQlite3 database table stored inside a database called test.db:

create table test (
    id int auto_increment,
    first_name varchar(20),
    last_name varchar(20),
    primary key (id)
);

Now we can use the tina4 command prompt to create our ORM objects
For windows php bin\tina4 and on Mac or linux tina4

====================================================================================================
TINA4 - MENU (C:\projects\myproject)
====================================================================================================
1.) Create index.php
2.) Run Tests
3.) Create database connection
4.) Create ORM objects
5.) Runs webservice
Choose menu option or type "quit" to Exit: 4

Choose menu option 4 and press Return, if you do not have a database connection you will be prompted to create one, see our help about database connections.

====================================================================================================
TINA4 - MENU (C:\projects\myproject)
====================================================================================================
1.) test
0.) Exit
Type 'all' to convert all the tables, no existing objects will be over written
Choose table: all

You should see a screen like this:

====================================================================================================
TINA4 - MENU (C:\projects\myproject)
====================================================================================================
Creating object in C:\projects\myproject\src\orm\Test.php Test
Done, press Enter to continue!
All the ORM objects are placed in the src/orm folder and can be used in your code. You can also create these files manually, here is an example of the src/orm/Test.php file:
class Test extends \Tina4\ORM
{
    public $tableName="test";
    public $primaryKey="id"; //set for primary key
    //public $fieldMapping = ["id" => "id","firstName" => "first_name","lastName" => "last_name"];
    //public $genPrimaryKey=false; //set to true if you want to set the primary key
    //public $ignoreFields = []; //fields to ignore in CRUD
    //public $softDelete=true; //uncomment for soft deletes in crud
    public $id;
    public $firstName;
    public $lastName;
}
The most important properties on the class are the $tableName, $primaryKey and fields. Note that the fields are mapped to the database naming convention from camel case. All your ORM objects should extend the Tina4\ORM class in order for the magic to happen.

Using ORM objects in your code

Here are some practical ways of using ORM objects in code:

The most basic way to save data to the database is as follows, notice how you can echo out the id after saving the object:

$test = new Test();
$test->firstName = "Joe";
$test->lastName = "Smith";
$test->save();

echo $test->id;

Should we want to retrieve the record we can use the load method

//Example 1
$test = new Test();
$test->load("id = ?", [1]); //prevents SQL injection

echo $test->id;

//Example 2
$test = new Test();
$test->id = 1;
$test->load();

echo $test->id;

//Example 3, not recommended if using variables because of injection
$test = new Test();
$test->load("id = 1");

echo $test->id;

There are some other special cases when using the ORM to load up files or binary data into a table and then of course deleting a record. Use the delete method passing the primary key as a parameter or relevant filter.

//Example 1
$test = new Test();
$test->id = 1;
$test->load();
$test->delete();

//Example 2
$test = new Test();
$test->delete("id = 1");

//Example 3
$test = new Test();
$test->delete("id = ?", [1]);

Finally, here we see how we can manipulate large amounts of data into the table or get a list of records from the table.

//Saving content to a blob field in the table
$test->saveBlob("content", "Loads of content");

//Saving a file like an image to a blob field in the table
$test->saveFile("image", "/path/to/file.jpg");

//Getting records from the database
$tests = (new Test())->select();

Conclusion

Using ORM means you do not have to write common SQL statements, all your data manipulation is done with code

  • Handy Insights
    • To make life easier for yourself add an id column to each table and make it the primary key
    • ORM is not a replacement for SQL, it is a tool to make your life easier
    • Camel case fields are mapped to database naming, firstName becomes first_name
    • The ORM object constructor takes an array, object or JSON as an input parameter to populate the object

Functional Testing

The most common way to write tests in PHP is using PHPUnit or Codeception, but they require a lot of configuration and may be hard to use for beginners. Our functional testing framework is done using annotations and simple evaluations. It is a good way to start your test driven development journey. Once you have the basics running you can then move on to PHPUnit or Codeception.

    Skill sets
    • TTD
    • Annotations

Our first test

The first example is a simple test checks if the answer of the sum of two numbers is actually correct. We will look at how we can go about the process of TTD. First we create the function that will return the sum of two numbers, we deliberately return 0 as we want to check if our tests work.

function addNumbers($a,$b) : int
{
    return 0;
}

Next we add a single test using the @test annotation and then assert. We want to assert if the sum of 1 and 2 is 3. The message if the test fails is after the assertion separated by a comma. So our pattern for a test is as follows: assert method(arguments) === expected, message The === operator can be replaced with any other operator.

/**
* @tests
*   assert addNumbers(1,2) === 3, 1 + 2 is not 3
*/
function addNumbers($a,$b) : int
{
    return 0;
}

Open up a terminal in the root of the project and run the test using:

composer test

You should have received something similar on your console:

BEGINNING OF TESTS
================================================================================
Testing Function addnumbers
# 1 addnumbers: Failed (addNumbers(1,2) === 3) 1 + 2 is not 3,
Actual:
addNumbers(1,2)
Expected:
3
Tests: Passed 0 of 1 0%
================================================================================
END OF TESTS

Let's fix the function to return the correct answer:

/**
* @tests
*   assert addNumbers(1,2) === 3, 1 + 2 is not 3
*/
function addNumbers($a,$b) : int
{
    return $a + $b;
}

Run the tests again using:

composer test

You should see something similar to this:

BEGINNING OF TESTS
================================================================================
Testing Function addnumbers 100%
================================================================================
END OF TESTS

Conclusion

You can add as many tests per method as you want, in this way you can think of many test scenarios which should exist before you code out your method. The annotated tests work on Class methods as well, you can use $this in your assert statements to access the class methods and properties.

  • Handy Insights
    • Look into Tina4Auth for more advanced test examples
    • "is_array", "is_object", "is_bool", "is_double", "is_float", "is_integer", "is_null", "is_string", "is_int", "is_numeric", "is_long", "is_callable", "is_countable", "is_iterable", "is_scalar", "is_real", "is_resource" are methods you can use with your assert statement
    • You can call normal PHP functions as well in your assert statement
    • The assert statement must evaluate to true for the test to pass
    • You can add tags for your tests so that only specific tests run, for example @tests app will be targeted by running composer test app

How Do I ?

How do I render a Twig file?

Im having trouble with rendering a Twig file from a custom GET router, How do I solve this?

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Rendering Twig files

Twig files can be rendered by using the built in Tina4 function \Tina4\renderTemplate(). Below are some examples of using this method.

//Example of a GET router rendering a Twig file with some variables
\Tina4\Get::add("/render/file", function(\Tina4\Response $response, \Tina4\Request $request){

    $html = \Tina4\renderTemplate("render.twig", ["variables" => ["one" => "One", "two" => "Two"]]);
    return $response ($html);
});

//Example of rendering a Twig template from a text string
\Tina4\Get::add("/render/text", function(\Tina4\Response $response, \Tina4\Request $request){
    $html = \Tina4\renderTemplate("I am a twig template {{ variables.one }} {{ variables.two }}", ["variables" => ["one" => "One", "two" => "Two"]]);
    return $response ($html);
});

  • Handy Insights
    • You can pass a Twig template string to Tina4\renderTemplate() to be rendered
    • Templates are cached automatically so you may have to run /cache/clear to refresh if /cache/clear doesn't work, check the /cache folder and delete the files their instead.

How do i create a JWT token

How do I generate a JWT token from my form?

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Create JWT tokens

Some text about something

  • Handy Insights
    • Some useful stuff about the help section which may open doors to new things or just clarify obscure things

How do i send a email in Tina4 ?

Tina4 supports sending emails with the default system mailer and with PHPMailer. Tina4 also can store the .eml files for you to read if you are just testing. Lets have a look at the basics.

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Sending emails

Emails can be sent by using the built in Tina4 function

  • Handy Insights
    • Some useful stuff about the help section which may open doors to new things or just clarify obscure things

Advanced

Modules

To expand one's application and make code reusable for other developers, Tina4 has a module system built in to easily allow expansion, by including other Tina4 modules. To allow modules to be universally used between all Tina4 projects, the will need to be built to certain standards.

    Skill sets
    • Modules
    • Composer

Building the module

The module should be built as a normal Tina4 application. Should it be planned to make the module available to the wider public, it is important to build the module to certain standards.

Add a loadModule file to the module

A file, loadModule.php should be created in the root of the module. The example below has commented out the config inclusion for brevity.

<?php
\Tina4\Module::addModule("Module Name", "1.0.0", "myNamespace",
    static function (\Tina4\Config $config) {
        //(new Content())->addConfigMethods($config);
    }
);

Changes to the Module composer.json file

To enable the module to be included in a Tina4 project, a name and description needs to be added to the module composer.json file. It is important to note, that the name given is important, as that name needs to be carried through to the project.

{
    "name": "vendor-name/repo-name",
    "description": "Tina4 Example Module",
    "require": {
        "tina4stack/tina4php": "dev-master"
    },
....

Changes to the Project composer.json file

To enable the module to be included in a Tina4 project, the module needs to be required in the project composer.json file. The name required, is the same name added to the module composer.json file. Depending on how the module is packaged, it might be needed, to add information related to the repository. The example below includes the dev branch of a github repository.

{
    "repositories": [
        {
        "type": "vcs",
        "url": "https://github.com/user-name/repo-name"
        }
    ],
    "require": {
        "tina4stack/tina4php": "dev-master",
        "vendor-name/repo-name": "dev-master"
    },
....

Run composer upgrade to get the module to download into the vendor folder.

  • Handy Insights
    • The Tina4 Module White paper, is an attempt to create a set of standards, allowing modules to be universally available to all Tina4 projects. Currently this working document is available from the Discord Server
    • While namespaces will protect class and variable conflicts, this does not protect against route naming, and care does need to be taken.

Services

Tina4 supports a synchronous process queue system. The service is easily run from the command line using a composer script. Processes can be registered and removed from anywhere in your process code or from a web applicatioin running on the same server.

    Skill sets
    • Services
    • Routing

Starting the service runner

The service runner is started from the command line using a composer script.

composer start-service

Registering and removing processes

Adding and removing processes can be inserted anywhere in running Tina4 code, both in the process class, or an associated website running alongside the service.

// Place this anywhere in your code to ADD a process
$service = new \Tina4\Service();
$service->addProcess(new TestProcess("My Process"));

// Place this anywhere in your code to REMOVE a process
$service = new \Tina4\Service();
$service->removeProcess("My Process");

Building a process

The ProcessInterface determines that at least two methods must be included. After each pause, the service runner will loop through all processes. It first tests if the canRun method returns true, then runs the process.

class TestProcess extends \Tina4\Process implements \Tina4\ProcessInterface
{
    public $name = "My Service";

    public function canRun(): bool
    {
        if ($myCondition == true){
            return true;
        } else {
            return false;
        }
    }

    public function run(): void
    {
        echo "If the canRun() method returned true, this code will run";
    }
}

  • Handy Insights
    • As the process code is instantiated at each run, properties in the process class do not persist
    • There is a $session array variable defined in the service runner which can be used to persist values
    • Both the vendor autoloader, and Tina4 code are called in the service runner, allowing access to the full Tina4 environment
    • Setting the TINA4_SERVICE_TIME to an integer in the env file, will override the default 5 second pause to the number of seconds desired
    • Active processes are stored in the bin/services.data file in your Tina4 project

Threads

If services do not meet the required use case, perhaps one wants a more immediate action, or the server environment does not allow command line interaction, threads could be an option.

    Skill sets
    • Threads
    • Routing

Registering a thread

Adding a trigger to start a thread, can be done anywhere in the code. Just remember that it has to have run before actually calling the trigger. If the use case is just to start a thread, then the trigger registration and trigger call can happen in the same place. If the trigger needs to be used in multiple places, probably best to place it somewhere that the composer autoload will find it. Suggestion is to create a triggers.php file in the "src/app"

<?php

\Tina4\Thread::onTrigger("register-me", static function ($name, $greeting) {

    file_put_contents("trigger.txt", "{$greeting}, {$name}", FILE_APPEND);

});

Firing off a thread

A simple line of code fires off the thread. It uses the trigger name, and an array of parameters. The order of the parameters, matches the order of arguments in the trigger function.

\Tina4\Get::add("/test-trigger", function (\Tina4\Response $response) {

    \Tina4\Thread::trigger("register-me", ["Tina", "Good morning"]);

    return $response("This will add to the file: 'Good morning, Tina'");
});

Debugging threads

The getEvents() method in the Thread class can be used to see what handlers (registered Threads) and events (Thread calls) exists upto the point of calling the method.

$debugThreads = \Tina4\Thread::getEvents()

  • Handy Insights
    • The thread code is not allowed to have comments and can only have simple variables
    • To register a thread you can use onTrigger or addTrigger

Contributing

Making videos on Loom

Setup Environment

Install PHP

How do I install PHP and what is it?

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Install PHP

Some text about something

  • Handy Insights
    • Some useful stuff about the help section which may open doors to new things or just clarify obscure things

Install Composer

How do I install composer and what is it?

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Install Composer

Some text about something

  • Handy Insights
    • Some useful stuff about the help section which may open doors to new things or just clarify obscure things

Install an IDE

What IDE should I use and why should I use it?

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Main headers

Some text about something

  • Handy Insights
    • Some useful stuff about the help section which may open doors to new things or just clarify obscure things

Install openSSL

How do I install openSSL and why should I ?

    Skill sets
    • Requirement 1
    • Requirement 2
    • Requirement 3

Main headers

Some text about something

  • Handy Insights
    • Some useful stuff about the help section which may open doors to new things or just clarify obscure things