pocket_php Devlog

Updates on the development of the pocket_php project.
(Click the post title for a direct link)

The framework's dead simple template engine was reworked to expand on it's modular design. Namely, it now keeps track of the final HTML string in a stack and renders everything in a single, uninterrupted call. The content stack is filled using addFile() (adds and prepares a file for writing) and addString() (adds and prepares a string for writing) which completely replaces the previous functions. Once filled, the internal content stack is finally sent to the client using render().

This helps smooth the server to client content parse since it's sent in a sequential matter without interruptions. By writing the complete stack to the socket all at once the potential for performance hiccups and browser related issues greatly diminishes as the server load grows and the time between writes potentially increases. Previously, it was possible to break the parsing sequence by sandwiching unrelated operations between renderHeader() and renderFooter(), which start and end the writing process respectively. Though this was never the intended design, as the projects relying on pocket_php diversify and the use of its more elemental components get tested from different angles, the limitations of the overly restrictive templating interface became apparent. Leaving services like REST APIs with little choice but to modify the core class in ways that don't disrupt its intended functionality, a clear design flaw.
The new TemplateEngine class

class TemplateEngine
    private $contentStack;
    private $stackCounter;

    function __construct ()
        $this->contentStack = array();
        $this->stackCounter = 0;

    public function addFile($filename, $data = NULL)
        $fileContents = file_get_contents(VIEWS_DIR . $filename);
        if (empty($fileContents))
            $this->addString($fileContents, $data);

    public function addString($string, $data = NULL)
        if (empty($string))

        if ($data != NULL && is_array($data) || !$empty($data))
            $this->contentStack[$this->stackCounter] = replaceValues($string, '{{', '}}', $data);
            $this->contentStack[$this->stackCounter] = $string;


    public function render()
        if (!ob_get_status()) // IF ob hasn't been manually started by the user

        foreach($this->contentStack as $section)

The core/utility.php file was also overhauled. The template engine keyword replacement function replaceVariables() was substituted by replaceValues(), a more flexible version that can parse keywords between any two different string limiters to encourage its use outside TemplateEngine. Basic directory management, a very general requirement to deal with user uploads was also added in the form of newDir() & delDir(). Check out the core/utility.php commentary on how to speed up directory deletion using system calls.

POCKET_PHP has been updated with extra session management features. For starters, the database table for accounts and the provided HTTPRequest object now include the following session information:

session_timestamp: UNIX style timestamp of the session start
session_id: The UID PHP assigns to the user at session start
logged_in: Flag value that reflects the state of the session using this account
logout_type: How the session was last terminated

With this data the framework can provide a more thorough status of the account assigned to each session. This is necessary because PHP manages sessions by storing them in either a database or as a file in the local file system (default), then sends the client this same UID as a cookie. When the client connects to the POCKET_PHP™©® server the browser (by default) sends whatever cookies match the target domain along with the actual HTTP request, the server then matches the cookie's UID with it's internal sessions and, if it finds a match, proceeds to assign the account data to the $_SESSION superglobal. Linking the client's session with an account in particular.
As long as the client keeps the session cookie and the server keeps the session alive (be it as a local file or an entry in a database) the user will be recognized by the server and automatically assigned the sessions data on each request.

As simple and efficient as it may seem, the codependency on the user's cookie and the server's session registry does further complicate the problem. Neither side has any control or notion about it's opposite SID container. If the server decides to delete a user's session (i.e. delete the correspondent file from the sessions directory) then the user's cookie can't be verified and thus, becomes useless. Likewise if the user decides to manually delete or tamper with the cookie (or it simply expires) then no ID is sent with the user's requests, the server-side session file then becomes useless. In the case of the former, the client's now invalid cookie is discarded and a new one is generated, in the case of the latter, however, it's the server's session file that is now invalid. It will simply generate a new UID, store it as another session file and send it back as a cookie to the user, leaving the user's previous session file pointlessly waiting for activity.

