Introduction

Hello and welcome to the Official SPLIT PHP Framework Documentation!


The Purpose

"Knowing PHP should be enough!"

This framework is born from the perception that the current tools available in the marketplace for PHP developers are hyper-complex, heavy-weighted and old-styled. This is, I think, what is causing the exodus, by the new generations of web devs, to other languages and technologies, which makes sense, when you look from the perspective of a dev who learnt how to make web apps using the simple Python's Flask or the practical Node's ExpressJS, for examples, and suddenly comes face to face with these gigantic behemoths available as frameworks in PHP, with their huge learning curves and their tons of dependencies and vendors. When these new devs are saying "I don't like PHP" what they don't understand is that it is not PHP itself what scared them off.


SPLIT is not MVC

This framework is not (just one more) MVC (yes, finally!). It is built on top of SOA (Service Oriented Architecture), which makes it more reusable, straightforward, RESTful friendly and an easy-to-go for making micro-services systems. It is designed to be simple and lean, with a low learning curve. Its mission is to be a light tool with simplified interface. Knowledge required to understand and maintain an application written with this framework is basic PHP and OOP and the only dependency to run it is PHP itself.

See more at: https://github.com/splitphp


The Concept

Why "SPLIT"? Firstly because the word "split" is a reference to micro-services and split systems architecture (of course you can make monoliths with it, if that's your thing). Furthermore, it is an acronym for these 5 bound concepts which are the bases that this framework leans on: "Simplicity", "Purity", "Lightness", "Intuitiveness" and "Target Mindness".

Simplicity:

The good and old KISS principle. An engineer shall solve a problem using the simplest way possible. If there is a simple solution that works, this is the right solution! When it depends on tons of configurations and different files just to have an endpoint that prints out "Hello World!" on the screen, something is wrong.

Purity:

No tons of vendors and no new vendor-specific syntaxes, only the plain and good PHP and Object-Oriented Programming. A framework is intended to be a facilitator, a toolbox for a specific technology, so the dependencies of the framework, shall be, ideally, only the technology itself.

Lightness:

Related to the 2 concepts above, a fast and light software tool creates cheaper and better quality systems and avoids lots of headaches and money losses.

Intuitiveness:

A developer should only have difficulties learning how to use a lib or framework if this developer isn't acquainted with the very technology on which the lib or framework is based. Take the colossally successful example of JQuery. A dev who knows javascript, understands JQuery in a matter of hours. This is tightly related to the "Purity" concept. If a PHP senior has to practice for weeks before becoming really comfortable using a specific PHP framework or lib, again: something is definitely not right.

Target Mindness:

The framework exists as a facilitator and must respect the "Simplicity" and "Intuitiveness" concepts, so it allows the engineer to not have to worry about technical issues like: handling requests, basic security, installations, pre-configurations, handling responses, dealing with external connections, dealing with concurrent executions, data consistence, etc., and to be able to focus only on building the solutions that address the business issues for which the system is being created. This framework aims to provide an interface that is more direct and as straightforward as possible.


The Service Oriented Architecture


What is a Service?

The key concept of a service is reusability and it is basically an encapsulated piece of functionality, which is accessible from any part within the application. So all services are accessible to one another from within the system, but not directly from the client, who only have access to an API Layer, which acts as a "gatekeeper" to the application. The result is a pool of reusable services which can be accessed from everywhere inside the application, but with controlled external access.

Service Oriented Architecture


How does it work in practice?

The SPLIT PHP Framework represents its API Layer as WebServices, where the applications's endpoints are defined. From within an endpoint the WebService can call services and/or respond to the client. In summary, to create an API using SPLIT PHP's SOA, the dev will create the services, which are classes that perform the actual operations, then register endpoints on a WebService. Simple as that!

SPLIT PHP I/O Cycle


Licensing

SPLIT PHP Framework is an OPEN SOURCE software under the MIT license


This framework is free and open source and is intended to always be. You can use and modify it as you wish, respecting just a few rules defined by the MIT open source license bellow:


MIT License

Copyright (c) 2025 SPLIT PHP Community

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Getting Started

In this section you will find a walkthrough to install, set up and run a SPLIT PHP application.


  1. Installation
  2. Configuration
  3. Run The Application

Installation

To install the framework is definitely simple. You can use Composer or, if you're a non-composer user, you can simply download and unzip its contents:

With Composer:


Go to the desired location, then:
composer create-project splitphp/starter myapp
*PS: replace "myapp" with your actual app name.


For Non-Composer users:

Here is 4 options that can be used to accomplish this.

All the 4 options bellow presume you already have PHP 7+ installed on your device. If you don't know how to install it, visit the PHP Official Manual.

1. Download from direct link:

Download the source and extract the contents.


2. With Command Line (Linux):

  • Download the source:
    wget https://github.com/splitphp/core/releases/latest/download/splitphp-distribution-latest.zip
  • Extract it:
    unzip splitphp-1.2.11.zip -d path/to/extraction/destination
OBS -d path/to/extraction/destination is to indicate the location where you want the extracted folder to be. If you're already on it, you can omit this part of the command.


3. With Command Line (Windows PowerShell):

  • Download the source:
    Invoke-WebRequest https://github.com/splitphp/core/releases/latest/download/splitphp-distribution-latest.zip -O splitphp.zip
  • Extract it:
    Expand-Archive -LiteralPath splitphp-1.2.11.zip -DestinationPath path\to\extraction\destination
OBS -DestinationPath path\to\extraction\destination is to indicate the location where you want the extracted folder to be.


4. With Command Line (MacOS):

  • Download the source:
    curl -O https://github.com/splitphp/core/releases/latest/download/splitphp-distribution-latest.zip
  • Extract it:
    unzip splitphp-1.2.11.zip -d path/to/extraction/destination

OBS -d path/to/extraction/destination is to indicate the location where you want the extracted folder to be. If you're already on it, you can omit this part of the command.

Configuration

In SPLIT PHP there are several options and settings, which are static information, that are available in the entire system and they're used to do many different things.


Firstly, in the root folder of your project, run:

php console setup

Then it will appear a file named .env. In this file you can set up your application to do stuff like establish connection to a specific database, define what to show as the first page of your system and so on. In summary: it is used to configure the app.


The Available Options

Here you'll find a detailed explanation of what is each option and how to set it up:

Option Value Description Default
DB_CONNECT "on"/
"off"
If set to "on", tries to connect to a database and throws an exception if the connection fails. If you haven't a database associated with the application, keep it "off". "off"
DBHOST A string containing the URL to the database's host. This option indicates the address of the host where the database is located. "localhost"
DBNAME A string containing the name of the database. This option indicates what is the name of the database of the application, inside that host. None
DBPORT An integer containing the port at which database is available for connection. This option indicates what is the port of the database of the application, inside that host. 3306
DBUSER A string containing the user's username of the database. This user is used to perform all operations in the database, except read operations. This option is this user's username credential. None
DBPASS A string containing This user's password of the database. This user is used to perform all operations in the database, except read operations. This option is this user's password credential. None
DB_CHARSET A string containing the charset used on the database. Define here the default charset. utf8
RDBMS* "mysql"/
"mariadb"/
"postgresql"/
"sqllite"/
"sqlserver"/
"oracle"
Indicates the type of the database. "mysql"
DB_TRANSACTIONAL "on"/
"off"
All write operations in the database are executed, by default, within transactions. This allows the system to automatically undo (rollback) these operations if something wrong happens and the application throws an exception during runtime. This assures the consistence of the data in the base. If, for some specific reason, you don't want these behavior, you can turn it off setting this option to "off". Otherwise, keep it "on". "on"
DB_WORK_AROUND_FACTOR An integer value When an attempt to connect or to execute an operation in the database fails, the system tries to redo it again. This option indicates how many times the system shall retry it before give up and throws an exception. 5
CACHE_DB_METADATA "on"/
"off"
When performing operations in database, the system does some mapping on the entities and stores it as metadata. This options indicates whether or not these metadata should be cached in-file. "on"
APPLICATION_NAME A string containing the name of the application. The name of the application, that will be available in the entire system to whatever purpose. None
DEFAULT_ROUTE A string with the default route. (Example: "/home/hello") This options indicates what route shall be executed when an end-user accesses the root URL. (Example: http://www.my-site.com/) /site/home
DEFAULT_TIMEZONE A timezone formatted string. This option sets the timezone of the entire system to one of the PHP's supported timezone strings. (PHP Supported Timezones) None
HANDLE_ERROR_TYPES PHP constants. This tells to PHP what kinds of errors and warnings it shall capture and handle. Do not change the defaults unless you know exactly what you're doing. E_ALL & ~E_NOTICE & ~E_USER_NOTICE
APPLICATION_LOG "on"/
"off"
This indicates to the system whether or not it should save logs of the errors and exceptions at the application level. "on"
PRIVATE_KEY A string containing a hash. This hash is generated automatically by php console setup and represents a key which can be used to identify the application or to perform encryption/decryption operations. None
PUBLIC_KEY A string containing a hash. This hash is generated automatically by php console setup and represents a key which can be used to identify the application or to perform encryption/decryption operations. None
ALLOW_CORS "on"/"off" Wheither allow CORS requests or not. on
MAX_LOG_ENTRIES An integer value An integer that defines how many log entries the system is allowed to store at the same time in a single log file. 5
MAINAPP_PATH A string containing, the path to the application source directory. It is in this directory that all your services, WebServices and etc. will lie. /application
MODULES_PATH A string containing, the path to the modules source directory. It is in this directory that all your preset modules and plug-ins will lie. /modules
APP_ENV A string containing, the name of the current environment. This can be used to control some environment-bound behaviors. development

NOTE The RDBMS option currently supports only MySQL databases. So if you set any other value than "mysql" on this option, it won't work properly. The support to the other relational databases described, will be implemented in future releases.


Running The Application

Finally, let's put it to run!


Run SPLIT PHP

Inside the root directory of your project ("/"), open up a command line prompt or terminal and enter:
php console server:start

Done! Now go to your web browser and navigate to "http://localhost:8000" and you shall see the SPLIT PHP Framework's welcome page.

OBS The serve used here is the PHP built-in web server, which is only fit for development purposes. To deploy your SPLIT PHP application in the real world, in a production environment, visit the Deploy Section in this documentation.
NOTE You also have the options to stop and to monitor its status with:
  • php console server:stop
  • php console server:status
. For more information, run: php console server:help

Basic Resources

In this section you will find detailed explanations for each of the main resources you'll need in order to create and maintain an application with this framework.


  1. Service
  2. Web Service
  3. DAO
  4. Migrations

Service

A Service is an independent piece of functionality, which is available all over the system. The keyword here is "reusability".


What is a Service?

A Service is basically a class, that you can name in any way you want, which extends the main class SplitPHP\Service, inheriting all its mechanics, and located at "/application/services" folder.

It's in the services that the magic happens. All database operations, business rules, requirements, etc. goes in the services. We can say that the ensemble of the services, is the system itself. So, basically what you got is a system that is formed by an ensemble of classes. Simple enough?!

Let me show you an example of a Service:

Imagine that you're building a Service which performs basic arithmetic operations.

  • Firstly, you create the Service itself:
    <?php
    // this class is located in a file called "arithmetic.php" inside the folder "/application/services".
    
    namespace YourAppName\Services;
    
    use SplitPHP\Service; // this is the main class that this service will inherit from.
    
    class Arithmetic extends Service{
      
    }
  • Now, let's add the sum functionality of our Arithmetic's Service:
    <?php
    // This class is located in a file called "arithmetic.php" inside the folder "/application/services".
    
    namespace YourAppName\Services;
    
    use SplitPHP\Service; // this is the main class that this service will inherit from.
    
    class User extends Service{
      public function sum($value1, $value2){
        return $value1 + $value2;
      }
      
    }
  • If you get the idea, let's add subtraction, multiplication and division operations in our service:
    <?php
    // This class is located in a file called "arithmetic.php" inside the folder "/application/services".
    
    namespace YourAppName\Services;
    
    use SplitPHP\Service; // this is the main class that this service will inherit from.
    
    class User extends Service{
      public function sum($value1, $value2){
        return $value1 + $value2;
      }
      
      public function subtraction($value1, $value2){
        return $value1 - $value2;
      }
    
      public function multiplication($value1, $value2){
        return $value1 * $value2;
      }
    
      public function division($value1, $value2){
        return $value1 / $value2;
      }
    }
Ok, done! But how can you access these functionalities from outside this Service?

Inside any service you have the $this->getService() method available. This method is used to invoke and instantiate any other service. So, from a service, you can invoke another service by using $this->getService() builtin method.

The $this->getService() method requires the service's path as a parameter - this path starting from the "/application/services" folder - to identify which service it will return and the returned value is an object of the service's class itself. This allows you to call service's operations using builder design pattern, directly, without having to instantiate the service's object explicitly:

<?php
...
$this->getService('path/to/the/service')->serviceMethod();
...

Here's an example:

Imagine, now, that in your system you have a "Salesorder" service which has an applyDiscount operation, which uses the operations defined in that Arithmetic's service, that we created before, to calculate the discounts of a sales order. In order to do it, it will make use of $this->getService() method:

<?php
namespace YourAppName\service;

use SplitPHP\Service;

class Salesorder extends Service{
  public function applyDiscount($orderValue, $discountValue){
    $finalValue = $this->getService('arithmetic')->subtraction($orderValue, $discountValue);

    return $finalValue;
  }
}
    

OBS You can find more detailed information about the method $this->getService() in the session Reference Guide of this documentation.
NOTE It's in the service that the interface to operate the database is available, by using the SPLIT PHP's DAO (Data Access Object). Learn more about it visiting the session Components -> DAO of this documentation.
The special init() method:

This method can be used to put some routines that must be executed right after the construction of the class happens. You can use it, for example, to initialize some variables or include some external classes.

<?php
namespace YourAppName\service;

use SplitPHP\Service;

class Salesorder extends Service{
  private $foo;
  private $bar;
  private $externalClassObject;

  public function init(){
    $this->foo = "foo";
    $this->bar = "bar";

    include "path/to/an/external/class.php";
    $this->externalClassObject = new ExternalClass();
  }

  ...
}
    

This method will be executed exactly at the finishing of classe's construction.

NOTE This special method init() is particularly important on WebServices.
What's more in it?

Ok, so now we know that a service is just a class stored in "/application/services" folder, which extends the SplitPHP\Service framework's class, we can make any initializing using init() special method and, also, that we can, from any service, access any method of any other service, using the builtin method $this->getService(). But what more can a service do?

The answer is: manage Templates!

A template is a PHP file, which contains HTML (or any other markup language that you want) and it must be located in "/application/templates". The service does so by using another builtin method called $this->renderTemplate(). This method receives a template's path as the first parameter - this path starting from "/application/templates" folder - and returns a string of the contents of this template, which you can use to render as page in the browser or use as a message's body in an email, for instance:

  • The template located at "/application/templates/hello.php":
    <h1>Hello World from a template!</h1>
  • The service that manages it:
    <?php 
    namespace YourAppName\Services;
    
    use SplitPHP\Service;
    
    class Servicename extends Service{
      public function printTemplateHello(){
        $tplContent = $this->renderTemplate('hello'); // the variable $tplContent holds a string with the content "<h1>Hello World from a template!</h1>"
    
        echo $tplContent; // prints out the HTML recovered from the template.
      }
    }

You can also pass, as the second parameter, a var list, with dynamic content, to this template, so it can render it's value inside the HTML content:

  • The template located at "/application/templates/hello.php":
    <h1>Hello <?php echo $name; ?>!</h1>
  • In the service:
    <?php 
    namespace YourAppName\Services;
    
    use SplitPHP\Service;
    
    class Servicename extends Service{
      public function printTemplateHello(){
        $tplContent = $this->renderTemplate('hello', ['name' => "Gandalf"]); // the variable $tplContent holds a string with the content "<h1>Hello Gandalf!</h1>"
    
        echo $tplContent; // prints out the HTML recovered from the template.
      }
    }
NOTE The name of the variable in the template is the same key of the var list array, passed as the second parameter to $this->renderTemplate() in the service.
OBS You can learn more about the templates in the session Components -> Template of this documentation.
OBS You can find more detailed information about the method $this->renderTemplate() in the session Reference Guide of this documentation.

Wrapping up!
  • A service is a class, located at "/application/services" folder, that manages a single part of the system (Ex.: User service, which manages application's users).
  • If you need to automatically run anything at the initialization of the Service you are able to it by making use of the special method init().
  • It is available to any other service in the application by the use of $this->getService() method, but it's not directly available to the client.
  • All logics, rules and operations of the system will be performed by services.
  • The $this->getService() method exposes a builder design pattern interface, so you can call the service's method right after $this->getService(), without having to instantiate the service's class object explicitly.
  • It's in the service that you will operate your application's database, using the interface of DAO.
  • You can manage your application's templates by the use of $this->renderTemplate() method.

That's it! In the next section you will learn about the Web Service and how you connect your services to the requests of the client. See you there!


Web Service

The WebService is your API layer, the gatekeeper of your application.


How to create a WebService

Web Services are classes, which extend the SplitPHP\WebService main class, inheriting, though, all it's mechanics. Your application's web services, are gonna be located under "/application/routes".

So, answering the question "How do I create a WebService in my app?": it's just a matter of creating a class, which extends SplitPHP\WebService class, and save it in a file under the directory "/application/routes".

WebService, Son of Service

The WebService extends Service, though inheriting all of its methods, including the special method init(), which will be particularly important for us here.

All the routes registration occurs at runtime, using an imperative paradigm, instead of using a static, config-like mechanic.

This has several advantages but one trade-off. The main advantage is the fact that everything about an endpoint is dynamic, so you could, for instance, write some condition to weither or not add some endpoints or even change dynamically their very routes. This is robust, but comes with a trade-off: you can't just register them in the class as functions, variables or constants as they will be added on-the-flight. So, they must be called from within a method.

Here's where the special method init() comes in hand. You can add all your endpoints inside the init() and they will be automatically added as soon as the WebService class finishes its construction. See examples in the next section.

Routes and the URL

An application exposes its resources by the use of endpoints, which are basically routes, on which a function is executed, when accessed.

To register endpoints under your WebService, you make use of the addEndpoint() built-in method:

<?php 
namespace YourAppName\Routes;

use SplitPHP\WebService;

class MyApi extends WebService{
  public function init(){
    $this->addEndpoint('GET', '/hello', function(){
      return $this->response
        ->withStatus(200)
        ->withText("Hello from endpoint '/hello'");
    });

    $this->addEndpoint('GET', '/foo', function(){
      return $this->response
        ->withStatus(200)
        ->withText("Hello from endpoint '/foo'");
    });

    $this->addEndpoint('GET', '/bar', function(){
      return $this->response
        ->withStatus(200)
        ->withText("Hello from endpoint '/bar'");
    });
  }
}

NOTE Be aware that the endpoints are being added from within init().
The anatomy of an endpoint

The UX of this is similar to Node's "Express.JS". An endpoint have 3 main parts basically:

  • The HTTP verb: which consists in the Verb(s) accepted by the endpoint. The allowed values are:
    • GET
    • POST
    • PUT
    • DELETE
  • The route: it is the URL listened by the endpoint. The address that will be accessed by the client.
  • The callback function: it is the function that will be called when the endpoint is triggered.
So, when a client access the URL registered in route, using the HTTP Verb registered in Verb, it will run the function registered in the callback function. Pretty straightforward, isn't it?

But if I need to retrieve data from the request?

You can just pass, to the callbak function, a parameter of type SplitPHP\Request:

<?php 
namespace YourAppName\Routes;

use SplitPHP\WebService;
use SplitPHP\Request; // <== SplitPHP\Request class type

class MyApi extends WebService{
  public function init(){
    $this->addEndpoint('GET', '/hello/?name?', function(Request $req){
      $name = $req->getRoute()->params['name'];
      return $this->response->withStatus(200)->withText("Hello, {$name}! Welcome to SplitPHP Framework.");
    });

    $this->addEndpoint(['POST','PUT'], '/save-something', function(Request $req){
      $formData = $req->getBody();
      $newSomething = $this->getService('foo/bar')->save($formData);

      return $this->response
      ->withStatus(201)->withData($newSomething);
    });

    $this->addEndpoint(['POST','PUT'], '/other/?someParam?', function($params){
      $someParam = $params['someParam'];
      unset($params['someParam']);

      $reqBody = $params;

      $this->getService('foo/bar')->doSomething($someParam, $reqBody);

      return $this->response
        ->withStatus(204);
    });
  }
}

NOTE The route params are placed in the route between 2 question marks (?paramName?).
NOTE The 3rd endpoint callback doesn't receive a Request object, instead, it will be given a merge of the body with the route params.
The Response object

All endpoints must always return a Response object and the WebService already initializes and stores it in $this->response property.

This object is used to control the API's response informations as: status code, content type, headers and body. This response is formed using the builder pattern, with subsequent callings of Response object's methods:

<?php 
...
$this->response
  ->setHeader("Some-Header: Value")
  ->withStatus(200)
  ->withHTML($htmlContentString);

The withStatus() method set the http status code that will be returned to the client in the response. This can be any integer.

The setHeader() method receives a qualified header-string and set it to the response.

Furthermore, we have the methods which defines the content format of the data that will be sent in the body of the response. Currently we have the following options:

  • withText(string $text, bool $escape = true): sets response's content type to "text/plain" and attach the content of $text in the response body with no formatting.
  • withData($data, bool $escape = true): sets response's content type to "application/json", encodes the content of $data into JSON and then attach it to the response body.
  • withHTML(string $content): sets response's content type to "text/html" and attach the content of $content to the response body with no formatting.
  • withXMLData($data): sets response's content type to "application/xml", encodes the content of $content to XML and then attach it to the response body.
  • withCSS($content): sets response's content type to "text/css", then attachs the content of $content to the response body with no formatting.

To give a full example, let's imagine that we're building a RESTful API with a CRUD for the entity "Animal":

<?php 
namespace App\Routes;

use SplitPHP\WebService;
use SplitPHP\Request; // <== SplitPHP\Request class type

class Animals extends WebService{
  public function init(){
    // Get one:
    $this->addEndpoint('GET', '/v1/animal/?id?', function(Request $req){
      $id = $req->getRoute()->params['id'];
      $data = $this->getService('animals')->findOne($id);

      return $this->response
        ->withStatus(200)
        ->withData($data); // <= JSON response
    });

    // List:
    $this->addEndpoint('GET', '/v1/animal', function(Request $req){
      $filters = $req->getBody()
      $data = $this->getService('animals')->list($filters);

      return $this->response
        ->withStatus(200)
        ->withData($data); // <= JSON response
    });

    // Create
    $this->addEndpoint('POST', '/v1/animal', function(Request $req){
      $formData = $req->getBody();
      $newAnimal = $this->getService('animals')->create($formData);

      return $this->response
        ->withStatus(201)
        ->withData($newSomething);
    });

    // Update
    $this->addEndpoint('PUT', '/v1/animal/?id?', function(Request $req){
      $id = $req->getRoute()->params['id'];
      $formData = $req->getBody();

      $affectedRows = $this->getService('animals')->upd($id, $formData);

      // Responds 404 (not found) if no record is updated:
      if($affectedRows < 1) return $this->response->withStatus(404);

      return $this->response
        ->withStatus(204);
    });

    // Delete:
    $this->addEndpoint('DELETE', '/v1/animal/?id?', function(Request $req){
      $id = $req->getRoute()->params['id'];

      $affectedRows = $this->getService('animals')->remove($id);

      // Responds 404 (not found) if no record is deleted:
      if($affectedRows < 1) return $this->response->withStatus(404);

      return $this->response
        ->withStatus(204);
    });
  }
}


Data Access Object (DAO)

This is the main way to access and operate data in the database. You don't need to setup persistence's data classes, models, etc. The connection to the database is already established and it uses the credentials specified in the config.ini file, all the setup is already made and it's already good to go. You can just invoke the DAO's methods directly in your services and eureka!


How does a data retrieving looks like using this Data Access Object?
<?php

$results = $this->getDao('YOUR_TABLE_NAME')
  ->filter('id')->equalsTo($someValue)
  ->find("SELECT some_column FROM `YOUR_TABLE_NAME` WHERE id = ?id?");
  

This code above retrieves data from a table named "YOUR_TABLE_NAME", using the query passed as argument to method find() and stores the retrieved data as an array into variable $results. Furthermore, it replaces the "?id?" in the query string with the value in $someValue variable. Note that the text between the "??" is the same string passed as argument to filter() method.


Let me show you an example:

Imagine that you have a table of PEOPLE and you want to build a method, in your People's Service, that retrieves a list of people's name and age, filtered by genre:

<?php

// Inside People's Service
public function listByGenre($genre){
  $results = $this->getDao('PEOPLE')
    ->filter('genreParam')->equalsTo($genre) // Passing the value received by parameter.
    ->find("SELECT name, age FROM `PEOPLE` WHERE genre = ?genreParam?");

  return $results; // This function is returning an array containing a list of people, filtered by the genre received in parameter $genre.
}
  
NOTE The filter()->equalsTo() serves the purpose of avoiding SQL Injection attacks. Of course you could just concat $someValue into the query string directly, but this would open your system to these kind of hacking. To ensure the security, always use filter()->equalsTo() to pass dynamic values to your SQL queries.

Let's take a look into the anatomy of a DAO invoking:
  1. Setting-up and retrieving DAO:
    <?php
    $dao = $this->getDao('YOUR_TABLE_NAME') // It says to DAO in which table it is going to operate and returns the DAO itself.
  2. Applying filters to the operation:
    <?php
    $dao->filter('fieldName') // It says to DAO in which table's field it is going to be applied a value and returns the DAO again.
    ->equalsTo($someValue)// Passes along the value to be applied in the filter that have just been created.
  3. Executing the operation and returning results:
    <?php
    $dao->find($stringContainingSQL) // You can define the SQL that will be executed in here. It returns the results of the executed SQL.

Further options:

> The available operations:
Of course, besides the reading operation, which uses the find() method, you can also perform Insert, Update and Delete:

  1. Insert:
    <?php
    // Define the data that will be inserted in the new record.
    $data = [
      'field1' => $value1, 
      'field2' => $value2, 
      'field3' => $value3, 
    ];
    
    /*
    * Insert a new record in table YOUR_TABLE_NAME, using the data passed in $data, 
    * then returns an object containing the new record data.
    */
    $newRecord = $this->getDao('YOUR_TABLE_NAME')->insert($data);
  2. Update: pretty much the same of insert, but here you can filter the records which will be affected by the update:
    <?php
    // Defining the "field => data" to be updated:
    $data = [
      'field1' => $value1, 
      'field2' => $value2, 
      'field3' => $value3, 
    ];
    
    // Updating records, filtered by ID. This returns the number of affected rows:
    $affectedRows = $this->getDao('YOUR_TABLE_NAME')
      ->filter('id')->equalsTo($id) // This $id var, can be a parameter of the service's function, for example.
      ->update($data);
  3. Delete: It's just a matter of defining the filters of the deletion and invoking the function without arguments:
    <?php
    // Delete records, filtered by ID. This also returns the number of affected rows:
    $affectedRows = $this->getDao('YOUR_TABLE_NAME')
      ->filter('id')->equalsTo($id) // This $id var, can be a parameter of the service's function, for example.
      ->delete();
NOTE The data which will be written in the database in insert and update operations are represented as objects or associative arrays, where the keys are the name of the fields in the table and the values of this array or object are the proper values that will be stored in these fields.

> The available filter comparison options:
Sometimes we do not want our filter to be equals to the value passed. We may want to perform a search filtered by values that are lesser than or bigger than the value we passed along to the query. For example: imagine that you want delete all old records from a table, that were created before a certain date. Instead of using ->equalsto() method, you'll use lesserThan():

<?php
// This deletes all records that were created before january 1, 2022, then returns the number of the deleted records:
$deletedRecords = $this->getDao("TABLE_NAME")
  ->filter('date_created')->lesserThan('2022-01-01')
  ->delete();

Here's a list of all the available comparison methods:

Method Ref. Operator Use case
equalsTo() =
<?php
$this->getDao("TABLE_NAME")
  ->filter('field_name')->equalsTo($value)...
differentFrom() !=
<?php
$this->getDao("TABLE_NAME")
  ->filter('field_name')->differentFrom($value)...
biggerThan() >
<?php
$this->getDao("TABLE_NAME")
  ->filter('field_name')->biggerThan($value)...
lesserThan() <
<?php
$this->getDao("TABLE_NAME")
  ->filter('field_name')->lesserThan($value)...
biggerOrEqualsTo() >=
<?php
$this->getDao("TABLE_NAME")
  ->filter('field_name')->biggerOrEqualsTo($value)...
lesserOrEqualsTo() <=
<?php
$this->getDao("TABLE_NAME")
  ->filter('field_name')->lesserOrEqualsTo($value)...
likeOf() LIKE
<?php
$this->getDao("TABLE_NAME")
  ->filter('field_name')->likeOf($value)...
in() IN
<?php
$this->getDao("TABLE_NAME")
  ->filter('field_name')->in($value) // <= array...
notIn() NOT IN
<?php
$this->getDao("TABLE_NAME")
  ->filter('field_name')->notIn($value) // <= array...

> The available filter logical options:
If we want to stack conditions on our filters, we have to make use of logical operators ("AND" and "OR") in our queries, right?! To do it with this DAO, we betake te methods and() and or() (what a surprise! xD).

For example, imagine that you have a table which contains a list of students of all grades and you want to label those of the 7th grade, who obtain the better scores in the tests(9 points or more), with "better of the 7th grade". So you need to perform an update, filtering by the grade and by the scores:

<?php
$this->getDao("students")
  ->filter('grade')->equalsTo("7th")
  ->and('score')->biggerOrEqualsTo(9)
  ->update([
    'label' => "better of the 7th grade"
  ]);

Now an example using or(): Imagine that you want to delete all the products of 2 different categories. This is how you do it:

<?php
$this->getDao("products")
  ->filter('category')->equalsTo("category_1")
  ->or('category')->equalsTo("category_2")
  ->delete();
NOTE When making use of reading operation methods as find(), first() or fetch(), if you're passing a query to it, you'll have to specify the comparison and logical operators explicitly in the query, so the use of these different methods will have no different effect.
OBS You can find more detailed information about the DAO class in the session Reference Guide of this documentation.

Database Migrations

This resource serves the purpose of managing the structure of the database, providing a way to version it.


How to setup a Database Migration

As everything else in the system, Migrations are classes. The only special thing about them is that its names/filenames must follow a specific pattern, so the system can control the order of each Migration. To facilitate this, Split PHP has a built-in command to generate the migrations, already with its name and with a boilerplate, ready for you to set it up:

php console generate:migration

This will ask you some questions, then, create a new file at the location specified at "MAINAPP_PATH/dbmigrations/", with the following structure:

<?php
namespace YourApp\Migrations;

use SplitPHP\DbManager\Migration;
use SplitPHP\Database\DbVocab;

class YourMigrationName extends Migration{
  public function apply(){
    /**
     * Here goes your migration's statements. For example, the following code
     * creates or alters a table called 'Person', and adds or changes this 
     * table's columns: 'id_person', 'id_company' 'name' and 'dt_birth':
     * 
     * $this->Table('Person')
     *  ->id('id_person') // int primary key auto increment
     *  ->int('id_company') // int
     *  ->Foreign('id_company')->references('id_company')->atTable('Company')->onUpdate(DbVocab::FKACTION_CASCADE)
     *  ->string('name', 100) // varchar(100)
     *  ->datetime('dt_birth') datetime
     *    ->setDefaultValue(DbVocab::SQL_CURTIMESTAMP()); // default current timestamp
     */
  }
}
OBS More information about the MAINAPP_PATH settings, visit the Configuration Section in this documentation.

The syntax is declarative and very straightforward: you start an operation by invoking $this->Table('TABLE_NAME') and, using the building pattern, you start declaring the columns, indexes and foreign keys. Here are the available options:

➤ Column-defining Options:
  • ->id(string $columnName): defines an integer-type column, named $columnName, with auto increment, then sets it as the table's Primary Key
  • ->int(string $columnName): defines an integer-type column, named $columnName
  • ->string(string $columnName, int $length): defines a varchar-type column, named $columnName, with length: $length.
  • ->text(string $columnName): defines a text-type column, named $columnName.
  • ->bigInt(string $columnName): defines a bigint-type column, named $columnName.
  • ->decimal(string $columnNam): defines a decimal-type column, named $columnName.
  • ->float(string $columnName): defines a float-type column, named $columnName.
  • ->date(string $columnName): defines a date-type column, named $columnName.
  • ->datetime(string $columnName): defines a datetime-type column, named $columnName.
  • ->time(string $columnName): defines a time-type column, named $columnName.
  • ->timestamp(string $columnName): defines a timestamp-type column, named $columnName.
  • ->boolean(string $columnName): defines a boolean-type column, named $columnName.
  • ->blob(string $columnName): defines a blob-type column, named $columnName, to store binary file content.
  • ->json(string $columnName): defines a json-type column, named $columnName, to store JSON-formatted data.
  • ->uuid(string $columnName): defines a uuid-type column, named $columnName, unique string IDs.
➤ The Anatomy of an Index:

The indexes are defined by a couple idiomatic chained functions:

  • ->Index(string $name, string $type): start the indexe's definition by setting its name and type.
  • ->onColumn(string $columnName): sets a single column on which the index will be attached.
  • ->setColumns(array $columns): sets multiple columns on which the index will be attached.

The whole definition of an index would look like this:

<?php
      ...
      $this->Table('TABLE_NAME')
      ...
      ->Index('INDEX_NAME', 'INDEX_TYPE')->onColumn('COLUMN_NAME');
    

➤ The Anatomy of a Foreign key:

The FKs are defined by chaining some functions in an idiomatic way:

  • ->Foreign(string|array $columns): start the Fk's definition by attaching it to one or multiple columns.
  • ->references(string $columnName): sets the column of the referenced table.
  • ->atTable(string $tableName): sets name of the referenced table.
  • ->onUpdate(string $action): defines the Fk's behavior on update events.(Ex.: CASCADE)
  • ->onDelete(string $action): defines the Fk's behavior on delete events.(Ex.: RESTRICT)

The whole definition of a foreign key would look like this:

<?php
      ...
      $this->Table('TABLE_NAME')
      ...
      ->Foreign('COLUMN_NAME')
      ->references('REF_COLUMN_NAME')
      ->atTable('REF_TABLE_NAME')
      ->onUpdate(DbVocab::FKACTION_CASCADE)
      ->onDelete(DbVocab::FKACTION_RESTRICT);
    

➤ Extra options:

And here are the other controls and options necessary to manage your database structure:

  • ->drop(): sets the previous definition (Table, Column, Index or FK), to be dropped.
  • ->nullable(): applicable only to columns, tells the system that the column to be created/updated must accept null value.
  • ->unsigned(): applicable only to int/bigint-type columns, sets the column as UNSIGNED.
  • ->setDefaultValue($val): applicable only to columns, sets the default value for the column.
➤ How does a whole migration looks like:

<?php
namespace YourApp\Migrations;

use SplitPHP\DbManager\Migration;
use SplitPHP\Database\DbVocab;

class YourMigrationName extends Migration{
  public function apply(){
    
    /**
     * Creates or updates a table named 'Person' with the following setup:
     */
    $this->Table('Person')
     ->id('id_person') // int primary key auto increment
     ->string('u_key' 60) // unique key varchar(60)
     ->string('name', 100) // varchar(100)
     ->int('id_company') // int
     ->date('dt_birth')->nullable()->setDefaultValue(null) // nullable date 
     ->datetime('created_at')->setDefaultValue(DbVocab::SQL_CURTIMESTAMP()) // datetime which defaults to current date and time
     ->Index('U_KEY', DbVocab:IDX_UNIQUE)->onColumn('u_key')
     ->Foreign('id_company')->references('id_company')->atTable('Company')->onUpdate(DbVocab::FKACTION_CASCADE)->onDelete(DbVocab::FKACTION_RESTRICT)
  }
}

➤ Dropping a column which was set in a previous Migration:

Imagine that, some time after running the Migration above, you need to replace the column 'dt_birth' by 'age':

<?php
namespace YourApp\Migrations;

use SplitPHP\DbManager\Migration;
use SplitPHP\Database\DbVocab;

class YourNextMigrationName extends Migration{
  public function apply(){
    
    /**
     * Updates the table 'Person' with the following setup:
     */
    $this->Table('Person')
     ->date('dt_birth')->drop() // drops the previously-defined column 'dt_birth' 
     ->int('age')->nullable->setDefaultValue(null); // sets a new nullable, integer column 'age'
  }
}


Running and managing your Migrations
➤ Running my Migrations:

The command bellow applies all the pending migrations:

php console migrations:apply
➤ Rolling-back my Migrations:

The command bellow rolls back all the applied migrations:

php console migrations:rollback
NOTE You don't need to explicitly define the rollback behavior(the "down" function) of the Migrations, the framework is smart enough to guess it by itself.
➤ Checking my Database Status:

The command bellow shows a list of all the migrations and their statuses("pending" or "applied") and other information:

php console migrations:status
NOTE For more information about the specific migrations options, run the command:
php console migrations:help

Deploy In Production

Put it out to the world!

As it was pointed out in the Running Application section of this documentation, one should, instead of using the PHP's built-in web server to deploy applications on production environments, use a web server application as Nginx or Apache 2. Here it is explained how to deploy your SPLIT PHP app using these two, which are the main solutions fot this in the current marketplace.


Using NGINX Web Server

This tutorial assumes that you already have some knowledge on NGINX and Server Block files.

If you're not acquainted with NGINX or want to know more about it, visit the NGINX Official Docs.

To configure Nginx to serve a SPLIT PHP application you need to:

  1. create a new Server Block file inside the sites-available Nginx's directory
  2. insert the code shown bellow, making the necessary adaptations, into this file
  3. save it and then enable it on Nginx

<?php
server {
  listen 80;
  listen [::]:80 ipv6only=on;

  server_name example.com.br;

  root /path/to/your/app/root/public;
  index index.php;

  location / {
    try_files $uri $uri/ /index.php?$query_string;
    fastcgi_param QUERY_STRING $query_string;
  }

  location /resources {
    try_files $uri $uri/ =404;
  }

  location ~ \.php$ {
    try_files $uri /index.php =404;
    # This line below is a default path to PHP in linux systems. If your PHP is installed in another location, change it to your actual fpm path.
    fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
  }
  location ~ \.git {
    deny all;
  }
}

Here we have some points of attention:

  1. The server_name option: you must set this option with your application's domain name.
  2. The root option: you must set this option with the absolute path to your application's public directory.
  3. The fastcgi_pass option, inside location ~ \.php$ section: this option indicates the PHP fpm path inside your server. If you have an UNIX based OS installed on it, you probably won't have to change it. But if your server's PHP fpm is located elsewhere you will have to set the absolute path to it in here.

Now you just have to enable the newly created site's Server Block config file, and restart the NGINX web server.

OBS This configuration is not prepared to serve sites under SSL certificates (HTTPS). In order to make this, you will have to provide additional settings. Learn more about it following this link.

Using Apache 2 Web Server

This tutorial assumes that you already have some knowledge on Apache 2 and its Virual Hosts.

If you're not acquainted with Apache 2 or want to know more about it, visit the Apache's Official Docs.

To configure Apache to serve a SPLIT PHP application you need to:

  1. create a new Virual Host file inside the sites-available Apache's directory
  2. insert the code shown bellow, making the necessary adaptations, into this file
  3. save it and then enable it on Apache

<?php
<VirtualHost *:80>
    ServerAdmin your.email@site-domain
    ServerName site-domain.com
    ServerAlias www.site-domain.com
    DocumentRoot /path/to/application/root/public
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Here we have some points of attention:

  1. The ServerAdmin option: you must set this option with a proper email, where the site's administrator can receive messages.
  2. The ServerName option: you must set this option with your application's domain name.
  3. The ServerAlias option: you must set this option with your application's domain name preceding by "www.".
  4. The DocumentRoot option: you must set this option with the absolute path to your application's public directory.

Now you just have to enable the newly created site's Virtual Host config file, and restart the Apache web server.

It is possible that the Apache's mod_rewrite module isn't activated, then you will run into some problems with URL. In order to activate it manually run this command:

<?php
a2enmod rewrite
*: This command can vary a little bit depending on your OS.

OBS This configuration is not prepared to serve sites under SSL certificates (HTTPS). In order to make this, you will have to provide additional settings. Learn more about it following this link.

FAQ

Here's a list of frequently asked questions (FAQs) and answers on particular topics.


You can put all static files as CSS, Javascript, media and font files in "/public" directory. Then you can refer to these files by indicating their URLs starting from "/", which is a reference to /public.

See, below an example of an image file placed at "/public/resources/img/example.jpg": <img src="/resources/img/example.jpg"/>

For a better organization SPLIT PHP already has, inside /public, a "resources" folder, which is a preset that you can use to put your static files. But keep in mind that you have total freedom to create your own directory/file tree inside /public, to make it the way you want.

When executing queries to read data from the database, you can make use of Dao::find() passing to it a string containing the SQL Command, like this:


$this->getDao("TABLE_NAME")
  ->find("SELECT * FROM `TABLE_NAME`");

But what if you're writing a complex query with multiple lines? It would work perfectly, but your code's readability could be compromised. In this "/application/sql" folder you can store your .sql files, then pass its path to Dao::find():


$this->getDao("TABLE_NAME")
  ->find("foo/bar");


Changelog

See what's new added, changed, fixed or updated in the latest versions.

For Future Updates Follow SPLIT PHP on Github


Version 1.2 (10 May, 2022)

This release 1.2.11 has improvements in security, error handling and a major improvement on data access and general flexibility, besides some bug fixes.

Now it became more conceptual than ever, so I decided to change its name and image, from "DynamoPHP" to "SPLIT PHP", to better represent the fundamentals of the tool.

  • Added DynamoPHP is now SPLIT PHP. See the section The Concept to understand why.
  • Added New setHeader() and getHeaders() methods in Response object.
  • Added Added an additional field $info on System::errorLog() method.
  • Added Database errors handled with specific Exception object which stores the problematic SQL Command.
  • Added The application level errors logging now can be turned on/off in the configurations.
  • Added A method Service::setTemplateRoot() to set a root path within /application/templates directory.
  • Added Standard Anti-XSRF validation for requests.
  • Added New Dao::first() and Dao::fetch() wrapper methods.
  • Added New Dao::bindParams() method, that automatically parameterizes queries to the database.
  • Added New Service::init() method, for your service's custom initializations.
  • Added Added a DEFAULT_TIMEZONE config option.
  • Changed Removed too much specific functionalities from the Utils helper class.
  • Changed Automatic database connection closing assurance.
  • Changed It is now possible to execute multiple DAO nested calls at once.
  • Changed Database read operations are now separated from the others in a specific read-only connection.
  • Changed RestServices now possess a ready-to-use Response object at $this->response.
  • Changed The RestService::addEndpoint() method now allows closure functions to be passed as handler function.
  • Changed Removed all vendor dependencies from the entire framework.
  • Changed Restructured the entire framework in namespaces.
  • Changed Removed DEFAULT_RESTSERVICE option from configurations.
  • Changed The DEFAULT_ROUTE config option now expects the full route.
  • Changed For better security, all configurations are now based on environment variables.
  • Changed Improved CORS Policy to meet the compliance.
  • Fixed Confusion and unpredictability on Internal Server Error status code
  • Fixed Problem to parse requests containing query strings with encoded chars.
  • Fixed Added a default logical operator "and" to database operations parameterized with Dao::bindParams().
  • Fixed Fixed liability with constant URL_APPLICATION.
  • Fixed Fixed issues with error reporting.
  • Fixed Replace linebreak method with PHP_EOL constant.
  • Fixed Fixed null issue with some outputs sanitized by htmlspecialchars().
  • Fixed Fixed a deprecation warning for using explode() on a possibly null value.
  • Fixed An issue of log entry duplication in index.php.

  • I think there are changes and features here to even release a major version, but as I choose to change the framework's name, I decided to maintain it in version 1.


    Version 1.1 (30 Aug, 2021)

    This release 1.1.4 has some improvements with database operations, included a feature to perform CURLs easier and have some fixes. See the list below for further details.

    • Added Standard .gitignore file.
    • Added Automatic transactional database operations.
    • Added Method Service::requestURL() to perform cURL requests.
    • Changed Dao::find() accepts no parameters for default queries.
    • Changed Improved user readable errors handling.
    • Fixed Removed references to legacy debugger "Pesticide"
    • Fixed Fixed a bug which was causing POST and PUT to not receive query strings params.
    • Fixed RestService executing DAO operations was throwing exception.

    • This release was intended to create features that make the developer's life easier, also improved the consistence of the resulting applications and, of course, fixed some bugs. Hope you enjoy!


      Version 1.0 (7 July, 2021)

      Initial Release