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.
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!
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.
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
-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
-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
-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.
php console server:stopphp console server:status
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.
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;
}
}
$this->getService() in the session
Reference Guide 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.
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. } }
$this->renderTemplate() in the service.
$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'");
});
}
}
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:
GETPOSTPUTDELETE
- 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.
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);
});
}
}
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$textin the response body with no formatting.withData($data, bool $escape = true): sets response's content type to "application/json", encodes the content of$datainto 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$contentto the response body with no formatting.withXMLData($data): sets response's content type to "application/xml", encodes the content of$contentto XML and then attach it to the response body.withCSS($content): sets response's content type to "text/css", then attachs the content of$contentto 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.
}
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:
-
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. -
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. -
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:
-
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); -
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); -
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();
> 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() | = |
|
| differentFrom() | != |
|
| biggerThan() | > |
|
| lesserThan() | < |
|
| biggerOrEqualsTo() | >= |
|
| lesserOrEqualsTo() | <= |
|
| likeOf() | LIKE |
|
| in() | IN |
|
| notIn() | NOT IN |
|
> 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();
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.
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
*/
}
}
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 asUNSIGNED.->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
➤ 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
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.
To configure Nginx to serve a SPLIT PHP application you need to:
- create a new Server Block file inside the sites-available Nginx's directory
- insert the code shown bellow, making the necessary adaptations, into this file
- 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:
- The
server_nameoption: you must set this option with your application's domain name. - The
rootoption: you must set this option with the absolute path to your application's public directory. - The
fastcgi_passoption, insidelocation ~ \.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.
Using Apache 2 Web Server
This tutorial assumes that you already have some knowledge on Apache 2 and its Virual Hosts.
To configure Apache to serve a SPLIT PHP application you need to:
- create a new Virual Host file inside the sites-available Apache's directory
- insert the code shown bellow, making the necessary adaptations, into this file
- 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:
- The
ServerAdminoption: you must set this option with a proper email, where the site's administrator can receive messages. - The
ServerNameoption: you must set this option with your application's domain name. - The
ServerAliasoption: you must set this option with your application's domain name preceding by "www.". - The
DocumentRootoption: 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.
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()andgetHeaders()methods inResponseobject. - Added Added an additional field
$infoonSystem::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/offin 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()andDao::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_TIMEZONEconfig option. - Changed Removed too much specific functionalities from the
Utilshelper class. - Changed Automatic database connection closing assurance.
- Changed It is now possible to execute multiple
DAOnested calls at once. - Changed Database read operations are now separated from the others in a specific read-only connection.
- Changed
RestServicesnow possess a ready-to-useResponseobject 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_RESTSERVICEoption from configurations. - Changed The
DEFAULT_ROUTEconfig 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 withDao::bindParams(). - Fixed Fixed liability with constant
URL_APPLICATION. - Fixed Fixed issues with error reporting.
- Fixed Replace linebreak method with
PHP_EOLconstant. - Fixed Fixed
nullissue with some outputs sanitized byhtmlspecialchars(). - Fixed Fixed a deprecation warning for using
explode()on a possiblynullvalue. - Fixed An issue of log entry duplication in index.php.
- 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
POSTandPUTto not receive query strings params. - Fixed
RestServiceexecutingDAOoperations was throwing exception.
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.
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