The way PHP deals with these potentially obsolete files is by having a chance of triggering a garbage collection routine every time session_start() is called. The probability of it happening is calculated by divding the php.ini settings session.gc_probability and session.gc_divisor. By default, PHP assigns these values to 1 and 100 respectively, giving the garbage collection a 1% chance of being triggered by calls to session_start(). This routine checks the existing session files and deletes them based on the expiry time set by session.gc_maxlifetime. Needless to say, each file has to be individually checked for expiration making this a linear search problem with a (O)N performance rating, meaning that the more session files there are, the more computationally expensive the routine grows. Hence why PHP defaults to a 1% chance of calling it, and only during session creation (perhaps the least intrusive step to add more overhead to).

In a scenario where the client's cookie is prematurely disabled and the server side SID file remains, the account associated to the session won't be properly logged out, even the internal session life counter won't be recognized as expired because the client can't match the session file to trigger this check anymore. This means that if the client (or anybody else, for that matter) tries to log in to this account, POCKET_PHP will recognize it as already logged in and must either reject the latest login attempt or reassign the account data to the newly created session. Either way the previous session file should be manually destroyed to avoid having to wait for the garbage collector, hence why the SID assigned to an account on login is stored in the accounts table. Should a login clash occur, POCKET_PHP will attempt to manually destroy the session file saved in the account entry and replace it with the new SID. The account's logged in status then correctly reflects it's pairing with the new session and it's previous session identifier no longer hogs memory by uselessly sitting in the sessions directory. It also updates the "logout_type" cell with "SESSION_HIJACKED".

Do note that a "SESSION_HIJACKED" simply means that the account shifted sessions without logging out, this is also the case when the server side SUI file is deleted or expires.

Of course, the other ways of ending a session are also reflected in the account's entry.

CLEAN_LOGOUT The user manually logged out
SESSION_INACTIVITY: The user (with a valid SUI cookie) refreshed the page after the inactivity tolerance (specified in SESSION_INACTIVITY_TOLERANCE) was over
SESSION_MAX_DURATION: The user (with a valid SUI cookie) refreshed the page after the session max duration (specified in SESSION_MAX_DURATION) was over
SESSION_HIJACKED: The account's assigned session was reassigned without logging out

This extra control enables the administrators to make better informed decisions in regards to suspicious accunt activity, an account that continuously has it's sessions hijacked is a clear sign that something's wrong, in contrast with the occasional hijack which may simply be a user that forgot to logout of his account in his desktop computer and relogs to the same account from his phone before the garbage collector can clear the previous session. A smooth transition between sessions that offers a much better user experience than outright rejecting the login attempt.

The app/configuration.php file has been updated to include settings for the session management changes, including a new session directory (app/tools/sessions) to store the SID files in a virtual host basis, this prevents PHP from piling up all the hosted sites SIDs into the same folder and the php.ini global settings from messing with individual configurations. Sessions themselves can also be toggled on and off from the config file, if enabled alongside the included request tracker (TRACK_REQUESTS), they are also added to the tracked data.

