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
to7.4
,8.1
or8.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 thesrc/templates
folder - The
{{ formToken }}
Twig variable outputs a form token
- Tina4 templates are located under
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 stackbcmath 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
ordocker 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
- You can switch the PHP versions by adding the version to the docker image eg.
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
Follow the debugging messages in your command line or tail or view the log/debug.log
file
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
- Get your ip address in windows by running
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.
- Notice the use of the
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
- Create sub folders in the
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 codeTina4\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 aTINA4_TEMPLATE_LOCATIONS
array in theindex.php
, perhaps you need to add more paths?
- You don't need to add all your methods inside
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 fromTina4Auth()
- 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
- The
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 Name | Command | Connection String |
---|---|---|
Sqlite3 | composer require tina4stack/tina4php-sqlite3 | $DBA = new \Tina4\DataSQLite3("dbname", "username", "password", "dateformat"); |
ODBC | composer require tina4stack/tina4php-odbc | $DBA = new \Tina4\DataODBC("connection string", "username", "password", "dateformat"); |
MySQL | composer require tina4stack/tina4php-mysql | $DBA = new \Tina4\DataMySQL("host:dbname", "username", "password", "dateformat"); |
Firebird | composer require tina4stack/tina4php-firebird | $DBA = new \Tina4\DataFirebird("host:dbpath", "username", "password", "dateformat"); |
MongoDB | composer require tina4stack/tina4php-mongodb | $DBA = new \Tina4\DataMongoDb("host:dbname", "username", "password", "dateformat"); |
PostgreSQL | composer require tina4stack/tina4php-postgresql | $DBA = new \Tina4\DataPostgresql("host:dbname", "username", "password", "dateformat"); |
MSSQL | composer require tina4stack/tina4php-mssql | $DBA = new \Tina4\DataMSSQL("host:dbname", "username", "password", "dateformat"); |
PDO | composer 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
becomesfirst_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 runningcomposer 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.
- You can pass a Twig template string to
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 LoomSetup 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
Recommendations
What else does Tina4 recommend i get?
-
Skill sets
-
- Requirement 1
- Requirement 2
- Requirement 3
Recommendations
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