Finally, the included example site has been repurposed as a landing site and as a feature tester, keeping the project information updated in three different domains (xenobyte, the project's README file and the example site) was getting tedious, not to mention redundant. It will instead serve as the default landing page for POCKET_PHP installations.

New home and settings pages

There are still a couple of relatively minor tweaks and additions I'd like to make before releasing a few more example projects as previously planned. But with my current workload I'm just glad I got this update through.

All the projects I've worked on that rely on pocket_php and make use of POST requests to process user provided data require checking if the request was indeed sent as POST. The HTTPrequest->arguments member variable was supposed to abstract this away by only parsing the POST data into the request arguments array if the request was indeed a POST, if it happened to be a GET request containing data that would otherwise be sent by POST, then the engine would discard those form elements. Not a big deal since it's mandatory to check if the data is even present in the first palce, but the monolithic arguments container still loses the intended context by mashing both GET and POST arguments into a single entity.

The HTTPrequest->arguments variable has been replaced by HTTPRequest->GET & HTTPRequest->POST respectively, this means that all and any inline URL arguments will always be present in GET despite the request having been originally of type POST and the POST argument array will only be populated on POST requests.

Minor change with a big impact in readability.

I still have a beefy backlog to clear but I hope to upload a few pocket_php backed websites as documentation in the coming months.

If you've ever managed any kind of online service you're well aware of how easy it is to abuse unprotected forms. A simple python script can wreak havoc by bombarding a server with randomized input that has to be carefully sanitized to prevent injections. Said processing, however, is typically the most computationally expensive request for a simple website to honor due to the complexity of the steps involved; sanitizing the user input, validating the cleansed data, writing it to a database, etc. Thus, unless we know for certain that the form we received was legitimately answered it is best left discarded, lest we neglect gate-keeping the database only to find it littered with nonsense or worse.

pocket_php captchas samples
The previously mentioned script that fills the form with randomized characters wouldn't be too difficult to detect and mitigate, but a slightly improved version that generates a well formatted email address (regardless of it's authenticity)? Not so much. Even potential solutions, complicated as they mey get, would most likely only work when validating the email field, not the rest of the form which would probably require their own specialized routines. The resulting increase in resource consumption doesn't even ensure the processed form was legit in the first place, only that it passed the aforementioned filters.

For the sake of brevity, now that the problem has been illustrated I'll jump to the point. This is no easy problem to solve, but there are simple and effective precautions that universally apply to internet forms that will at least help deter automated injections, namely captchas.
The idea behind them is quite clever, exploit the fact that there are easy to generate problems that a computer still can't crack but a human can solve in an instant. With character recognition tests being among the more popular.

As for the inevitable cost, captchas may be straightforward but are certainly not free, at least in contrast with leaving a form unprotected, and in cases where the server is working at or near capacity having to generate captchas would definitely worsen the service's responsiveness. On the opposite scenario where the server is practically idling it's quite likely that working the captchas becomes the most expensive step of the form's validation anyway. Alternatively, it's common practice to outsource captchas to reduce the local workload at the expense of the user's privacy.
whichever you choose, safeguarding forms automated attacks has a price, but it is practically ALWAYS WORTH PAYING.

The captcha functionality added to the pocket_php example login page is very simple and effective. It randomizes a set amount of characters from a given input string, draws randomly generated squares to the randomly colored background to obfuscate the foreground, renders the selected characters at a random angle, position and color (within reason, it'd be redundant to make this hard to answer for humans) and finally, the generated string is stored in a PHP session variable to subsequently validate the client's answer. Should the created captcha be too difficult to read for a human, all the client has to do is ask for a new one either by pressing the refresh captcha button or by reloading the form. Ezpz.

Note that the internal login captcha can be (de)activated by setting the ENFORCE_LOGIN_CAPTCHA in app/configure.php and utilizes the php-gd library.

Local, private, effective captcha

Before ending the post I'd like to clarify why captchas were added while other utilities get overlooked from the project. Pocket_php is currently powering fourteen web services with more planned, working with these individual projects I often get tempted to add a particularly handy snippet into the pocket_php codebase for future use, only to scrap the idea in favor of it's original vision; to provide the fastest, simplest template for webprojects to use as foundation. Abiding by this rule means that certain kinds of utilities are often not incorporated into source due to them being either too situational or just not worth the effort to implement on behalf of the programmer.

What separates captchas from other potential features is how necessary they've become and how widespread the use of (often subversive) third-party captcha services has grown in response, specially among sites that don't really benefit from such an approach at all. In the end, it's not about how easy or fast it is implement captchas, it's about having the option of privacy at the same reach as the alternatives.

Pocket_php's session manager has been updated to handle both inactivity timeouts and login expiration independently from the php-fpm daemon configuration, this enables projects with different session configurations to be simultaneously run on the same web server. The demo website was also updated. Updating to 1.3.

The new pocket_php demo site

PHP's native cookie management is already as simple as it can possibly be, wrapping its minimalism around a mere rebranded interface would only bloat the codebase. Though I have yet to encounter the need to modify said cookies policy, the encapsulation of the cookies themselves and off the PHP global scope fits the overall design better, the HTTPRequest class now has a container for all of the client's cookies.

Updated to 1.2.

In Pocket_PHP ver1.0 the included configuration file for the NGINX virtual server block allowed all non .php files within the specified root directory to be publicly available. This was intentionally done to keep the rules as general and open as possible, should the need arise to limit access to certain file names and formats it could be filtered either through NGINX or in some cases (like hidden UNIX files) through Pocket_PHP.

This meant that the internal sqlite database file (/app/core/pocket_php.db) was available for download as a static file, with the new VBS rules such files can be blocked by the webserver itself. As an alternative to keep such files publicly available by the webserver while simultaneously keeping files of the same format as private, place said files outside the root (/app/) folder and thus inaccesible to the NGINX process. Just make sure to give the php-fpm daemon access to this private folder.

Lastly, the www. extention is by now irrelevant, an extra server block was added to the configuration file to redirect calls with the www. prefix to be redirected to the clean URL.

Here's the updated VBS config for NGINX 1.17:

server {

    listen 80;
    listen [::]:80;
    listen 443;
    listen [::]:443;

    server_name www.pocket_php.localhost.localhost;
    return 301 $scheme://pocket_php.localhost$request_uri;

server {
   listen 80 default_server;
   listen [::]:80 default_server;

    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;

    # Change to your own certs
    ssl_certificate     /etc/nginx/ssl/pocket_php.crt;
    ssl_certificate_key /etc/nginx/ssl/pocket_php.key;
    ssl_ciphers         EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
    # ssl_dhparam         /root/certs/example.com/dhparam4096.pem;
    ssl_prefer_server_ciphers on;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    # Whatever folder you pick, it must be owned by the same user:group running the 
    # nginx instance, permissions should be 775 for maximum security gainz
    root /var/www/html/pocket_php/app/;
    # Add index.php to the list if you are using PHP
    index index.php;

    # LOCAL SERVER NAME (remember to add the localhost address to /etc/hosts)
    server_name pocket_php.localhost www.pocket_php.localhost;

    # Do not serve hidden files (.filename)
    location ~ /\. {
        deny all;

    # Serve static files directly
    location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|webm|txt|asc)$ {
         access_log off;
         expires    30d;
         try_files $uri =404;
         #deny all;

    # Deny sqlite.db files
    location ~* ^.+.(db)$ {
         deny all;

    # Execute all .php files
    location ~ \.php$ {
         include fastcgi.conf;
         fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
         fastcgi_param SCRIPT_FILENAME /var/www/html/pocket_php/app/index.php;   

    # Redirect all requests to /app/index.php
    location / {
         #try_files $uri $uri/ /index.php?$args;
         try_files $uri /index.php?$args;

    # Redirect errors to pocket_php
    fastcgi_intercept_errors on;
    error_page 400 403 404 /index.php;

A friend of mine let me know that xenspace.net wasn't available through certian browsers (like Safari) that discard all "non secure" sites by default. Turns out I forgot to update the site's SSL certificate.
I requested a new one and in the process of verifiying ownership of the site, I realized that pocket_php (the server's backend) didn't support access to folders that began with a ".". I updated the configuration.php file, adding two constants that represent the assigned strings as well as new routing exceptions for the very specific URL the certificate authority uses to validate the site.

Note that this requires a simple server configuration update, see the provided "default" nginx configuration file.

While setting up a client's VPS to host a pocket_php based web project I noticed the include nginx.conf file is no longer compatible with nginx ver. 1.16.1. The "group" directive has been deprecated and must now be specified in after the user in the "user" variable field.

I rather keep all relevant info related to the project's development here. PHP is still seen as a "funny" tool of the past, like those bone saws that surgeons used to amputate limbs. It seems to me that those that ironically hold these opinions are just inexperienced for they have yet to understand that Web development is shit by nature, regardless of the tools used.

I can't remember when I originally started working on pocket_php, since the original devlog is mostly useless I'll just start fresh here.