Table of Contents
  1. Introduction
    1. Welcome
    2. Features
    3. Requirements
    4. License
    5. Brief history
    6. Migration guide
    7. Glossary
  2. Get Started
    1. Design and goals
    2. Installation
    3. Usage
    4. Background jobs
    5. Webserver config
  3. General Topics
    1. Security
    2. Configuration
    3. Monitoring
    4. Directory Structure
    5. Running Overview
    6. URL Routing
    7. URL Filters
    8. Models
    9. Controllers
    10. Views
    11. Cache Management
    12. Services
    13. Sitebuilds
    14. Assets
    15. Contents
    16. Events
    17. Extensions
    18. Repositories
    19. Coding Standards
    20. Developing
    21. Benchmarks
    22. Clustering
    23. Manage Database
  4. Tutorials
    1. Hello World!
    2. Routing
    3. Application
    4. Service
    5. Widget Add-On
    6. Raspberry Pi setup
    7. Clustering
    8. Easy carousel
  5. Library reference
    1. PHPPE\Core
    2. PHPPE\ClassMap
    3. PHPPE\Client
    4. PHPPE\Model
    5. PHPPE\Ctrl
    6. PHPPE\Http
    7. PHPPE\DS
    8. PHPPE\Cache
    9. PHPPE\Assets
    10. PHPPE\View
    11. PHPPE\Tools
    12. PHPPE\Filter
    13. PHPPE\AddOn
    14. PHPPE\UsersPack
    15. PHPPE\RegistryPack
    16. PHPPE\DBPack
    17. PHPPE\EmailPack
    18. PHPPE\GPIOGPIO
    19. JavaScript PluginsPack
  6. Downloads

Enable Javascript

Downloads

PHPPE Core

Core (90k)
Minified production version.
github.com Inlined
Source (200k)
Developer friendly version. github.com Browse online
Developer (95k)
Tests and repository build tools.
Self Test View (24k) Download Source

Extensions

PHPPE Pack (96k)
Core extensions, most notably jQuery.
Download Source
CMS (28k)
Content editor.
Download Source
Extensions (24k)
Web-based extension manager.
Download Source
Bootstrap (149k)
Twitter's environment.
Download Source
Data Library (19k)
Utility to upload and organize attachments.
Download Source
Gallery (4k)
Picture manipulation and management tool.
Download Source
DBA (5k)
DataBase Administrator web interface.
Download Source
GPIO (4k)
GPIO Interface for Raspberry Pi.
Download Source
RPi (8k)
Web-based monitoring tool for Raspberry Pi.
Download Source

Welcome to PHPPE3!

HINT: just start typing what you're looking for.

PHP Portal Engine - single file framework

PHPPE is a minimalistic, yet feature-full micro-framework and CMS. The framework's core is a single file and only a few kilobytes in size, so small, that it fits on your clipboard! Unlike other existing OpenSource PHP frameworks, PHPPE was written with security, MVC, KISS principle and no dependency at all in mind. As being a micro-framework, it won't solve all of your web-development oriented tasks, but will definitely solve the most common ones and make your life easier. It's not bloated, and with simplicity cames stability and high performance.

As it contains 7 bit ASCII characters only it's safe to paste over terminals for easy installation. As a matter of fact this single html file you're reading is enough to set up a working PHPPE environment, internet connection is NOT required at all!

Features

This ~90k bytes of PHP code will give you:

Of course one single file is limited, so here's the PHPPE Pack (another ~90KiB) to save the day and give you an easy start with configuration registry, email services, user management, SQL Query Builder etc.

For full CMS capability you'll also need the Content Editor in PHPPE CMS (46KiB), because PHPPE Core on it's own only serves contents.

Requirements

Optional

License

PHPPE Core, PHPPE Pack, PHPPE CMS, PHPPE Extensions as well as PHPPE Developer are free and OpenSource softwares, licensed under LGPL-3.0+. See LICENSE for details.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

Although the framework is free, keep in mind that extensions may or may not use different licenses. PHPPE's licensing allows you to implement a closed source application on top of it.

Brief history

Quick and dirty 2 to 3 migration guide

FilesLibraryTemplater
  • $sys->needframe → PHPPE\Core::$core->noframe
  • $sys->neederror → Ø
  • field_*() → *->edit()
  • show_*() → *->show()
  • valid_*() → *::validate()
  • <!template> → <!include>
  • Ø → <!template>...<!/template>
  • <!mode> → <!app>

Design Choices and Architectural Goals

Let's make one thing clear: when I started to write this micro-framework about ten years ago, I was unsure how many of these goals can be achieved. It was started as an experiment, a PoC that later begun it's own life and turn out to be extremly useful in many every-day-real-life situations.

Really small foot print

What I mean by that is a really-really-really small foot print, not in a CI (11MiB) or lumen (30MiB) sense, but measured in couple hundreds of kilobytes. I wanted to run this framework efficiently on micro-computers, later my choice fall on Raspberry Pi.

Single file deployment

Must work on it's own, but has to be able to detect and corrigate it's environment by building up directory structures if necessary without any kind of help.

Keep It Simple Stupid

Only optional dependencies. The main reason why other frameworks fail being small and fast lies in their huge amount of dependencies. There's no reason to include a bloated library if only one or two methods is used out of it. PHP has a reach, constantly expanding function set, there's always a better, simpler way with pure php functions. You can argue with that, but time has shown that I'm right.

Also there should be about a dozen, clear event hooks. Too few or too many hook-points reflects serious design flaw.

Modern MVC Framework

It has to be a Model/View/Controller framework, meaning you can build custom webapplications on top of it if you want easily. For that it must provide all key features of a modern PHP framework (ORM, class autoloading, templater, etc.)

Not just a framework, but a CMS as well

It was also important that if no webapplication found for an url, it must serve contents from a database or cache.

Performance

This is the main reason of low foot print and why caching is so sophisticated in PHPPE. Not to mention nginx compatibility and the built-in benchmarking feature.

Scalability

PHPPE was designed in a way that it can run on several load-balanced, dynamically started/stopped content provider servers, all feeded by a single content editor server.

Configurationless modularity

It's a very important design goal that installing an Extension should not require anything else than copying files under vendor/phppe/. If file access rights are correct, no need for running diagnostics mode.

Transparent datascheme installation

As a part of the previous goal, it has to be able to install new tables transparently, without of the need of loading sqldumps with third-party utilities.

Security

Security is not something that you can achieve by constantly patching your codebase. It has to be designed that way, making strict decisions, like:

Stability

The Core must be stable and well-tested. No new features or interface changes within major php releases allowed there, that's what well-though hook points and Extensions are for.

Effectiveness

Eliminate the need of PHP coding as much as possible for webpage changes. This means that every aspect of the page should be stored as data, so that it can be changed from a web interface without coding. As for the Models, no field specific stuff should be hardcoded in controllers, so changing the database scheme won't lead to chaging the code, only Model class top.

By doing so makes the website changes easy, rapid and cheap as no development involved.

Usability

From an administrator's point of view, it must have a short learning-curve and intuitive interface. It's good if that also can be achieved at developer level, but not so important. An application is written once, but used many-many times, that should make the priority clear.

Directory Structure

PHPPE has a very comfortable build-up. The PHPPE Core, the application as well as all extensions share the same directory structure.

ProjectRoot

This is the working directory of PHPPE. When you specify any path within PHPPE, you'll have to use paths relative to this directory. This saves you a lot of typing in your code and ease the migration across servers.

.tmp/directory where the webserver temporarily saves uploaded files, caches etc.
app/your application, follows PHPPE Extension structure. In next chapter replace "vendor/phppe/extension" with "app" for application.
data/permanent, local file storage (subdirectories are unspecified, as you like, but using separate subdirectory for each model and application is strongly advised)
phppe/symlink to vendor/phppe/Core for convience
public/used by the webserver, index.php and static files goes here. In your webserver's configuration when you set up DocumentRoot, you should point here: "ProjectRoot/public".
vendor/main code directory. All directory beneath vendor/phppe shares the same PHPPE Extension structure
composer.jsonPHP Composer metadata of your project

Subdirectories of every PHPPE Extension

These directories are optional. If an extension does not provide let's say controllers, ctrl/ can be ommited.

vendor/phppe/extension/
Mandatory files
composer.jsonExtension packaging information for PHP Composer and PHPPE Extensions.
config.phpconfiguration file.
init.phpExtension initialization file. These will be included automatically for every page
Model
libs/classname.phpmodel libraries, included by applications or other libraries. Also business logic goes here.
sql/table(.engine).sqldatabase schemes for tables used by models. It's highly recommended to prefix table names with lowercase extension name, like users, users_history, forum_topics, forum_posts etc. You can also use engine specific extensions, like ".sqlite3.sql".

Controller
ctrl/action.php
ctrl/application_action.php
page handler logic goes here. First holds code for common code, second is for action specific code.
libs/extension.phpIf you don't want to separate controller files, you can but action methods in your service class here.

View
views/application_action.tpl
views/application.tpl
view templates. If action specific template found, it will be used.
addons/addon.phpif the extension provides Add-Ons for the view layer.
css/extension specific stylesheets
images/images used by the extension
js/javascript libraries provided by the extension
fonts/webfonts provided by the extension
lang/lang.phplanguage specific text definitons for each extension
out/format_header.php
out/format_footer.php
for formats other than HTML (like RSS), their header and footer generating code goes there

Important files and directories

phppe/config.phpthe framework's global configuration
data/log/extension.loglogs are generated here. The files php.log, phppe.log, db.log, diag.log and validate.log are handled by the Core iself
public/.htaccesspermissions and url rewrite directives (used by Apache webserver. Not needed with nginx)
public/favicon.icoyour project's logo
public/index.phpthe framework itself (don't touch)
app/config.phpyour project's configuration
app/init.phpyour project's initialization code, for example url routing goes here
app/css/your project's stylesheets. One for each theme
app/images/images used by your project's face (no content specific imagery allowed here)
app/js/javascript libraries required by your project
vendor/bin/if you need shell or other non-php scripts (like a special nagios plugin) for your project, it should be placed here.
vendor/phppe/*/libs/ds_engine.phpSQL translations and connection initialization commands for datasources.

Symbolic links

It's very important that your working directory is ProjectRoot. Therefore all symlink paths should be relative in PHPPE.

FileLink toPurpose
phppevendor/phppe/CoreThis link is merely a shortcut in ProjectRoot.
vendor/phppe/appappThis link points in opposite direction on purpose: we don't want composer to accidently overwrite your project's files. Also, app/ is managed by your git repository, whereas extensions in vendor/phppe/* is stored on github.
app/csspublic/cssYour application's assets. The symlink points to a subdirectory in "public/", which means they will be served by the webserver as static files without php interpretation. This does not mean you cannot use dynamic assets in "app/", Core will handle them just like any other extension's assets.
app/jspublic/js
app/imagespublic/images

Glossary

Access Control Entry (ACE)

Specifies which user can do what, optionally on which object. It can be granted to PHPPE\Users object like PHPPE\Core::$user->grant("editgroup"). If you want to limit access to a specific object, you can append it's id to the ACE: PHPPE\Core::$user->grant("editgroup:13").

Access Control List (ACL)

A list of ACEs. In Menu, URL filters as well in Templates you can refer to an ACE with "@" prefix, like "@loggedin". For a list, use pipe to concatenate ACEs (like "@siteeditor|siteadmin") or in php you can use an array.

Add-On

Kind of Extension that expands templater's functionality on <!var>, <!field>, <!widget> and <!cms> tags. They have to be inherited from PHPPE\AddOn.

Application

This is the main part that generates the page. Usually has one or more controllers. Your main application resides in app/ folder, and it's controllers in app/ctrl, models in app/libs and views in app/views. Otherwise every Extension can provide controllers, models, views and therefore act as an application. Also, your application is symlinked as an Extension in vendor/phppe/app so that it can be a Service as well. The PHPPE environment is a set of applications, each responsible for different urls.

Client

End user's application that interacts with PHPPE. It's a webbrowser or a shell running in a terminal. PHPPE\Client class holds every information you'll ever need about it, but if not, it can be expanded using extensions.

Concept

The PHPPE concept of how a homepage is organized and should be generated is as follows:

Core

This is the heart of the single file framework, defined in public/index.php. It's also the name of the first extension, that's located in vendor/phppe/Core, so to distingwish between, the latter called Pack. If the extension PHPPE Pack is not installed, the built-in Core will create the directory structure for it when executed in diagnostics mode.

Diagnostics mode

This can be invoked only from CLI by passing the argument "--diag". In this mode Core will examine the ProjectRoot's directory structure, will fix missing files and if run with root privileges it will fix file permissions as well. Nonetheless it fires diag event, so that extensions can run their own, specific diagnostic functions.

Dock

Right corner of Panel, holds status information about extensions. Populated by stat() hook methods.

DocumentRoot

The base folder where the webserver will look up files. It's the folder named public under your ProjectRoot.

Extension

Any Add-On or Service that can be added to or removed from your PHPPE environment. One Extension can add several Add-Ons and Services at once to the system. It was one of the main goals that installing an extension should require no more than copying it's files. In case file access rights aren't set up correctly, you can run diagnostics mode.

Frame

This is the main template that frames every application's output. Within a frame template, use <!app> tag to mark the place of your application, in other words this tag separates frame header and footer. Header usually contains a page menu and a navigation bar, while footer has a sitemap and an impressum line.

Maintenance mode

This can be turned on in phppe/config.php with 'maintenance'=>true. In this mode controllers are skipped and only a view displayed using a template called "maintenance". This is used to temporarily put your site down until you finish with delicate upgrades.

Menu

Left side of Panel, populated by extensions with PHPPE\View::menu() calls during their initialization.

Model/View/Controller

PHPPE uses MVC design pattern. Routing desides which controller's method to run, which in turn finds out which models to call to get data for filling in the gaps in view template.

Service

Is an extension that adds a function library (in form of a php class) to your environment.

Object Relational Mapper

Model that's inherited from PHPPE\Model has the ability to be mapped directly to database tables.

Pack

Is the name of the first extension that ships vendor/phppe/Core. It's called that way because it ships more classes, and also because the name Core is already used for the class defined in public/index.php.

Panel

If the user is logged in and has "panel" ACE, a nice panel will be shown on top of the page at fixed position. This panel holds the Menu and the Dock with status icons for extensions; as well as the language selector dropdown and the user menu with profile and logout links.

ProjectRoot

The main directory of your project. It has the main composer.json and the folders app, public, vendor etc.

Runlevel

This can be set in phppe/config.php with 'runlevel'=>X. This modifies how PHPPE behaves and the verbose level of logs it generates.
  0 - production
  1 - verbose / testing
  2 - development
  3 - debug

Template or Layout

It's an (usually, but not necessairly) xml with placeholders for application properties and tags for control structures and Add-Ons. Views are generated using templates.

Installation

All-in-one commands for people who are too lazy to read through:

With Packagist
composer create-project bztsrc/phppe3
mv phppe3 (yourproject) && cd (yourproject)
$EDITOR phppe/config.php
With git
git clone --branch 3.0 https://github.com/bztsrc/phppe3.git
mv phppe3 (yourproject) && cd (yourproject)
sudo php public/index.php --diag
$EDITOR phppe/config.php
Without Packagist (Composer alone)
mkdir public
curl https://raw.githubusercontent.com/bztsrc/phppe3/master/public/index.php >public/index.php
sudo php public/index.php --diag
composer update
$EDITOR phppe/config.php
From this documentation, no dependencies at all
mkdir public
cat >public/index.php
         copy this, paste here and press ^D
sudo php public/index.php --diag
$EDITOR phppe/config.php
HINT: If you already have a running PHPPE somewhere, you can use PHPPE Extensions Manager to bootstrap the framework on another server. All you need is a key based SSH connection.

1. Get the single file framework

Open up a terminal, and ssh to your server (or use a windowed ssh client). Change working directory to your ProjectRoot, then type

$ mkdir public
$ cat >public/index.php

Copy the Core to your clipboard: click here, select all and copy. Switch back to Terminal, paste the code from clipboard then press Ctrl+D.

If your terminal is laggy and you cannot paste the code without mistakes, copy'n'paste this much shorter command that downloads the file from the server.

From server
$ curl https://raw.githubusercontent.com/bztsrc/phppe3/master/public/index.php >public/index.php

If you take a look at your site in a browser, you'll see a greeting message:

PHPPE works!

Next step: php public/index.php --diag

NOTE: you can skip the following when using Composer as it automatically triggers diagnostics (go to step 3).

2. Run diagnostics

Now framework is in place, it's time to run diagnostics to create directory structure and missing files. Without the "sudo", file ownerships and permissions may be left incorrect.

$ sudo php public/index.php --diag

If you failed to copy'n'paste the code correctly, you'll see this:

PHPPE-C: Corrupted index.php
LOG-C: unable to write data/log/phppe.log

Root privilege is only needed for setting up file permissions, if you run it as a normal user, chown and chgrp may fail. In this case you'll see a warning:

DIAG-W: not root or no php-posix, chown/chgrp may fail!

If your webserver's group cannot be autodetected, or you run your vhosts under different users with setuid and setgid, you can specify the desired group id like:

$ sudo php public/index.php --diag --gid=33

If everything went well, you should see this message:

DIAG-I: OK

3. Check it in a browser

Check your site in a browser. You should see something similar to this:

PHPPE works!

Next step: php public/index.php --self-update

Test form
Text Pass Num(100..999) Phone File
_REQUEST:
array (size=0)
  empty
_FILES:
array (size=0)
  empty
 
core.req2arr('obj'):
array (size=6)
  'f0' =>  '' (length=0)
  'f1' =>  '' (length=0)
  'f2' => null
  'f3' =>  '' (length=0)
  'f4' =>  false
  'f5' =>
    array (size=0)
      empty

At this point the framework is fully functional, although it worth expanding it's capabilities by configuring and installing one or more extensions. The easiest way to do that is --self-update.

The --self-update option will update the Core to the latest version and it will also install PHPPE Pack (see step 5) and PHPPE Extensions Manager (see step 6). It's useful if you don't have Composer or you prefer web interfaces and want to minimize your command line activity.

4. Configure your site

Open phppe/config.php with your favourite editor over a terminal, and set up framework parameters.
$ nano phppe/config.php
$ vim phppe/config.php
It's very likely that you have an $EDITOR environment variable holding default editor's name.

You can use the passwd utility to generate masterpassword hashes from command line.

$ php public/index.php passwd
Password? You won't see what you type here
$2y$12$VUzOd391c2oYiESz3FSi2eZz1Gk5B9A2Kz66rvuNkkR04xQ/6yp0G

5. Install PHPPE Pack (optional)

NOTE: if you've used composer create-project, Pack is already installed. Go to step 6.
NOTE: if you've used --self-update, nothing left to do, you're done! Go to step 7.

The diagnostics mode created missing files, among others composer.json. With that you can make PHP Composer to install vendor/phppe/Core from PHPPE Pack:

With Composer (not using Packagist)
$ composer update
Without Composer
$ curl https://bztsrc.github.io/phppe3/phppe3_core.tgz | tar -xz -C vendor/phppe/Core && sudo php public/index.php --diag

6. Install PHPPE Extensions Manager (optional)

NOTE: if you've used --self-update, nothing left to do, you're done! Go to step 7.
With Composer
$ composer require phppe/Extensions
Without Composer
$ mkdir vendor/phppe/Extensions && curl https://bztsrc.github.io/phppe3/phppe3_extensions.tgz | tar -xz -C vendor/phppe/Extensions
You'll find it's configuration in vendor/phppe/Extensions/config.php. Once you've set up this, you can configure all the other Extensions over a nice web interface. This is the recommended way.

7. Install PHPPE CMS (optional)

For a full Content Management System you'll also need the Content Editor:

With Composer
$ composer require phppe/CMS
Without Composer
$ mkdir vendor/phppe/CMS && curl https://bztsrc.github.io/phppe3/phppe3_cms.tgz | tar -xz -C vendor/phppe/CMS

This extension is not installed automatically as in a cluster you only need to install CMS on the management node(s).

That's it! Enjoy using your shiny new PHPPE environment! In the browser, add "/login" to the URL, and log in as "admin" with password "changeme".

Usage

PHPPE has a concept of an url-related application that has several actions, each operating on a specific item. An application houses one or more constroller classes, in which every action is mapped to a method, that will receive item as argument.

As a web CGI

If no URL Routing rules applies to the queried url, it will fallback to default routing, which parses the url as

(base) / application / action / item
https://my.domain/some/path/users/modify/joesmith
which will call modify("joesmith") method of PHPPE\Ctrl\Users class. See routing section of this documentation if you want more sophisticated control on which method with which arguments to be called for a specific url.

As a CLI script

Some projects requires cron jobs or administration tools. For these PHPPE has CLI support, so that you can access and use your models and classes from a command line script as well. You can specify the application, action and item triumvirate as command line arguments.
php public/index.php users sendnotifymails
php public/index.php users delete joesmith
php public/index.php cron dailytasks
php public/index.php --version
php public/index.php --help
php public/index.php --diag

It's worth setting up an alias for PHPPE

alias phppe = 'php public/index.php'

As a library

If you want to access PHPPE environment from another framework or single php, just simply include it:

1
include("public/index.php");

This will bootstrap the basic PHPPE Core, autoloads classes and initializes services, but does nothing more. If you also want to load an application, run a controller in it and generate a view for it, you'll also need

1
2
$phppe = include("public/index.php");
$phppe->run();

NOTE that the latter is quite uncommon and discouraged. It's clearer to use routing than calling run() directly.

Configuration

PHPPE Core is configured in phppe/config.php, with an array. Available properties can be found on PHPPE\Core Class documentation, these are only the most commonly used ones.

dbspecifies primary database connection
titlename of the site (page title)
runlevelselect production (0), verbose/testing (1), developer (2) or debug (3) mode
maintenanceif set to true, will bypass all routing and controller, and only displays mainteance view for all urls
mailerEmail backend, used by PHPPE Pack's Email sender, like "smtp://[(user)[:(pass)@]](server)[:(port)][/(sender email address)]"
allowedlist of allowed functions in an array, leave it unset for no restrictions.
disabledlist of disabled Extensions
blacklistlist of unwanted domains (for example in email addresses)
masterpasswdEncrypted password hash for superadmin (see passwd utility to generate hash)
reposarray of third-party repository urls. For details, see Extensions repository section.
tzOverride autodetected client timezone (for embedded systems)
...See PHPPE\Core for all properties
NOTE: this file is only for configuring the framework itself, extension and application configuration located in separate files!

Application configuration

Your application's configuration resides in app/config.php. Your application's init() method will receive it as it's only argument.

Extension configuration

Every extension has it's own configuration file, named vendor/phppe/*/config.php. When initializing, their init() method will get the configuration array.

Configuration files for Extensions (and for Application as well) required to return associative arrays:

1
2
3
4
5
<?php
return [
	"key" => "value",
	//...more properties
];

Security

Unlike any other OpenSource PHP framework, being secure is one of the main concerns of PHPPE. It uses different methods at several levels, which all together provide a decent security for your application.

If you found any leakage in PHPPE Core or PHPPE Pack, please send it to me right away (zoltan.baldasztigmail.com). Thank you!
As with any other frameworks, it can be only secure if you pay attention on your configuration and if you take the recommendations below into consideration. A badly configured webserver will be vurnelable no matter what.

File access

PHPPE has a concept of UNIX style file permissions, and requires that
  • owner is a user who is allowed to modify the code (webmaster or deployment system's dedicated user)
  • group belongs to the webserver that only executes the code, never allowed to modify it
  • others are not allowed to even read directories and files by default

With it's built-in diagnostic feature, PHPPE will check these (and if run with root privilege, also fix issues on it's own).
The file owner can belong to webserver's group, but it strictly forbidden to set webserver as owner.

NOTE: owner and group MUST BE different!

Websever access

Webserver can access the files with group permissions. It's only allowed to read and execute code, except a few directory (namely ".tmp" and "data") that must be writeable, but CGI execution must be disabled in these directories in webserver's configuration.

It's strongly advised to follow webserver's and PHP tickets on CVE, upgrade security patches, and keep all the unused features turned off. Also, webserver configuration should limit vhost's file access to it's ProjectRoot (that is PHP's open_basedir restricted to DocumentRoot/..).

User access

The framework has a built-in access control. Access control entries can be granted to PHPPE\Users instances. Calling actions and displaying view blocks may require specific access entries depending on view templates.
You can group access control entries into role groups and grant them to users at once. See (PHPPE\User)->has().

Templater expressions

Function calls inside view templates can be limited to those listed in PHPPE\Core::$core->allowed array.

Form tokens and validation

PHPPE will generate a unique security token for each and every form. This avoids retransmissions and hand-made POST requests.
It also keeps track of the field types on a form, and upon submit it will call the appropirate validator on each form field's value. If it's not enough (for example you'll need a more complex business logic) you can do further checks in your application's controller.

Form fields access

In templates, you can require access control entries for every field and widgets. If the user does not have the appropriate ACE, it will fall back to displaying the value.

Use AppArmour

PHPPE applications supposed to be run through AppArmour or other similar security module. This is pain in the ass for the first time, but pays out on the long run.


Monitoring

Parsing output

The HTML output format will include statistics in a comment tag, right before the </body> element. Here's a regexp to grab the info from it.
<!-- MONITORING: ([A-Z]+), page ([0-9\.]+) sec, db ([0-9\.]+) sec, server ([0-9\.]+) sec, mem ([0-9\.]+) mb[, mc] --> </body>
MONITORINGmagic to match pattern
 OK - everything was ok
 WARNING - running was ok, but generates more log than necessary
 ERROR - something (like a validator or security check) failed
pagetotal page generation time including database time
dbtime consumed by data source queries
serverwebserver time (framework start time minus http received time)
memmaximum memory peak
mcflag exists if the page was served from cache.
NOTE: these statistics are updated even if the output is otherwise cached.

Configuring syslog

Messages send through PHPPE\Core::log() can be sent to syslog in the form:
(vhost name):PHPPE-(A|W|E|C|I|D)-(extension):(original message without line breaks)
It's easy to grep.

phppe/config.php:

1
2
3
4
//send all messages to syslog, don't write log files.
'syslog' => true,
//also log debug trace along with messages
'trace' = true,

Background Jobs

Running as webmaster

NOTE: PHPPE\Cache\Files' clean up method and the database PHPPE\Email sender are connected to "cron minute" background job.
Use crontab command from SSH Terminal. This will invoke an interactive editor:
$ crontab -e
*    * * * * /usr/bin/php (your DocumentRoot)/index.php --diag
*    * * * * /usr/bin/php (your DocumentRoot)/index.php cron minute
*/15 * * * * /usr/bin/php (your DocumentRoot)/index.php cron quaterly
0    * * * * /usr/bin/php (your DocumentRoot)/index.php cron hourly
5    0 * * * /usr/bin/php (your DocumentRoot)/index.php cron daily
*/20 * * * * /usr/bin/php (your DocumentRoot)/index.php cron myjob1
*/21 * * * * /usr/bin/php (your DocumentRoot)/index.php cron myjob2

It's important that in this case the script will be running with owner's rights. Or, if you're a system administrator, you can use system wide configuration file and specify user

/etc/cron.d/phppe_(vhost name):
*    * * * * (vhost's user name) /usr/bin/php (vhost's DocumentRoot)/index.php cron minute
*/15 * * * * (vhost's user name) /usr/bin/php (vhost's DocumentRoot)/index.php cron quaterly
0    * * * * (vhost's user name) /usr/bin/php (vhost's DocumentRoot)/index.php cron hourly
5    0 * * * (vhost's user name) /usr/bin/php (vhost's DocumentRoot)/index.php cron daily

Example action handlers for these commands can be found in events section. For each job, you'll have to implement cron + ucfirst(jobname) ($arg), like cronMinute($arg) or cronMyjob1($arg).

Running as webserver

You can create background jobs programatically as well with Tools. With this interface the scripts will run with the webserver's rights.

1
2
//! call $myClass->myMethod('cleanup','me') every 5 secs.
\PHPPE\Tools::bg( "myClass", "myMethod", [ "cleanup", "me"  ], 5 /*secs*/ );

Webserver configuration

PHPPE is tested under Apache and nginx webservers. Apache is more common, while nginx has better performance. Both are capable of url rewriting which is a key feature to hide 'index.php' from the url. Here are two examples on how to do it.

Apache example

<VirtualHost *:80> ServerName mysite.tld DocumentRoot "/var/www/mysite/public" <Directory "/var/www/mysite/public"> Options -Indexes +SymLinksIfOwnerMatch +ExecCGI AllowOverride None Order allow,deny Allow from all RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php/$1 </Directory> CustomLog /var/log/apache2/mysite-access.log combined ErrorLog /var/log/apache2/mysite-error.log php_admin_flag register_globals off php_admin_flag allow_url_fopen off php_admin_flag allow_url_include off php_admin_value open_basedir /var/www/mysite php_admin_value upload_tmp_dir /var/www/mysite/.tmp </VirtualHost>

NGINX example

user www-data; server { listen 80 default_server; root /var/www/mysite/public; server_name mysite; location / { try_files $uri $uri/ =404; index index.php; if (!-e $request_filename){ rewrite ^(.*)$ /index.php; } } location ~ \.php$ { fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param REDIRECT_STATUS 200; ## !!! important !!! use $host for catch-all fastcgi_param SERVER_NAME $host; fastcgi_pass unix:/var/run/php-fpm.sock; } }

Running Overview

PHPPE\Core Constructor

The framework has two different running scheme. PHPPE\Core constructor controls which one of these should be called. The $islib boolean variable is set if the framework was included from another php file as a library.

      constructor
           ↓
       autoload
           ↓
(CLI and has "--diag"?)
      y ↙     ↘ n
bootdiag()   Init event
     ↓           ↓
Diag event    ($islib?)
                    ↘ n
                  run()
  • bootdiag() - runs diagnostics mode
  • run() - runs application (controller)

The following files will be processed in order:

public/index.phpcalled by webserver directly or via rewrite mechanism
vendor/phppe/Core/config.phpframework configuration
vendor/autoload.phpif found, PHP Composer autoloader will be included
vendor/phppe/*/init.phpinit scripts for extensions
vendor/phppe/*/config.phpconfigurations for extensions
vendor/phppe/*/lang/?language dictionaries for extensions
vendor/phppe/*/libs/?other libraries referenced by extension's init.php

Initialization event

When normal mode is choosen, the Init event will initialize services.

Run Application

The URL routing mechanism chooses one controller class in an application to run. To do that, PHPPE\Core::run() consult the ClassMap to find the class.

Generating output

Finally templater will be called to generate output for the specified view. For CLI scripts templater is skipped unless you explicitly set a template in the action handler.

Because templater will try to find a view on it's own, it's possible to omit controller for a page entirely. This is very useful for pages like "About Us".

vendor/phppe/*/out/format_header.phpnote that 'html' format has a built-in fallback. This code is responsible for generating the html, head blocks and body tag.
view for 'template'First, the template given in PHPPE\Core::$core->template will be looked up in these places. If that fails, application_action will be used as template name. If even that fails, as a last resolt application's name will be looked for. If neither found, templater will fall back to the 404 template.
view for 'frame'After application's output is generated and PHPPE\Core::$core->noframe is not set, output will be wrapped in this frame template. This is the template that holds page header (some nice, wide image usually), the navigaton bar and the page footer (with copyright notices and sitemap, Impressum etc. links).
vendor/phppe/*/out/format_footer.phpfinally footer is generated. Built-in version for 'html' includes JavaScript libraries here, and calculates values for monitoring comment before the /body tag.

URL Routing

First of all, if PHPPE\Core::$core->maintenance is true, all routing will be bypassed and only maintenance view will be displayed (using maintenance.tpl template). Otherwise the routing decision is made in PHPPE\Core::run().

PHPPE has a concept of application that has several actions, each operating on a specific item(s). With other words, application is a controller class, action is a method in it, and item is it's argument (or more items separated by slash '/'). To explain URL routing, first we'll need an example application with actions. PHPPE gives you the freedom on how to organize your code. You can put all of your application's actions in a single file, or you can create separate files for each action. That's your choice.

app/ctrl/myapp.php
vendor/phppe/MyApp/ctrl/myapp.php:

1
2
3
4
5
6
7
8
9
namespace PHPPE\Ctrl;
class MyController extends MyApp {
    //this action will be called via url routing with specific arguments
    function myspecaction( $arg1, $arg2 = "" ) {}
    //without route() call, arguments will be splitted by directory separator
    function myaction( $item,  $item2 = "" ) {}
    //default action
    function action( $item ) {}
}

app/ctrl/myapp_myaction.php
vendor/phppe/MyApp/ctrl/myaction.php:

1
2
3
4
5
namespace PHPPE\Ctrl;
class MyAppMyAction extends MyApp {
    //if you use separate files, only one action handler defined in each.
    function action( $item ) {}
}

URL matcher routes

Routes are loaded with PHPPE\Http::route() in one of the extensions (or your applicaiton) initialization file, and they will be matched against the url. If url mask does not contain pharenthesis (no arguments), the remaining of the url will splitted at slashes and passed to the method as argument(s). If method is omited, default action route will choose the appropriate method.

Routes has to be placed in app/init.php or vendor/phppe/*/init.php as extension initialization code gets called before the routing decision is made. It's too late to call PHPPE\Core::route() in a controller's constructor.

You can pass any class that can be autoloaded by PHP Composer to PHPPE\Core::route(), meaning the controller can be anywhere under vendor/ directory.

app/init.php
vendor/phppe/MyApp/init.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//plain arguments
PHPPE\Http::route( "myspecurl/([^/]+)/?(.*)", "MyApp", "myspecaction", "post,@admin" );

//json friendly format
PHPPE\Http::route( [
	"url" => "myspecurl/([^/]+)/?(.*)",
	"name" => "MyApp",
	"action" => "myspecaction",
	"filters" => [
		"post",
		"@admin"
	)
] );

//compact format for adding multiple routes at once
PHPPE\Http::route( [
	[ "myspecurl/([^/]+)/?(.*)", "MyApp", "myspecaction", ["post", "@admin"] ],
	[ "myspecurl2/([^/]+)/?(.*)", "MyApp", "myaction", ["loggedin"] ],
] );

//if you specify only the class, default action routing will apply
//the class can be loaded via composer's autoload as well
PHPPE\Http::route( "tests", "\PHPPE\Tests" );

As you may noticed in the examples above you can add filters to routes. If a filter fails, the URL will be automatically skipped. If all routes failed, and there was at least one that because of a filter, the url will be routed to 403 (access denied) page. When no routes matched at all, PHPPE\Core::run() will fall back to default route.

Default route

If no routes given, or neither matches, PHPPE Core will try to find the appropriate class by parsing the url as:
(base) / application [ / action [ / item ] ]

Default application

If classname not specified, application name will be translated to controller classname as follows:
  1. PHPPE\Ctrl\applicationAction
  2. PHPPE\Ctrl\application
  3. PHPPE\application
  4. PHPPE\Content

First PHPPE will check whether the url is handled by an action specific controller. Next, it will check if it's handled by a controller or service class with several action methods. As a last resort fallbacks to PHPPE Content built-in action, which in turn will look for a content in database.

NOTE: if you inherit all controllers from your application's class, then they will share the constructor. This way you can create common code for all actions in an application, even if they are separated.

Default action

After executing controller class constructor, the caching instance will be consulted. If the page can be found in cache, action method won't be called. See caching for details. On the other hand, if it's not in cache, or page generation is forced by setting PHPPE\Core::$core->nocache in constructor, action method will be executed.

If you omit action parameter from URL rule, the action() and default action() methods will be looked up in the specified class.

Fallback action

If no application class found, run() will fallback to act as a Content Server, serving pages from database. Contents are generated on a dedicated server by the PHPPE CMS extension. If a page for the url found, it will populate the application's properties and sets the template to be used in view. In this case action method will provide a way to execute code from database, unless it's turned off by the PHPPE\Core::$core->noctrl flag.

Templater

After an action method returns (either called by matching, default or fallback) the templater takes control over, unlike other frameworks where controller has to call the templater.

Because view is called regardless to controller, PHPPE allows controllerless, view-only pages.

Special URLs

Some urls are handled by the Core itself, mostly for workarounds and security reasons. These are as follows:
/loginLog in user (requires POST arguments). It handles admin user internally and will also call (PHPPE\Users)->login() method if found.
/logoutLog out user and destroy session. It handles admin user internally and will also call (PHPPE\Users)->logout() method if found.
/css, /js, /images, /fontsAssets proxying.
?clearClear ClassMap cache and destroy session, but preserve logged in user
?cache=(hash)Get content from cache
?nojsDisable JavaScript client detection
?lang=(langcode)Override client's language
?app=, action=, item=Override webserver's url rewrite
?--dumpDump environment, only works in developer and debug mode (runlevel 2 and above)
?benchmarkCollect benchmark data for the utility shipped with Developer.

URL Filters

You can add one or more filters to pages and fields on a form, not to mention the menu items. Most common case to apply as fourth argument to PHPPE\Http::route().

If a filter name starts with a '@', the user will be checked against the given ACE, otherwise \PHPPE\Filter\name::filter() will be called that returns a boolean value.

For an ACL list, use pipe to concatenate ACEs (like "@siteeditor|siteadmin"). If you would like to describe user has A and at least one of B or C, that would be "@A,@B|C".

PHPPE Core has four built-in filters:

NOTE the difference of filters "@loggedin" and "loggedin". The former simply returns true/false, the second redirects user to login page and arranges that he/she will be redirected back after successful authentication.

You can find filter examples in the API section.

Models

Model classes are inherited from PHPPE\Model. This way they will have three methods at least:

find()to get a filtered list of objects of the same kind
load()to populate the object with a specific record's data
save()to save object into database record

A typical example of a model is PHPPE\Users class.

Controllers

These classes are the glue that connects HTTP requests with models and a view.

An application (a PHPPE extension) can house several controllers, and each controller can hold code for several (but at least one) action handler.

Using PHPPE\Http::route() you can specify any controller and action to run for a specific url, or you can rely on default route to choose the controller and action method.

Controllers are located under the ctrl/ directory of every extension (and in your application as well). PHPPE gives you freedom on how you want to organize them, you can have one single controller class with more action handlers, or separate classes for each action. There's no inheritance involved, but controllers should reside in the PHPPE\Ctrl namespace.

NOTE: as PHPPE is very flexible, it allows you to specify any class as controller, it's strongly recommended to use the PHPPE\Ctrl namespace for code readability.
vendor/phppe/MyExtension/init.php:

1
\PHPPE\Http::route( "myurl", "MyController", "action" );

vendor/phppe/MyExtension/ctrl/mycontroller.php:

1
2
3
4
5
namespace PHPPE\Ctrl;
class MyController {
    //default action
    function action( $item ) {}
}

HINT: you can pass more arguments to an action handler method by using regular expressions when calling PHPPE\Http::route().

Views

Unlike other frameworks, in PHPPE the templater is not used to generate the entire output. Instead you've library calls to add links, meta tags, menus, stylesheets and javascripts to it. The templater is limited to the area between <body> and </body> only. This way PHPPE can manage asset aggregation and minification transparently.

Template used

View layer is omited for CLI commmands, unless a template name is explicitly set.

The view is generated using the first template found in order:

  1. PHPPE\Core::$core->template (unless application overwrites it, it defaults to application_action)
  2. application_action
  3. application
  4. 404

If the application clears the template's name, the view layer will not be used at all, and therefore there'll be no 404 page either.

Template lookup

Each of the above list will be looked up in:

views table id='template'if datasource available, views table will be checked.
app/views/template.tplif no datasource connected or the template is not found in views table, application's directory will be checked. This way your application can override any view templates shipped with extensions.
vendor/phppe/application/views/template.tplfinally the template will be searched for in corresponding extension's directory.
HINT: with database views you can add extra, template specific meta tags and stylesheets to the output.

Using the templater

Iteration

<!foreach array>
 ...
<!/foreach>

Repeats xml block array length times. If array elements are instances or associative arrays, their fields can be referred on each interation. On scalar arrays, you may want <!=VALUE>. Also the index of the array available as <!=KEY>. For non-associative arrays, <!=IDX>, the iteration counter is one bigger than KEY. See expression tag below.

Conditional

<!if expression>
 ...
[ <!else>
 ... ]
<!/if>

Conditional blocks. First block is parsed if the expression is true, the second block otherwise (if exists). A special expression "cms" can be used here which evaluates to true only if page is viewed in Content Editor. With this you can configure otherwise hidden page properties in Editor.

Include

<!include view>

Insert another template's content into current one just as if it were entered here.

Re-entrance

<!template>
 ... <% ... > ...
<!/template>

Blocks surronded by the template tag interpreted twice. On first run <!, and <% on second. This can be used to generate template tags with templater.

You can nest and mix iterations and conditional blocks as you like.

Object reference

<!form objectname
  [ cssclass
    [ app/action
      [ onsubmitjs ] ] ]
>
 ...
</form>

Starts a form for an object. There's no closing tag, use the usual </form> (without !). Inside a form, you can refer to it's fields with <!=>. Also, every addon can load and save it's properties.
Object can be either an instance or an associative array. If neither application/action url nor onsubmitjs specified, it will post the form to the same url as it was generated from.

Date/Time formating

Human readable format and time units are specified in translations (available in PHPPE Pack). Without that these tags will fallback to ISO date format (YYYY-mm-dd HH:MM:SS) and English units (sec, min, hour, day etc.).

<!date expression>

Display an integer (UTC UNIX timestamp) as a human readable, localized date.

<!time expression>

Similar to <!date>, but also displays time.

<!difftime expression>
<!difftime tstamp1 tstamp2>

Display the integer as difference to dates.

Localization

<!L textkey>

Translate a string to the browser's language. This is equivalent of calling L() in an expression: <!=L(textkey, ...)>

Misc. tags

<!dump field>

Converts object or a field to a human readble string and displays it along with it's name.
This only works if runlevel is at least 1 (verbose or testing). These level use print_r() for dumping, higher levels (developer and debug) use var_dump().


<!app>

This tag is unique. It's limited to sitebuilds at one position only.

Expressions

//variable, expression or object field.
<!=expression>

No php allowed in expressions; you have to convert both property names ($obj->field) and associative arrays ($obj["field"]) to obj.field. Also note that there's no "greater than" operator, if you plan to use, swap sides of the expression and use "less than" operator. Besides of these it works like php, you can use number_format() and sprintf() functions as well (<!=sprintf("%12d",obj.numval*2+10)>, <!=myobject.mymethod(myobject.field3)>).

Inside <foreach> iteration blocks you can use a couple special variables as well:

In nested iterations you can refer to outer array's fields by prefixing with "parent.". Also, "parent.KEY" and "parent.IDX" will work as expected.

Calling PHPPE Core library would require the prefix "core.", like

<!=(core.isinst("MyAddOn") && MyAddOn.mymethod())>
<!=core.getval("core.lib()")>
You can limit the functions if PHPPE\Core::$core->allowed array is set: only functions listed there will work, save core functions and L() which are always available. Otherwise templater will generate an E-BADFNC error if referenced function is not listed.

You can access the framework object's properties and methods with

PHPPE\Core::$corecore.PHPPE Core
PHPPE\Core::$useruser.Logged in user
PHPPE\Core::$clientclient.Client's (browser's) properties
Controller instanceapp.Your application's controller class. Properties can be accessed without prefix as well

Templater Add-Ons

Both field types and widgets are extended from PHPPE\AddOn class. The difference is how you refer to them in view templates.

//create an add-on instance for a form field.
<!field [ @acl ] [*]addon [ ( args ) ] objectname.fieldname [ attributes ]>
<!var [ @acl ] [*]addon [ ( args ) ] objectname.fieldname [ attributes ]>
<!widget [ @acl ] [*]addon [ ( args ) ] instancename [ attributes ]>
<!cms [ @acl ] [*]addon [ ( args ) ] app.propname [ attributes ]>

The reason for having four different kind of tags is to ease the creation for templates. For example with "var" you can display and edit a database record with a single template, so you won't have to create more templates for the same object. Similarly "widget" can be used to place an AddOn on the page regardless whether it's showing it's face or it's configuration. Both will use the same template.

The "field" tag will call the edit() method of the Add-On to render html content, regadless if there's a show() method or not. If you use "var" instead of "field", the show() hook will be called, edit() will be limited to edit mode only ($_SESSION['pe_e'] is true). If edit() hook is undefined, fallbacks to show(). If neither show() method found, "var" will output raw value, while "field" reports an error.

Widgets works just like "var", show() method should draw it's face, and edit() method displays a widget configuration form (optional, if $_SESSION['pe_c'] is true).
Technically "var" and "widget" are the same, the difference is limited to the meaning of what edit() renders, and when edit() gets called instead of show().

The "cms" tag will use edit() only if page opened in Content Editor. Normally it won't display anything unless it's marked mandatory (by an asterisk prefix in type), when it will use show(), just like "var". Also you can specify editor mode only templater blocks with "<!if cms>...<!/if>", see conditional block above. The "cms" tag is special in a way as it accepts extra dimension parameters to help intergation of the editor area into your layout more smoothly.

//autodetect editor position and size
<!cms
//place editor at icon in given size (pixels)
<!cms(width,height)
//display editor as a modal occupying given percentage of the screen
<!cms(0,0,percentage)

Comparision

fieldvarwidgetcmsPurpose
Normaledit()show()show()- / show()Display a record with formated values to the user, also showing widget faces.
Editedit()edit()show()- / show()User editable form for a record with the same view template.
Confedit()show()edit()- / show()Widget configuration.
Cmsedit()show()show()edit()Editing content on the page.

It's easy to expand functionality with new Add-Ons, see tutorial for an example.

Cache Management

Caching is configured in phppe/config.php:

cachememcache location, "(hostname)[:(port)]" or "unix:(path)" or "apc" or "files". Extensions can provide other caching mechanisms.
nocacheSet it to true to disable caching of view output and assets (templates will be still cached).
cachettlView output and asset cache time to live in sec (defaults to 10 minutes)
noaggrset it to true to turn static css and js content aggregation off in html header
nominifyset it to true to turn css and js minifier off

If memcached (or any other cache alternative) can be accessed, raw templates will be always cached, and there's no flag to turn that off.
Without nocache set, templater's output and assets will be also cached for 10 minutes.

Assets added by either PHPPE\View::css() or PHPPE\View::jslib() will be aggregated into two files in memory to save HTTP requests as well.

If a page is cached, only the application's constructor will be called, but NOT the action handler.

If you want to support changing content along with dynamic data, your application must be aware. You should look for new data in your app's constructor, and set PHPPE\Core::$core->nocache to avoid view's caching and force regenerating the output.

Example

phppe/config.php:

1
2
3
'cache' => "127.0.0.1:11211",		//set up memcache connection
'nocache' => false,			//allow caching of generated output by default
'noaggr' => true,			//turn static content aggregation off

app/ctrl/myaction.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace PHPPE\Ctrl;
//your application's controller class
class MyAppController extends MyApp {
	public $data;

	//in constructor look for new data
	function __construct( ) {
		//if new data available, disable caching of output
		if( MyModel::getNumberOfNewItems() > 0 )
			PHPPE\Core::$core->nocache = true;
	}

	//WARNING: actions not called when output is cached!
	function action( $item ) {
		$this->data = MyModel::getItems();
		//when returning from this method, templater will
		//regenerate output using $data and raw templates
		//and will save the result into the cache
	}
}

Alternatives

You can use any other caching mechanism, if the extension is in PHPPE\Cache namespace, and has at least two methods:

1
2
function get( $key ) { }
function set( $key, $value, $compressed = true, $ttlinsec = 0 ) { }

Zero ttl means keep key in cache forever.
If the caching mechanism requires connection, it must be initialized in the constructor, using PHPPE\Core::$core->cache as argument.

PHPPE Pack ships APC(u) and Files cache extensions. If you decide to use local files, you'll also need the garbage colletor, which hooked to the "minute" background job:
* * * * * php public/index.php cron minute

Programming

There's a wrapper class to hide all the details of cache implementations, see PHPPE\Cache.

Clustering

On workers, install Pack and ClusterCli. On the management server, Pack, ClusterSrv and CMS.

You have to set up background jobs properly in order to get Cluster working.

Installation

Management nodes

$ composer require phppe/ClusterSrv

Setup

The cron minute will supervise if ClusterSrv is running. You can also trigger it from command line:

$ php public/index.php cluster server

Query status

That's simple as

$ php public/index.php cluster
This node is: 10.0.0.1 MASTER
Cluster nodes:
 Id              Type        Load  Last seen            Last viewed          Name
 --------------  ---------  -----  -------------------  -------------------  -----------------------------
*10.0.0.1        master         0  2016-09-28 02:06:20  2016-09-30 01:04:42  admin1.localdomain
 10.0.0.2        slave          0  2016-09-28 02:06:20  2016-09-30 01:04:42  admin2.localdomain
 10.0.0.3        lb             0  2016-09-28 02:06:20  2016-09-30 01:04:42  ns1.localdomain
 10.0.0.4        worker         0  2016-09-28 02:06:20  2016-09-30 01:04:42  worker4.localdomain
 10.0.0.5        worker         0  2016-09-28 02:06:20  2016-09-30 01:04:42  worker5.localdomain
 10.0.0.6        worker         0  2016-09-28 02:06:20  2016-09-30 01:04:42  worker6.localdomain

cluster help      - this help
cluster status    - prints the current status
cluster server    - checks management daemon (called from cron)
cluster takeover  - force this management server to became master
cluster flush     - flush worker cache
cluster deploy    - push code to workers
cluster client    - (!) checks worker daemon (called from cron)
cluster bindcfg   - (!) generate bind configuration on lb worker

Load balancer

$ composer require phppe/ClusterCli

Setup

The cron minute will supervise if ClusterCli is running. You can also trigger it from command line:

$ php public/index.php cluster client

Tell ClusterCli that you want it to work as a load balancer by setting the name(s) of the subdomain(s) in

vendor/phppe/ClusterCli/config.php:

1
  'loadbalancer' => [ 'www', 'cdn' ],

Create a shell script under vendor/bin/cluster_lb.sh. That will receive a bind configuration file on it's stdin, and has to save it and restart a bind server of your choosing. The format can be modified in vendor/phppe/ClusterCli/views/bind.tpl. If possible you should use layer 2 balancing (like RFC 5798) instead of a DNS balancer for smaller latency times.

Query status

$ php public/index.php cluster
This node is: 10.0.0.3 LOADBALANCER
$ php public/source.php cluster bindcfg
; GENERATED FILE, do not edit!
$TTL	1d ; 1 day ttl
$ORIGIN example.com.
@  1D  IN  SOA ns1.example.com. hostmaster.example.com. (
			      2016093003 ; serial (unique to every hour)
			      5m ; refresh
			      15 ; retry
			      5m ; expire
			      5m ; nxdomain ttl
			     )
        IN  NS     ns1.example.com. ; in the domain
        IN  NS     ns2.example.com. ; external to domain
        IN  MX  10 mail.another.com. ; external mail provider
; server host definitions
ns1     IN  A      127.0.0.3    ; dns load balancer
admin   IN  CNAME  admin2
admin2  IN  A      127.0.0.1    ; current master
admin3  IN  A      127.0.0.2    ; standby slave
www     IN  A      127.0.0.4    ; worker #4
www     IN  A      127.0.0.5    ; worker #5
www     IN  A      127.0.0.6    ; worker #6

Worker

$ composer require phppe/ClusterCli

Setup

The cron minute will supervise if ClusterCli is running. You can also trigger it from command line:

$ php public/index.php cluster client

Query status

$ php public/index.php cluster
This node is: 10.0.0.6 WORKER
That's all.

Automatic scaling

ClusterSrv will monitor for worker loads and will tell them to stop down or start a new if required. For that to happen, we'll have to do things first:

On workers

Make sure sudo is allowed for the following commands: poweroff, restart.

Make sure (openbase_dir) script is allowed to open and read /proc/loadavg.

On management node

Create a shell script vendor/bin/cluster_worker.sh, that should communicate with your VPS provider's API to start a new worker vm instance.

High Availability

You can start more load balancers, but change the name in bind.tpl for each.

As for management, ClusterSrv implements fail-over. That means that you can start more instances, but only one can be master at any given time. There may be a need to change master manually, for that run this command on one of the slave servers:

$ php public/index.php cluster takeover

Content Distribution

Contents are stored in the quorum database server. Workers cache the resulting html for subsequent use. That cache can be cleared with:

$ php public/index.php cluster refresh

Code Distribution

You can keep a master copy of your worker's document root on the management server. If you change that, you'll have to send it to the workers with:

$ php public/index.php cluster deploy
It's called push deployment, which is done by rsync over a secure channel. The reason why PHPPE prefers push over poll is performance and security: you can set up one-direction firewalls and if any of your workers get hacked, there's no way to the management zone from there. If one or more worker gets defaced, you can use this command to restore them to their original state.

DataBase AdministratorDBA

Provides a low level web interface to the database.

You can list tables, search and edit records with it. It's purpose is not to be a fully featured tool rather to be a swiss-army knife for S.O.S. fixes in the database.

You need siteadm ACE to use the DataBase Administator.

Services

Every extension can have an init.php that holds initilization code. If that file returns an object instance, that's called a Service.
Services provide library functions to PHPPE, or can extend functionality by implementing event hooks.

If classname is not obvious, other parts of the system can ask for a service by calling PHPPE\Core::lib(servicename) which will return the instance.

HINT: you can use the "create" utility in PHPPE Developer to generate your extension along with init.php
vendor/phppe/MyExtension/init.php:

1
2
//! return a class defined under Extension's libs/ directory
return MyService();

Sitebuilds

The frame concept

There's a special template in PHPPE, that can have an <!app> tag. This tag separates the page header (logo, header image etc.) from the page footer (copyright notice, impressum link etc.). So minimal frame is this tag alone. If you omit this tag, your application's output will not be included in the final output.

menu
(Panel)
status dock

page header
(Frame)
<!app>

page footer

This frame usually describes the basic layout of the website: where's the menu, where's the navigation bar, where are the ads etc.

For AJAX loaded applications and popup windows you can turn the panel and the frame off, leaving the entire space to the application (full screen mode if you like) with

1
2
PHPPE\Core::$core->nopanel = true;
PHPPE\Core::$core->noframe = true;

Layouts

In the views table of the database there are special records marked by a site build name. These are the layouts that can be used as frame, one at a time. All of them must contain an <!app> tag.

With PHPPE CMS you can manage views on a nice web interface, and you can select one sitebuild to be used as frame with a single click.

Assets

Along with code, usually come some static file. They can be CSS stylesheets, imagery or client side JavaScript libraries. You don't have to mess them up in a single directory under "public/", PHPPE Core is clever enough to locate these files and proxies their contents. For that, the following URLs are reserved:

Each of these has a corresponding directory within every PHPPE extension. Your application's assets are handled defferently, as they are symlinked under "public/". This means that the static contents of your application will be served by the webserver directly without PHP involved. Extension's assets (located under "vendor/" which is not accessible by the webserver) and dynamicaly generated assets have to be proxied through PHP.

If caching is configured, and PHPPE\Core::$core->nocache is not set, the assets will be stored in memcache, so there'll be no directory lookup and file read penalty for subsequent downloads.

Adding assets to output

For that there are two corresponding methods to add links to output's head section and libraries before the body tag:

These should be called in init() method of Add-Ons or action handler classes. Don't call them in service intitialization, as the referenced assets would be included in every output even if they're not needed at all.

NOTE that you only pass the filename to these methods, the path will be looked up automatically depending on the caller's location. This means that every extension can add only assets shipped with it, but not the other's.

One subdirectory is supported for grouping assets, but only one level for performance (to speed up directory lookups).

Dynamic Assets

It's possible to generate assets on-the-fly. For that you'll have to append ".php" extension to the filename. When you refer these, do that without that extension, PHPPE is clever enough to handle them on it's own.

NOTE: never add .php extension in urls for dynamic assets!
js/example1.jsPHPPE\View::jslib("example1.js");
js/example2.js.phpPHPPE\View::jslib("example2.js");
images/example1.pnglink as "images/example1.png"
images/example2.png.phplink as "images/example2.png"

This way the output looks the same for both static and generated assets.

You don't have to do anything special in these php files, when they are included, PHPPE is already bootsrapped for you, so you can focus on generating the output. If cache is configured, by default the generated asset will be cached and served like any other static asset. If you want to disable that behaviour (for example you're generating an image with a changing counter on it), set the PHPPE\Core::$core->nocache flag and your code will be called every time the asset queried.

Contents

Content Server

PHPPE Core can serve contents (generated by PHPPE CMS) on it's own, but it uses a database table for that. The scheme is supplied by PHPPE Pack (vendor/phppe/Core/sql/pages.sql).

PHPPE\Content will look for pages if routing was unable to choose a controller.

Content EditorPHPPE CMS

To be able to create and modify contents on a Content Server site, you'll need to install PHPPE CMS extension by copy'n'pasting one of these commands into your Terminal:

composer require "phppe/CMS"
mkdir vendor/phppe/CMS && curl https://bztsrc.github.io/phppe3/phppe3_cms.tgz | tar -xz -C vendor/phppe/CMS

It can be installed on a different server, and one CMS can manage several Content Servers. This way you can build up a scalable architecture that can serve huge loads with ease.

Typical, non-scalable, not so secure set up

visitors  webmaster  (Browser Clients)
     \     /
  websrv+dbsrv       (Content Server,
      \___/           Content Editor,
                      and database as well
                      on a single machine)

Scalable set up

           visitors        webmaster   (Browser Clients)
              |                |
      DNS load balancer        |
        /     |     \          |
       /      |      \         |
websrv1  websrv2 ... websrvN   |       (Content Servers)
       \      |      /       cmssrv    (Content Editor)
        \__   _   __/         /
             (_)  ___________/
      dbsrv  |_|                       (database server)

In scalable set up the webmaster (who is responsible for the contents) uses the cmssrv with Content Editor installed to manage the webpage. This should be done through a secure https channel protected by a firewall, so that nobody else could modify the contents. The Content Editor stores the changes to the database that resides on dbsrv over a sql connection. In the cheapest scenario database server and Content Editor can be installed on the same machine (although not recommended due to security considerations. Database server should not be accessible from outside).

Visitors are accessing the site on http(s) protocol targeting a load balancer. The load balancer forwards the http(s) request to one of the websrv instances that run Apache or nginx. The cheapest and simpliest solution is to use DNS load balancing, where the domain name resolves to several A records, each pointing to one of the websrv ip's. More professional load balancers can terminate ssl and forward plain http to save resources, and may also monitor the current load on websrv instances to route to the least loaded one.

All the websrv instances have the same document root and run PHPPE Core to act as a Content Server that in turn loads the content from dbsrv via sql connection. To speed up things ever more, Content Servers can cache the contents as well.

NOTE: Don't forget that in this scenario you'll have to configure php to store session info in database instead of files, otherwise strange things will happen!
NOTE: Also you should set up syslog on webservers to proxy the messages to a central syslog server.

EplosCMS

There's also an extension to support pages generated by third party software. EplosCMS generates pre-constructed pages in the "pages/" directory, this extension read files from there and creates routing entries as they are referenced. You can install this extension with:

composer require "phppe/EplosCMS"
mkdir vendor/phppe/EplosCMS && curl https://bztsrc.github.io/phppe3/phppe3_eploscms.tgz | tar -xz -C vendor/phppe/EplosCMS

Event hooks

In PHPPE you don't set handlers by calling a method. Instead, you simply name your method after the event in your registered class.

Overview

Service events:init($cfg), diag(), route($app,$action), ctrl($app,$action), view($html), stat(), cron()
Filtering event:filter()
Add-On events:init(), validate($name,$value,$required,$args,$attribs), show(), edit()
JavaScript event:init()

Service initialization event

Definedin extension classes returned by init.php
Calledduring the bootstrap process in PHPPE\Core constructor.
Purposeregister libraries, menus and other stuff needed by extensions

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Service init event handler
 *
 * @param config associative array
 * @return boolean, was initialization successful?
 */
function init($config)
{
	// PHPPE\View::menu() to add menu items
	// PHPPE\View::jslib() to add JavaScript libraries
	// PHPPE\View::css() to add custom stylesheets
	return true;
}

IMPORTANT: if your service unable to work (missing php extension prephaps), return false. This will exclude your service from PHPPE\Core::lib()'s list.

Extension diagnose event

Definedin extension classes returned by init.php
Calledin diagnostics mode (when called from command line with "--diag" argument)
Purposedo checks on extension's data integrity and consistency

1
2
3
4
5
6
7
8
/**
 * Extension diagnose event handler
 *
 */
function diag()
{
	echo("Hi\n");
}

Routing filter event

Definedunder libs/, but outside of your extension's namespace, so use a separate file for filters.
Calledif the url (for which the response is being generated) matches a routing rule with filter in it, the appropriate function will be called.
Purposerestrict access for specific urls.

1
2
3
4
5
6
7
namespace \PHPPE\Filter;
class myFilter extends \PHPPE\Filter {
    function filter()
    {
	  return true;
    }
}

Route event

Definedin any service extension.
Calledafter routing decision is made, but controller not instantiated yet.
Purposealter application's class and action method's name, expand routing capabilities.

1
2
3
4
5
6
7
8
9
10
11
/**
 * Route event handler
 *
 * @param application classname
 * @param action method name
 * @return array optionally altered input
 */
function route($app, $method)
{
	return [ $app, $method ];
}

Controller action event

Definedin any service extension.
Calledbefore action handler gets called (NOTE: only if caching is disabled for the page).
Purposeexpand controller functionality with application independent action code.

1
2
3
4
5
6
7
8
9
/**
 * Controller event handler
 *
 * @param application classname
 * @param action method name
 */
function ctrl($app, $method)
{
}

NOTE the differences between route and controller events:
  1. Route event handler may alter application and action, whilst controller can't.
  2. Route events are fired regardless to caching, controller events only when page is NOT served from cache.

View event

Definedin any service extension.
Calledbefore view is sent to the browser.
Purposealter html output before it's displayed.

1
2
3
4
5
6
7
8
9
10
/**
 * View event handler
 *
 * @param string html
 * @return optionally altered html
 */
function view($html)
{
	return $html;
}

Stat event

Definedin any service extension.
Calledwhen PE Panel is generated.
Purposereturn html to display status icon of the service on the panel.

1
2
3
4
5
6
7
8
9
/**
 * Stat event handler
 *
 * @return html
 */
function stat()
{
	return "<img src='images/icon_ok.png'>";
}

Cron events

Definedin any service extension.
Calledfrom crontab, see background jobs.
Purposerun a scheduled background job.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class myExtension extends \PHPPE\Ctrl {

    function cronMinute( $item ) {
	//here you can use the usual PHPPE environment. Don't forget to output text, not html.
	echo( L("Called.") . "\n" );
    }

    function cronQuaterly( $item ) {
	//do something here
	echo ( $item . "\n" );
	//most scripts will exit without calling the templater.
    }

    function cronHourly( $item ) {
	//if you want templater to continue, set a template manually in CLI scripts
	PHPPE\Core::$core->template = "cron_ncursestmpl";
    }
}

Add-On initialization event

Definedin PHPPE\AddOn classes
Calledonce per page generation, before output is displayed.
Purposeregister JavaScript libraries, stylesheets etc. needed by the Add-On

1
2
3
4
5
6
7
8
9
10
/**
 * Add-On init event handler
 *
 */
function init()
{
	// PHPPE\View::addon() to register Add-On
	// PHPPE\View::jslib() to add JavaScript libraries
	// PHPPE\View::css() to add custom stylesheets
}

NOTE Note that Add-On class' constructor can be called several times, but init event only fired once per kind.

Add-On input validation event

Definedin PHPPE\AddOn classes
Calledwhen PHPPE\Core::req2arr() called in an action handler.
Purposevalidate or sanitize user input. You can chain more Add-On validators on one input field with PHPPE\Core::validate().
NOTE: it's static!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * Add-On validation event handler
 *
 * @param string field name
 * @param mixed value passed by reference
 * @param boolean required
 * @param array arguments
 * @param array attributes
 * @return array of boolean and string
 */
static function validate ( $name, &$value, $required, $arguments, $attributes )
{
	return [
		true, /*boolean, expression to determine whether the value is valid*/
		"success" /*error message if any*/
	];
}

Add-On display events

Definedin PHPPE\AddOn classes
Calledevery time whenever a template refers to that Add-On
Purposereturn html content

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * Add-On display event handlers
 *
 */
function show()
{
	// display value or widget face
	return htmlspecialchars($this->value);
}
function edit()
{
	// display user input field or widget configuration form
	return "<input type='hidden' name='".$this->fld."' value='".htmlspecialchars($this->value)."'>";
}

NOTE that templater tags and user session influence which one of these gets called, see templater for details.

JavaScript init event

Definedas a second argument to PHPPE\View::jslib() calls, usually (library).init().
Calledon client side as soon as the DOM can be manipulated
Purposedo JavaScript stuff that has to be done after the page and JavaScript libraries loaded.
This is a pure client side event. No server side PHP involved. If you want that, use a dynamic asset as a first argument to PHPPE\View::jslib().

Extensions

Installing an Extension without Manager or Composer

Assuming you're in your ProjectRoot. If not, type "cd (your DocumentRoot without public)". Check current working directory with "pwd".

Type one of these into your SSH Terminal:

$ mkdir vendor/(extension name) && curl (repository)/(extension tarball).tgz | tar -xz -C vendor/(extension name)

Query list of installed Extensions without Manager or Composer

Type into your SSH Terminal:

find vendor/phppe -name composer.json -print -exec sh -c "cat {} | grep -e \\\"version[^_]" \;

Depending on your shell, it's possible you'll have to replace | with \|.

PHPPE Extensions ManagerPHPPE Extensions

The PHPPE Extensions Manager is a tool to install extensions over a web interface. It does not provide the same functionality as PHP Composer, so it's not recommended in development environments. Instead it's a comfortable and secure way to deploy tarballs remotely via web UI.

You need install ACE to use the Extensions Manager.

To install, copy'n'paste one of these into your SSH Terminal:

composer require "phppe/Extensions"
mkdir vendor/phppe/Extensions && curl https://bztsrc.github.io/phppe3/phppe3_extensions.tgz | tar -xz -C vendor/phppe/Extensions
php public/index.php --self-update

Configuring Manager

User login and Remote Access

If you have PHPPE\Users class installed with PHPPE Pack, there's little left to do, just grant "install" ACE to your user, and add "remote" configuration as an array to user's preferences. On the other hand if you're running a single user page without Users class, you'll need this to allow admin user login:

phppe/config.php:

1
'masterpasswd' => password_hash("changeme", PASSWORD_BCRYPT),

Don't ever use password_hash() in config.php (this is just an example), paste only the hash constant there. You can use the "passwd" utility to get the hash.

In lack of user preferences, you'll also need

vendor/phppe/Extensions/config.php:

1
2
3
4
5
6
7
return [
	"host" => "(your server name)",
	"port" => "(your ssh port, optional)",
	"user" => "(your username)",
	"path" => "(your ProjectRoot, DocumentRoot without 'public')",
	"identity" => "(your private key)"
];

For example:

1
2
3
4
5
6
7
8
9
10
11
return [
	"host" => "localhost",
	"port" => 22,
	"user" => "bzt",
	"path" => "/var/www/phppe3_test",
	"identity" => "-----BEGIN RSA PRIVATE KEY-----
MIIBdgIBABKBhQCdlCD+2At47+LwSk7w1rDISnj3n9QYjCKjzuj+UtI5S4vC0Dbx
     [...]
iCa3nxaMkW2qEmEPCd8=
-----END RSA PRIVATE KEY-----"
];

The path can be relative as well. Most common configurations are:

This array will be loaded to PHPPE\Core::$user->data['remote'] and handled afterwards as if it would be a normal Users preference setting.

User private key

For security reasons, users are identified by keys. No password configured ever for remote access.

The identity field contains your private key, and you have to append it's public key pair to (user's home)/.ssh/authorized_keys. If you don't have a key yet, here's how to generate it:

$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/bzt/.ssh/id_rsa): Enter
Enter passphrase (empty for no passphrase): Enter
Enter same passphrase again: Enter
Your identification has been saved in /home/bzt/.ssh/id_rsa.
Your public key has been saved in /home/bzt/.ssh/id_rsa.pub.
The key fingerprint is:
6f:16:8a:27:54:cf:2e:6d:37:23:30:fc:4d:d7:a2:12 bzt@localhost

Now copy the content of /home/bzt/.ssh/id_dsa to configuration, and append /home/bzt/.ssh/id_rsa.pub to authorized_keys:

$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

PHPPE Extensions Manager is capable of managing remote hosts as well, but in this case you'll have to append your key to the remote machine's authorized_keys file.

$ cat ~/.ssh/id_rsa.pub | ssh host -p port -l user 'cat >> ~/.ssh/authorized_keys'

Official repository

Adding third-party repositories

By default, PHPPE uses it's own repository, but you can add third-party extensions as well. You can configure repositories like

phppe/config.php:

1
2
3
4
'repos' => [
	"http://my.repo.com/phppe3",
	"http://another.repo.com/stuff/phppe3"
];

To provide your own repository, see Repository HOW-TO.

Usage

Now log in to your PHPPE site. You'll see an "Extensions" menu on the PHPPE Panel. Here you can

Screenshot

PHPPE Extensions Manager

Extensions Repository HOW-TO

You'll need a plain simple webserver for that. Place the tarballs and the packages.json in DocumentRoot, and you're done.

NOTE: you can use the "mkrepo" utility in PHP Developer to create packages.json and tarballs out of your /vendor/phppe/ directory.

Extension tarballs

Create gzipped tarballs with ".tgz" file extension of your extensions and add-ons. Omit leading directory, tar files should contain files relative to vendor/phppe/(extension)/.

Extension meta info

composer.json

The first file in the tarball *MUST* be composer.json. PHPPE Extensions Manager will check it and refuses to install tarballs without valid meta info. This file is a standard PHP Composer json file, with additional elements:

{
	"name" => name of your extension,
	"version" => human readable version of your extension,
	"version_normalized" => comparable version of your extension,
	"description" => description of your extension,
	"maintaner" => your name and email address,
	"license" => license you use for your extension, defaults to LGPL-3.0+,
	"type" => "library"
	"autoload" => autoload info
	"dist" =>
		"type" => "tar"
		"url" => link to the tarball
	"require" => dependencies

	"conf" => optional, configuration specification,
	"conf_X" => optional, translations for configuration keys,
	"price" => optional, integer price of your extension in EUR,
	"homepage" => optional, webshop's url where the user can buy your extension,
	"prio" => optional, priority when generating list in packages.json

}

The keys "name", "description" and "conf" can be translated by appending underscore and language code to them, like

{
	"name" => "phppe/something",
	"name_en" => "Some Extension",
	"name_hu" => "Valami Bővítmény",
	"description" => "English description, NOTE: NO _en suffix",
	"description_hu" => "Magyar nyelvű leírás",
	"conf_en" => "{ "host":"Host name", "port":"SSH Port" },
	"conf_hu" => "{ "host":"Szerver neve", "port":"SSH Port" },

}

Extension configuration

The configuration prototype (keys of the array returned by it's config.php) can be described in "conf" field. Labels for them will be translated, see above.

For example:

	"conf":{
		"host": "string(localhost)",
		"port": "integer(1,65535,22)",
		"user": "string(username)",
		"identity": "string",
		"path": "sting(/var/www/localhost)"
	},
	"conf_en":{
		"host": "Hostname",
		"port": "SSH Port",
		"user": "Username",
		"identity": "Identity file",
		"path": "ProjectRoot"
	},

Extension preview

preview

This should be a 96 x 96 png picture or an svg image. Try to keep it small, below 8k. Use color palette or grayscale instead of RGB. Always use png tools such as TinyPNG to compress.

Preview picture is optional, if not included, default extension picture will be shown.

Versioning

Versions should be threefold, each number represents specific version in order

  1. main. You should be library compatible within main versions
  2. feature. Indicates feature release within main version
  3. patch. Indicates patch level

For example:

0.0.0 - first beta release
0.1.0 - second version of beta release
1.0.0 - first stable release, usable for production
1.0.1 - first stable release with a security patch applied
2.1.3 - second main version with a feature release and three security patches

Upcoming feature and main releases required to include former security patches.

Repository index

packages.json:
{
    "packages" =>
	(extension) =>
		(version_normalized) =>
				content of composer.json
				"time" => last modification time in "yyyy-mm-dd HH:MM:SS" format
				"size" => size of the tarball in bytes
				"sha1" => hash of the tarball
				"preview" => optional, base64 encoded png or svg icon, generated from preview file

	(extension) =>
		(version_normalized) =>
				content of composer.json

	... more extension info may follow

}

Commertial Extensions

PHPPE does not include a shop for Extensions to avoid legal issues and taxing regulations in different countries.
PHPPE Extensions Manager merely helps commertial extensions with a token in download url during installation, but that's all.

If you specify "price" in your composer.json, then "homepage" must point to a webshop. PHPPE Extensions Manager will handle extensions with price like:

NOTE that PHPPE does not specify whether you allow multiple downloads with a specific token or just one. It's up to you.

Coding Standards

To be extremely small (90k is not a plenty of space for a PHP code you can take my word on that), the Core is compressed. With this only exception, all other PHP files must follow PSR-2 Coding Style.

NOTE that PHPPE Developer ships a nice little tool that formats sources to PSR-2.

Comments and logging

It is strongly advised to write plenty of descriptive comments in your source. It is also highly recommended to put a lot of debug level PHPPE\Core::log() call, so that you'll get a clue what is your code doing when it's running.

Definitions

This document uses these notations consistently:

class::static property or method
(class)->property or method of an instance of "class"

Class names

Class names follow StudyCaps. To clearly distinguish Add-Ons from other classes, they're in camelCaps, like "myWidget". Don't ever use underscore in your class names.

Method names

Method names are in camelCase. Again, don't use underscores in your method names.

Functions

Along with classes and methods, PHPPE Core also provides two widely used functions outside of it's namespace (which by the way are only wrappers around PHPPE properties and method calls) for simplicity and to save you typing:

The last two are often called in view templates.

Application or Extension?

As the framework allows controllers in applications (app) as well as in extensions (vendor/phppe/*), you may ask which one to use for your project?
Short answer: application. If you later on plan to use your code on several sites, it's easy to convert it into an extension as they share the same directory structure. The only difference is that an extension must be inside PHPPE namespace, an application is not limited by that.

Benchmarks

PHPPE performance is a very important thing. Therefore you'll find a built-in benchmarker.

NOTE: only public/source.php supports benchmarking! The minified public/index.php version is for production, where unwanted large files could lead to a DoS attack.

Accumulate data

Append "?benchmark" to the page's url you want to measure. Reload it several times, more samples are the merrier.

Displaying the results

PHPPE Developer extension ships a nice web interface to aggregate and display the collected data. If everything went well, you should see something similar to this:

Rundown

Name Start Time consumed
phppatch 0.000000  0.000055
getconfig 0.000055  0.000158
autoload 0.000213  0.002528
init 0.002741  0.001077
tokens 0.003818  0.000047
viewinit 0.003865  0.000191
routing 0.004056  0.000190
cache 0.004246  0.000871
controller 0.005117  0.000015
action 0.005132  0.000585
view 0.005717  0.022582
header 0.028299  0.000155
content 0.028454  0.000125
footer 0.028579  0.000091
Total 0.02867 secs

Fluctuation

Name Min Avarage Max
phppatch 0.000042 0.000055 0.000078
getconfig 0.000119 0.000158 0.000232
autoload 0.002089 0.002528 0.003307
init 0.000910 0.001077 0.001267
tokens 0.000038 0.000047 0.000055
viewinit 0.000164 0.000191 0.000244
routing 0.000154 0.000190 0.000250
cache 0.000604 0.000871 0.001178
controller 0.000012 0.000015 0.000018
action 0.000500 0.000585 0.000751
view 0.022353 0.022582 0.022750
header 0.000145 0.000155 0.000166
content 0.000118 0.000125 0.000134
footer 0.000090 0.000091 0.000091

Neat, huh? :-)

Developer TemplatesPHPPE Developer

Files

The templates can be found in vendor/phppe/Developer/templates/ directory. Each file has a JSON header and an optional payload.

    {
        "desc_(lang)": "Description in the given language",
        "args": [ array of variable names ],
        "dirs": [ array of directory names to create ],
        "package": [ "template":{variable overrides} ],
        "file": "file/to/write",
        "append": "file/to/append/to"
    }
    payload

Description is one line long. Arguments are inherited from the environment, and can be referenced as:

With "dirs", you can create additional empty directories. Note that directories to files are generated automaticaly.

To include other templates, use the "package" keyword. You can override variables passed to the template.

Finally, one of "file" or "append" may exists. Former creates a new file out of payload, the latter one appends the payload to an existing content.

The payload is parsed, and all variables will be subtituted. You also have a handful of build-in variables:

You're allowed to use variables in the JSON with all right side expressions as well.

Example

    {
        "desc_en": "Example template",
        "args": [ "extension", "class" ],
        "file": "vendor/phppe/@Extension@/libs/@Class@.php
    }
    // This is a payload for @EXTENSION@.
    // Author: @@USERNAME, @@RFCDATE
    class @CLASS@ {
    }

Developing in PHPPEPHPPE Developer

HINT: Developer extension will guide you and generate PHP code for you!

You see, just like most of the developers, I'm a lazy one. I'd rather spend an hour developing a small utility for a simple task knowing it will be at hand next time needed, than repeating the same process over and over again. So one of the main goal was to make life easier and development effective. Simple, consistent directory structure, useful API, following the standards like PSR-2 etc. So even if you don't want to contribute to the framework, just using it or writing your own Extensions, it's worth installing the Developer package!

To install, copy'n'paste one of these into your SSH Terminal:

composer require "phppe/Developer"
mkdir vendor/phppe/Developer && curl https://bztsrc.github.io/phppe3/phppe3_developer.tgz | tar -xz -C vendor/phppe/Developer

Recommendation on environment

PHPPE suggests 3 layers:

  1. Development: running locally on developer's machine
  2. Staging: a separate test environment with latest master branch
  3. Production: preplanned updates
After the code is ready on development machine, it's commited to the remote master repository that resides on staging (with a version control system of your choosing). The same directory is configured in a webserver, so that you can do functionality tests. When everything is green the staging version should be synchronized to the production environment (rsync or ClusterCli extension).

Web based utilities

Tests

NOTE: PHPPE Developer has all the test cases for the PHPPE Core as well as for PHPPE Pack.

Provides a PHPUnit compatible test environment. This allows you to run tests from a browser, but nothing more: it's not as featureful as PHPUnit, for example it cannot generate code coverage reports.

The PHPUnit_Framework_TestCase compatibility class can be found in vendor/phppe/Developer/libs/phpunit.php, and thanks to autoloading, it will be used transparently if the real PHPUnit class not found.

The advantage over PHPUnit is that it scans all extensions for test cases, so you will see all of them in one place and can run all at once.

NOTE: Extensions should use another extension with the name of the original extension suffixed by "Dev". The "tests/" subdirectory and the file "phpunit.xml" goes in this new extension. For Composer it should be referenced with require-dev in the original extension's composer.json file.

Benchmark

Developer ships a nice and pleasant reporting tool for visualizing benchmark data. It aggregates the accumulated samples and shows the result on human readable charts.

Command line utilities

Create utility

This is the one that probably you'll use the most. It's a small templater to generate different files for the PHPPE environment. The templates can be found in vendor/phppe/Developer/templates/ directory, and you can add new ones any time you want. The format is plain simple, just take a look into one. Templates can be listed if you don't provide template name:

$ php public/index.php create
Usage:
  php public/index.php create <template> [arg1 [arg2 [...]]]

Template can be one of:
  action              action template
  addon               creates a view addon (widget)
  composer            create composer.json package information
  config              configuration file
  controller          controller template
  extension           create an extension
  filter              url filter template
  init                extension initialization
  lang                configuration file
  lib                 library template
  model               model template
  phpunit.xml         creates xml configuration for PHPUnit
  route               adds a new route
  scheme              SQL table scheme
  test                test case for a class
  update              SQL update
  view                create a view

As you can see, there's a template for almost everything you'll need. For example if you want to create a new extension, just name it, and the rest will be taken care of for you.

$ php public/index.php create extension MyService
Writing vendor/phppe/MyService/composer.json
Writing vendor/phppe/MyService/config.php
Writing vendor/phppe/MyService/init.php
Writing vendor/phppe/MyService/sql/myservicemodel.sql
Writing vendor/phppe/MyService/lang/en.php
Writing vendor/phppe/MyService/libs/MyService.php
Writing vendor/phppe/MyService/libs/MyServiceModel.php
Writing vendor/phppe/MyService/ctrl/myservice.php
Writing vendor/phppe/MyService/view/myservice.tpl
Writing vendor/phppe/MyServiceDev/tests/MyServiceTest.php
Writing vendor/phppe/MyServiceDev/phpunit.xml

Tests

The testing framework also available from CLI.

$ php public/index.php tests
Test boundle        Avg.time #Tests   Last run             Result
------------------- -------- -------- -------------------- ----------
MultilingualTest    0.0002s    2 /  2 2016-06-01 00:46:38  OK
SystemTest          0.2199s   55 / 55 2016-06-01 00:46:38  OK
AddonTest           0.0028s   40 / 40 2016-06-01 00:46:38  OK
RoutingTest         0.0208s   22 / 22 2016-06-01 00:46:38  OK
CacheTest           1.3239s   18 / 18 2016-06-01 00:47:15  OK
HttpTest            1.0810s   16 / 16 2016-06-01 00:46:40  OK
ConvertTest         0.0035s   23 / 23 2016-06-01 00:46:40  OK
DataSourceTest      0.0152s   50 / 50 2016-06-01 00:46:40  OK
TemplaterTest       0.1184s   82 / 82 2016-06-01 00:46:40  OK
PictureTest         0.4101s   30 / 30 2016-06-01 00:46:41  OK
DBTest              0.0047s   28 / 28 2016-06-01 00:46:41  OK
EmailTest           2.0121s   13 / 13 2016-06-01 00:46:43  OK
RegistryTest        0.0055s    6 /  6 2016-06-01 00:46:43  OK
UsersTest           0.0001s    2 /  2 2016-06-01 00:46:43  OK

shows the latest results, and you can run one or all test with:

$ php public/index.php tests run testfile
$ php public/index.php tests run

Pretty format

You can use this utility to format all your PHP sources in an extension to PSR-2 Coding Style.

$ php public/index.php pretty extension

Lang utility

It is a useful helper that scans your extension directory looking for translation references.

$ php publix/index.php lang extension

It can also merge the result with an existing language dictionary, prefixing unnecessary items by //-.

$ php publix/index.php lang extension languagecode

If you want to save the results, it can be done as easy as

$ php publix/index.php lang extension languagecode --write

DataURI utility

This small tool will generate data URI for images or icons.

$ php publix/index.php datauri file

Mkrepo utility

This small tool is very useful if you create your own repository let's say for your company's applications. This will scan all extensions under vendor/phppe/ directory of your developer environment collecting information from composer.json files. If source changed, it re-generates tarballs and saves packages.json as described in Repository section of this document. Now all you have to do is copy the latter along with the tarballs onto a webserver, specify the url in your production server's configuration and your deployment system is good to go!

$ php public/index.php mkrepo
Scanning for packages: 20 found
  CMS:                tgz  OK
  ClusterCli:         tgz  OK
  ClusterSrv:         skip OK
  CookieAlert:        skip OK
  Core:               tgz  OK
  DBA:                skip OK
  DataLibrary:        skip OK
  Developer:          tgz  OK
  EplosCMS:           skip OK
  Extensions:         tgz  OK
  GPIO:               tgz  OK
  Gallery:            skip OK
  GeoLocation:        skip OK
  POS:                skip OK
  YouTube:            skip OK
  bootstrap:          skip OK
  bootstrapcdn:       skip OK
  og:                 skip OK
  spectrum:           skip OK
  wyswyg:             skip OK
Saving packages info: OK
This tool also calls minify utility if public/source.php exists and is newer than public/index.php. It's because I personaly use this command to create the full PHPPE repository.

Minify utility

It's job is to compress the human readable, commented public/source.php into public/index.php, a minified, production ready format and to update the documentation you're reading now. It's also fires a minify event to compress 3rd party files, although PHPPE Core ships with an on-the-fly js and css minifier.

WARNING: never alter Core as it would break compatibility. Use event hooks or Extensions if possible. Only use this tool if you really know what you're doing.
$ php public/index.php minify
Compressing index.php: aa1c80932e73551a9816f7b84fabe43622c5adf4 80782 (0) OK
Updating documentation: OK

Classic Hello World! Tutorial

Simpliest

We'll only use view layer, no controller at all. If PHPPE does not find any application and controller, it will only serve the view. This behaviour is ideal for pages like About Us and Impressum. Default routes apply here.

app/views/helloworld.tpl:
<h1>Hello World!</h1>

Now open a browser tab and go to (your homepage)/helloworld url to see the result.

Bit more

A very minimal application, with only one controller. The view here refers to an application property.

app/views/helloworld.tpl:
<h1><!=hello></h1>

The application does not set any route, it uses the standard class naming convention for default routing. Also, action uses default action route.

app/ctrl/helloworld.php:

1
2
3
4
5
6
7
8
9
namespace PHPPE\Ctrl;
class HelloWorld {
	public $hello;

	function action( $item )
	{
		$this->hello = "Hello " . ( $item ? $item : "World" ) . "!";
	}
}

As a PHPPE Extension

Assuming we want to serve the page in an arbitrary PHPPE Extension.

vendor/phppe/MyExtension/views/myView.tpl:
<h1>Hello World!</h1>

This time we'll set a routing rule for that, because the url does not align with file and class naming. It has to be done in the extension's initialization code.

vendor/phppe/MyExtension/init.php:

1
PHPPE\Http::route("helloworld", "MyExtension", "myAction");

Here's the controller, that sets the template's name for the view. No default action route here, we've specified the action method with route.

vendor/phppe/MyExtension/ctrl/MyExtension.php:

1
2
3
4
5
6
7
namespace PHPPE\Ctrl;
class MyExtension {
	function myAction($item)
	{
		PHPPE\Core::$core->template = "myView";
	}
}

As a widget

Here we'll create a widget to display "Hello World". View refers to our widget without any specific controller.

app/views/*.tpl:
<!widget helloworld>
   - or -
<!field helloworld obj.field>

This will be the template that refers to our widget. Without reference, our widget never get's loaded.

app/addons/helloworld.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace PHPPE\AddOn;
class helloworld extends \PHPPE\AddOn {
	function init()
	{
		//! register our add-on
		PHPPE\Core::addon( "helloworld", "Hello World! Tutorial" );
	}

	function show()
	{
		return "Hello " . ( $this->value ? $this->value : "World" ) . "!";
	}

	function edit()
	{
		return "Hello <input type='text' value='" . htmlspecialchars($this->value) . "'>!";
	}
}

Now we have a widget face to draw, and a configration "form" where we can set who to greet.

Routing Tutorial

Controllerless route

This is the simpliest, the view will be directly mapped to the url. For an example, see Hello World! tutorial.

Default application

If no route given, or neither of them applies, PHPPE will fallback to default route. This is not the scope of this tutorial either.

Specify application

Adding application to routes, but still relying on default action route. See PHPPE\Http::route() documentation on how to specify routes.

app/init.php:

1
PHPPE\Http::route( "myurl", "MyApp" );

The corresponding controller:

app/ctrl/MyApp.php:

1
2
3
4
5
6
7
namespace PHPPE\Ctrl;
class MyApp{
	function action($item, $item2="")
	{
		//! something
	}
}

This will be served when (base)/myurl called. If you enter (base)/myurl/myaction in the browser, PHPPE will look for the method myaction($item) in this class first, then fallbacks to action($item).

Specify action method too

app/init.php:

1
PHPPE\Http::route( "myurl", "MyApp", "myAction" );

The corresponding controller now uses a custom method name for action:

app/ctrl/MyApp.php:

1
2
3
4
5
6
7
namespace PHPPE\Ctrl;
class MyApp{
	function myAction($item)
	{
		//! something
	}
}

Using custom arguments

This time we'll use pharenthesis in url regexp. Each corresponds to an action method argument:

app/init.php:

1
PHPPE\Http::route( "myurl([0-9]*)/([a-z]+)", "MyApp", "myAction" );

And the controller:

app/ctrl/MyApp.php:

1
2
3
4
5
6
7
namespace PHPPE\Ctrl;
class MyApp{
	function myAction($someNumber, $someLetters)
	{
		//! something
	}
}

Without pharenthesis, the url will be splitted up by slashes '/', and each passed as an argument.

(base) / app / action / item:

1
function action($item)


(base) / app / action / item1 / item2:

1
function action($item1, $item2)

Specifying filters

You can add a filter list as fourth argument to PHPPE\Http::route(). As any other list in PHPPE, it can be a comma separated string list or an array.

Filters that starts with a '@' excpets the user to have an ACE, others refer to a boolean logic placed in PHPPE\Filter\name::filter().

1
2
PHPPE\Http::route( "myurl([0-9]*)/([a-z]+)", "MyApp", "myAction", "@loggedin,post" );
PHPPE\Http::route( "myurl([0-9]*)/([a-z]+)", "MyApp", "myAction", ["@loggedin", "post"] );

Normally you would use one action handler for each url, and check for csrf-valid form post in there with PHPPE\Core::isBtn(form name). But, because PHPPE gives you maximum flexibility, you can also have a separate method:

1
2
PHPPE\Http::route( "myurl([0-9]*)/([a-z]+)", "MyApp", "myGetAction", "get" );
PHPPE\Http::route( "myurl([0-9]*)/([a-z]+)", "MyApp", "myPostAction", "post" );

What's more, if there's only one form on your page, you can spare the formentioned isBtn() call as well:

1
PHPPE\Http::route( "myurl([0-9]*)/([a-z]+)", "MyApp", "myPostAction", "post,csrf" );

Application Tutorial

This tutorial will show you a typical application page. It will teach you how to create a form, tide it to an object so that their values get filled in automatically, and finaly how to retrive the user provided data with validation.

Let's assume we're using the url (base)/tutorialapp and (base)/tutorialapp/myaction. For details see routing. In this tutorial, it's enough to know we're relying on default routing.

Now, create the views for our urls with forms in them.

app/views/tutorialapp.tpl:
Tutorial default action's view
<!include errorbox> <!-- include a box to show error messages -->
<!form tutobj> <!-- assign form to an object -->
<!field text tutobj.field1>
<!field num tutobj.field2>
<!field update>
</form>
app/views/tutorialapp_myaction.tpl:
Tutotiral myaction view
<!form tutobj>
<!field text tutobj.field1>
<!field update>
</form>
<!form tutobj2>
<!field num tutobj2.field1>
<!field update Save>
<!field update Save_and_New>
</form>

And here comes the controller.

app/ctrl/tutorialapp.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
namespace PHPPE\Ctrl;
class TutorialApp {
  public $tutobj = null;
  public $tutobj2;

  function action( $item ) {
    //! This flag is set on valid form submit once,
    //! making the transaction unrepeatable.
    if ( PHPPE\Core::isBtn() ) {
        //! Add an extra validation rule to some field
        PHPPE\Core::validate('tutobj.field2', 'myvalidator');
        //! Get the form fields and their values from user request.
        //! This will call field validators automatically.
        $this->tutobj = PHPPE\Core::req2obj('tutobj');
        //! Here you can force more complex validation or business logic if you like
        if ( $this->tutobj->field1=="" && $this->tutobj->field2==1 )
            PHPPE\Core::error( L("Validation failed."), "tutobj.field1" );

        //! If passed validation, we can save the data in database
        if ( !PHPPE\Core::isError() ) {
            //! Log what we're going to do
            PHPPE\Core::log('A', "Saving " . PHPPE\Core::obj2str($this->tutobj), "tutorialapp" );
            //! This should be in a Model, but this is a basic tutorial
            PHPPE\DS::exec( "INSERT INTO tutorialapp SET " . PHPPE\Core::obj2str( $this->tutobj ) . ";" );
            //! To avoid "Page expired" popups, you have to redirect user here
            PHPPE\Http::redirect();
        }
    }
    //! Here's the right place to load object from database.
    //! But only if the object is not full with user input that failed validation.
    if ( $this->tutobj === null && !empty($item) )
        //! This should be in a Model, but this is a basic tutorial
        $this->tutobj = PHPPE\DS::fetch( "*", "tutorialapp", "id=?", "", "", [ $item ] );
  }

  function myaction ($item) {
    //! only for the first form
    if ( PHPPE\Core::isBtn( "tutobj" ) ) {
        //! This is our own $tutobj, differs from action()'s.
        $this->tutobj = PHPPE\Core::req2obj( 'tutobj' );
    }
    //! only for the second form, and only if second button pressed
    if ( PHPPE\Core::isBtn("tutobj") == 2 ) {
        //! another form
        $this->tutobj2 = PHPPE\Core::req2arr( 'tutobj2' );
  }
}

Service Tutorial

Configuration

vendor/phppe/MyService/config.php:

1
2
3
4
//Configuration
return [
    "dosomething" => true
];

Translations

vendor/phppe/MyService/lang/en.php:

1
2
3
return [
    "myservice_helloworld" => "Hello World: "
];

Initialization

The initialization file will be loaded for each extension. This is the place to set routes and return service instance:

vendor/phppe/MyService/init.php

1
2
3
<?php
PHPPE\Http::route("myservice/myaction/(0-9]+)/([a-z]+)", "MyService", "myMethod");
return myService;

Service Provider class

These class will be used by applications for services. It also acts a controller.

vendor/phppe/MyService/libs/MyService.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
class MyService {
    public $version = "1.0.0";
    private $dosomthingextra = false;

    //! some service your extension provides
    function exampleService( $a, $b, $c ) {
	return ( L("myservice_helloworld") . round( $a + $b - $c ) );
    }

    //! initialize our service. This method gets config.php as argument
    function init($cfg)
    {
	//! Register our service with dependency check
    //! this is not a must, it's enough to return an instance from init.php
	PHPPE\Core::lib(
		"MyService",		//! unix name
		$this,			//! service reference
		[ "DB", "Users" ],	//! dependencies
	);

	//! parse and validate your configuration here
	if ( !empty( $cfg["dosomething"] ) )
		$this->dosomethingextra = true;

	//! Do any extension specific tests here, use PHPPE\Core::log() to indicate errors
	if ( $this->dosomething ) {
	    //! Do feature specific tests here. If some test fails,
	    //! Use PHPPE\Core::log('E',...) for high level services,
	    //!	if your service called by applications directly (no other service depends on it)
	    //! Use PHPPE\Core::log('C',...) for low level services,
	    //!	if you expect other services dependenting on your service
	    ...
	    PHPPE\Core::log ( 'D', "Feature dosomething initialized." );
	} else
	    PHPPE\Core::log ( 'D', "Feature dosomething disabled." );

	//! register menu on PHPPE Panel
	PHPPE\View::menu( L( "MyService" ), "myservice/myaction" );
    }

    //! register a status icon on PHPPE panel
    function stat()
    {
	echo( "<img alt='That is me!" . ( $this->dosomething ? " ds" : "" ) . "'>" );
    }
}

In runtime you can access the service as simply as

1
PHPPE\Core::lib("MyService")->exampleService(1, 2, 3);

Widget Tutorial

See templater for details.

You can use this Add-On in view templates as:

<!widget tutorialwgt(1,2,3,4,5,6) instancename>

The "widget" tag calls show(), unless configuration mode ($_SESSION['pe_c']) is true in which case it renders configuration panel by calling edit() hook. The "var" tag calls show(), unless edit mode ($_SESSION['pe_e']) is true.

NOTE: var tag checks edit mode, while widget checks conf mode to display edit().
app/addons/tutorialwgt.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
namespace PHPPE\AddOn;
class tutorialwgt extends \PHPPE\AddOn {
    //! initialize - this is used by view template editor to get configuration for this widget
    //! also called right before the first "show" or "edit" method call once
    function init()
    {
	//! This code is instance independent, and called only once per page generation
	//! Include widget specific stylesheet
	PHPPE\View::css( "tutorialwgt.css" );
    }

    //! function to draw widget face
    function show( )
    {
	//! Include widget's JavaScript - has to be done here since it requires instance name
	PHPPE\View::jslib( "tutorialwgt.js", "pe.tutorialwgt.init('" . $this->name . "');" );
	//! $this->args = [ 1, 2, 3, 4, 5, 6 ];
	//! $this->attrs = [ $opts ];
	return "face";
    }

    //! function to create widget configuration form
    //! OPTIONAL, leave it empty if there's no configuration at all
    function edit( ) {
	return "<input type='text' onchange='tutorialwgt_saveconfig();' class='reqinput' ".
	    "name='" . $this->fld . "_cfgopt1' value=''><br>\n".
	    "<input type='check' onchange='tutorialwgt_saveconfig();' name='" . $this->fld . "_cfgopt2'> ".
	    L( "tutorialwgt_cfgopt2" );
    }

}

Setting up PHPPE on Raspberry Pi

Step 1: install raspbian

This is quite straightforward. Download image and use dd utility. You'll need at least stretch for PHP 7.0.

Step 2: install packages

You'll need these:

# apt-get install lightdm nginx php7.0-cli php7.0-fpm php7.0-opcache php7.0-apcu php7.0-gd php7.0-sqlite openbox chromium

Many of these may have already been installed on your Raspberry, and you can choose alternative db drivers like php7.0-pdo_mysql as well. For jessie PHP 7 backports, you'll find a lot of resouce on the internet, just google. Note that jessie may not include chromium, so you'll have to use this for RPi2:
https://launchpad.net/ubuntu/precise/armhf/chromium-browser
https://launchpad.net/ubuntu/precise/armhf/chromium-codecs-ffmpeg-extra

Step 3: configure nginx

You can find many tutorials on how to do this, but here's a brief configuration:

/etc/nginx/sites-available/default
server {
	listen 80 default_server;
	root /var/www/localhost/public;
	server_name _;

	location / {
		try_files $uri $uri/ =404;
		index index.php;
		if (!-e $request_filename){
			rewrite ^(.*)$ /index.php;
		}
	}
	location ~ \.php$ {
		include snippets/fastcgi-php.conf;
		fastcgi_param  SERVER_NAME $host;
		fastcgi_pass unix:/var/run/php-fpm.sock;
	}
}
Basically we pass control to php-fpm and we do a little rewrite trick.
For testing, create /var/www/localhost/public/index.php:
echo "<?php phpinfo();" >/var/www/localhost/public/index.php
Now enable and start the service
systemctl enable nginx
systemctl start nginx

Step 4: grant access for GPIO

Add webserver's user to gpio group:

# addgroup www-data gpio

Also, you should add /sys/class/gpio and /proc/cpuinfo to open_basedir in your php.ini file as GPIO Extension uses the sys kernel interface and it detects the pin layout from the revision number.

Step 5: set up automatic login

In /etc/lightdm/lightdm.conf, change the following lines:
autologin-user=pi
autologin-user-timeout=0
Alternatively you can use raspi-config to get the job done for you.

Step 6: set up full screen browser

For that we have to arrange a few things first. Create a session resource and copy the following lines to it.
/home/pi/.xsessionrc
export DISPLAY=":0"
xset s off
xset s noblank
xset -dpms
xdotool mousemove 8192 8192
openbox --sm-disable &
sudo bash -c "echo '[info] phppe: Starting fullscreen browser' >>/dev/console"
bash -c "while true; do (ps u|grep [c]hromium >/dev/null) || chromium -kiosk --incognito --start-maximized http://localhost; done"
First line sets the display to be used, the x* commands turn screen blanking off and hides the mouse pointer. For fullscreen we need a window manager to accept fullscreen request, in this tutorial I've used openbox since it's the default. The line after that logs a message on console so that you will know when it's executing (Raspberry's bootup process is slow). And finally, the last line starts a fullscreen browser and makes sure of it that it would respawn if crashed.

Now tell openbox to start chromium in fullscreen without decorations:
/home/pi/openbox_rc.xml
<?xml version="1.0" encoding="UTF-8">
<openbox_config xmlns="http://openbox.org/3.4/rc" xmlns:xi="http://www.w3.org/2001/XInclude">
  <applications>
    <application class="*">
      <focus>yes</focus>
      <fullscreen>yes</fullscreen>
      <decor>no</decor>
    </application>
  </applications>
</openbox_config>

Step 7: install PHPPE

Read further instructions here. In a nutshell copy index.php there, call diagnostics mode then update with composer or download Pack tarball.

Step 8: configure PHPPE

/var/www/localhost/phppe/config.php
'db' => "sqlite:data/sqlite.db",
'cache' => "apc",

Step 9: Reboot and enjoy!

You should see the PHPPE Self Test and Cheat Sheet page in fullscreen after reboot, served by your own Raspberry!

If you want to control your Raspberry Pi from your PHPPE Application (of course you do), you can use the GPIO Extension as simply as:

PHPPE\GPIO::mode(pin, 'in'/'out');
PHPPE\GPIO::get(pin);
PHPPE\GPIO::set(pin,true/false);

Setting up a cluster

For this tutorial, you'll need at least 5 virtual machines:

Step 1: install PHPPE

Install a webserver and PHPPE on all nodes, except database server.

php public/index.php --diag
$ curl https://bztsrc.github.io/phppe3/phppe3_core.tgz | tar -xz -C vendor/phppe/Core

On the management servers, also install Extension Manager, Content Editor and Cluster Server:

$ mkdir vendor/phppe/CMS && curl https://bztsrc.github.io/phppe3/phppe3_cms.tgz | tar -xz -C vendor/phppe/CMS
$ mkdir vendor/phppe/Extensions && curl https://bztsrc.github.io/phppe3/phppe3_extensions.tgz | tar -xz -C vendor/phppe/Extensions
$ mkdir vendor/phppe/MultiServer && curl https://bztsrc.github.io/phppe3/phppe3_multiserver.tgz | tar -xz -C vendor/phppe/MultiServer
$ mkdir vendor/phppe/ClusterSrv && curl https://bztsrc.github.io/phppe3/phppe3_clustersrv.tgz | tar -xz -C vendor/phppe/ClusterSrv

Finally, on the workers only Cluster Client:

$ mkdir vendor/phppe/ClusterCli && curl https://bztsrc.github.io/phppe3/phppe3_clustercli.tgz | tar -xz -C vendor/phppe/ClusterCli

Step 2: setup load balancing

On one of the workers modify the configuration and add subdomains you're want to load balance for:
vendor/phppe/ClusterCli/config.php:

1
  'loadbalancer' => [ 'www' ],

To allow PHPPE to refresh the DNS, create a shell script:
vendor/bin/cluster_lb.sh:

#!/bin/bash

exec >/var/lib/bind/myzone.conf
systemctl reload bind

Step 3: setup syslog

On all workers,
vendor/phppe/config.php

    'syslog' => true
Configure your syslog daemon to send the logs to the active management server: admin.myzone.

Step 4: setup database connection

On all virtual machines,
vendor/phppe/config.php

    'db' => 'mysql:host=quorum4.myzone;dbname=phppe;charset=utf8@dbuser:dbpass'

On the database server, install and configure mysql or any other compatible database server (like mariadb). The best if your virtual machine provider has database service as well, like Amazon AWS. There you don't have to care about the database server.

Step 5: setup user session

On all workers, set up php.ini to store session data in database on quorum4.myzone.

Step 6: setup mailer

On all workers,
vendor/phppe/config.php

    'mailer' => 'db'

On the management servers, add
vendor/phppe/config.php

    'realmailer' => 'smtp:gmail-smtp-in.l.google.com'
as well.

Step 7: setup cluster supervisor

On all nodes, add to your crontab

*    * * * * /usr/bin/php /var/www/public/index.php cron minute
On workers that will call public/index.php cluster client, on management nodes public/index.php cluster server.

Step 8: testing high availability

On the standby management server, type

$ php public/index.php cluster takeover
You should see that within a minute the admin.myzone CNAME should point to admin3.myzone.

PHPPE\Core

This is the main class of PHPPE. One singleton instance can be always accessed with PHPPE\Core::$core. Some properties are run-time generated, others can be configured.

Configurable properties

PHPPE\Core::$core->titletitle of the site or name of the project
PHPPE\Core::$core->baseDomain name used by url(). It's autodetected if not given.
PHPPE\Core::$core->metaSite global meta tags.
PHPPE\Core::$core->linkSite global link tags.
PHPPE\Core::$core->runlevelcurrent runlevel
PHPPE\Core::$core->syslogif true, no log files will be written but messages will be sent to syslog
PHPPE\Core::$core->traceif true, debug trace will also be written along log messages
PHPPE\Core::$core->timeoutSession timeout in seconds
PHPPE\Core::$core->sessionvarOverride session variable name (optional, defaults to "pe_sid")
PHPPE\Core::$core->secsessionOnly use session on secure channel (https only cookie)
PHPPE\Core::$core->mailerEmail backend, used by PHPPE Pack's Email sender, like "smtp://[(user)[:(pass)@]](server)[:(port)][/(sender email address)]"
PHPPE\Core::$core->realmailerIf Email backend is a queue in database ($core->mailer = "db"), sendmail script uses this backend when consuming mails from queue.
PHPPE\Core::$core->nocacheSet it to true to force refresh in templater and avoid asset caching
PHPPE\Core::$core->noaggrSet it to true to turn static css and js content aggregation off in html header
PHPPE\Core::$core->nominifySet it to true to turn css and js minifier off
PHPPE\Core::$core->cacheConnection url for caching
PHPPE\Core::$core->cachettlView output and asset cache time to live in sec
PHPPE\Core::$core->dbPrimary datasource
PHPPE\Core::$core->noctrlDisable dynamic content controller scripts
PHPPE\Core::$core->allowedlist of allowed functions in a view template, leave it unset for no restrictions
PHPPE\Core::$core->disabledlist of disabled Extensions
PHPPE\Core::$core->blacklistlist of unwanted domains (for example in email addresses)
PHPPE\Core::$core->reposarray of third-party repository urls. For details, see Repositories
PHPPE\Core::$core->outputOutput format, defaults to "html"
PHPPE\Core::$core->masterpasswdEncrypted password hash for superadmin (works even if PHPPE\Users class does not exists)

Generated properties

PHPPE\Core::$corePHPPE Core instance (self reference)
PHPPE\Core::$userPHPPE Users instance, logged in user
PHPPE\Core::$clientPHPPE Client instance
PHPPE\Core::$core->idMagic, PHPPE version (read-only)
PHPPE\Core::$core->urlPage specific part of the url (read-only)
PHPPE\Core::$core->appcurrent application, (read-only, but see route event)
PHPPE\Core::$core->actioncurrent action (read-only)
PHPPE\Core::$core->itemcurrent item or null
PHPPE\Core::$core->templateTemplate's name used by view layer. Defaults to "(application)_(action)"
PHPPE\Core::$core->nowcurrent UNIX timestamp, from primary datasource if available
PHPPE\Core::$core->noframeIf not set, application's output will be placed in a frame
PHPPE\Core::$core->fmFile max size
PHPPE\Core::$core->formName of the submitted form if PHPPE\Core::isBtn() returns non-zero
PHPPE\Core::$core->tryOn submit the form's pressed button's serial, query with PHPPE\Core::isBtn()
PHPPE\Core::$core->errorarray of error messages, see PHPPE\Core::error() and PHPPE\Core::isError()
PHPPE\Core::$core->libsList of loaded libraries, register and query with PHPPE\Core::lib()
PHPPE\Core::$core->secTrue if called over ssl and channel is secure
PHPPE\Core::$startedFramework start time in microsecs, required by monitoring stats
PHPPE\Core::$vValidator data
PHPPE\Core::$pathsPaths to extensions (important if file system is case-sensitive)

Methods

__construct()PHPPE Core constructor. Called by public/index.php
bootdiag()Run diagnostics mode
run()Run the current application
static lib()Query and register libraries (services)
static isInst()Checks if an extension or Add-On is installed
static lang()Load language dictionary for an extension in current language
static log()Log a message
static isBtn()Checks if user tries to save a form with valid CSRF token by pressing a button
static error()Query and register error messages
static isError()Checks if there was an error
static event()Trigger a specific event
static validate()Add a validator on a form field
static req2arr(), req2obj()Convert user request into an associative array or object
static arr2str(), obj2str()Convert an associative array or object into a quote-safe string
static val2arr()Convert a templater expression into an array
static tre2arr()Flat a tree (recursive array, sub-levels in "_") into a simple one level array
static bm()Set a benchmark point. Only works with source.php

Session variables

To save values accross pages, the Core uses some SESSION variable.

$_SESSION['pe_e']True if user logged in and turned edit mode on (templater var tags)
$_SESSION['pe_c']True if user logged in and turned widget configuration mode on (templater widget tags)
$_SESSION['pe_u']Logged in user
$_SESSION['pe_s']Security tokens
$_SESSION['pe_v']Validator data (filled by PHPPE\View::template(), read on post by PHPPE\Core:req2obj())
$_SESSION['pe_r']Set if PHPPE\Http::redirect() should go back to referer (return url after authentication)

PHPPE\ClassMap

This class takes care of autoloading. It's bullet-proof and PSR-0 / PSR-4 compatible. It stores the map in JSON format because in PHPPE webserver is not allowed to write PHP files. The map is automatically regenerated if an extension is installed or removed.

Files

.tmp/.classmap
.tmp/.acemap

Properties

static string $fileName of the cache json file (.tmp/.classmap)
static string $aceName of the ace cache file (.tmp/.acemap)
static array $mapThe generated map, used by spl_autoload_register closure

Methods

static has()Check if a class or method exists
static ace()Return Access Control Entry map.
static map()Return Class map.

PHPPE\Client

This class holds every information on client. It's populated consistently in CLI as well in web mode, so you don't have to bother yourself with different $_SERVER keys.

Properties

string $ipClient's remote address (valid for forwarded requests and server behind proxies as well or "CLI" if called from command line)
string $agentClient's agent (browser or terminal type)
string $userClient's user (HTTP Authenticated user or UNIX user)
string $tzClient's timezone (browser's or from environment variable TZ or /etc/localtime)
bool $jsTrue if client has JavaScript enabled
string $langClient's preferred language's code on two characters (from request or environment variable LANG)
array $screenClient's screen [width, height] in pixel (or in characters, from tput utility)
array $geoClient's geo location (as returned by php's geoip_record_by_name(), provided by phppe/GeoLocation extension)

Methods

None.

PHPPE\Model

Object Relational Mapper prototype that correlates each record in database to a Model instance. Should not be used directly, instead inherited.

Properties

int $idNumeric identifier of the record
string $nameName of the record to show to the user (if nothing else, #id)
static string $_tableName of the table where the records are stored

Methods

__constructor()Loads data from database (if argument is an id, otherwise argument fields) into the object
load()Loads data from database into the object
save()Saves data from object into database
static find()Look up records in database

PHPPE\Ctrl

This is not a class, but a namespace for controllers. Each class in this namespace can have the following properties:

Properties

string $cliHint for CLI arguments
string $mimetypeChange the default mime type of the output. Defults to 'text/html'.
string $titleController specific page title.
string $faviconController specific favicon.
array $metaAdditional meta tags for the controller.

Methods

action()Default action handler, see routing.

PHPPE\Http

HTTP protocol helpers.

Properties

None.

Methods

static url()Generate permanent url
static redirect()Redirect user to url
static get()Perform a HTTP GET or POST request and return response
static route()Add routes to the routing table
static urlMatch()Query routing table and return class, method for an url

PHPPE\DS

DataSource services.

This is a low level interface, you should use the SQL Query Builder, PHPPE\DB instead.

Properties

string $nameName of primary datasource driver

Methods

static db()Add new database or return current datasource's PDO instance
static ds()Select datasource or return current selector
static like()Convert user input into an sql like phrase
static exec()Execute sql on current datasource
static query()Perform a SELECT query and return it's results
static fetch()Get the first record from database
static field()Get one field's value from database
static tree()Execute a reentrant SELECT to get a tree from database
static bill()Return time consumed by database operations

PHPPE\Cache

Wrapper around cache implementations.

Properties

string $nameCurrent implementation (class under PHPPE\Cache namespace)
static object $mcMemcache (or any other implementation's) instance

Methods

static set()Store a value for key in cache
static get()Retrieve a value for key from cache

PHPPE\Assets

This extension proxies assets under vendor directory. It also make use of cache if possible.

Properties

None.

Methods

static minify()A very small and fast asset minifier

PHPPE\View

The PHPPE templater.

Properties

None.

Methods

static assign()Assign a variable to the templater
static css()Add stylesheet to the output
static jslib()Add JavaScript library to the output
static js()Add JavaScript function to the output
static menu()Add menu to PE Panel
static template()Get and parse a view template
static get()Get and return raw template without parsing it
static getval()Evaluate a templater expression
static e()Format an error message according to output
static picture()Crop, resize and watermark imagery

PHPPE\Tools

Set of useful file manipulation tools.

Properties

None.

Methods

static rmdir()Recursive directory remover
static untar()Extracts a file from an archive or execute a callback for each
static ssh()Executes shell command securely on a remote server
static copy()Copies files securely to a remote server
static bg()Execute a background job in every nth sec.

PHPPE\Filter

Classes in PHPPE\Filter namespace that are inherited from the PHPPE\Filter class can be used as URL filters. For that it has only one argumentless method that returns a boolean success flag. You can apply filters to route calls as 4th argument, and they will be executed when the routing decision is made.

Properties

None.

Methods

static filter()Logic that checks validity for an URL and returns boolean

Examples

1
2
3
4
5
6
7
class \PHPPE\Filter\get extends \PHPPE\Filter
{
  public static function filter()
  {
    return @$_SERVER[ 'REQUEST_METHOD' ] == "GET" ? true : false;
  }
}

1
2
3
4
5
6
7
class \PHPPE\Filter\post extends \PHPPE\Filter
{
  public static function filter()
  {
    return @$_SERVER[ 'REQUEST_METHOD' ] == "POST" ? true : false;
  }
}

1
2
3
4
5
6
7
class \PHPPE\Filter\csrf extends \PHPPE\Filter
{
  public static function filter()
  {
    return \PHPPE\Core::isBtn();
  }
}

1
2
3
4
5
6
7
8
9
10
class \PHPPE\Filter\loggedin extends \PHPPE\Filter
{
  public static function filter()
  {
    if (\PHPPE\Core::$user->id)
      return true;
    /* save request uri for returning after successful login */
    PHPPE\Http::redirect("login", true);
  }
}

PHPPE\AddOn

These are the building blocks of the view layer. They implement user input boxes, widgets and basicly everything that has to display formated values in a view.

To include an AddOn in a template, you use

    <!tag addon(arguments) name attributes>
Where tag can be one of var, field, widget or cms. See templater for more details.

Properties

string $nameName of the instance. Most likely something like "object.property"
string $fldName of the instance, appropriate for DOM id or name. Most likely something like "object_property"
string $valueValue of the instance. It's passed by reference in most cases.
string $argsArguments passed in pharenthesis after addon name.
string $attrsAttributes passed after instance name.
string $cssGenerated class name, "input", "reqinput" or "errinput". You can add more css classes with attributes if you like.
string $confConfiguration scheme. Used by Content Editor.

Methods

init()Initialization event handler. Called once for every referenced AddOn per page generation.
show()Uneditable html representation of the value. For fields is a formated value, for widgets it means the widget's face.
edit()Editable html representation of the value. For fields is more likely one or more INPUT fields, for widgets it's the configuration form.
static validate()Validates or alters the AddOn's value.

PHPPE\User, PHPPE\UsersPHPPE Pack

This class is a bit tricky as PHPPE Core provides PHPPE\User (note singular) that can handle only one adminstrator user, whom password hash is set in configuration file. As soon as PHPPE Pack is installed under vendor/phppe/Core, that class will be transparently substituted with PHPPE\Users (note the prular) which can handle more users.

This class is a classic Model, it's inherited from PHPPE\Model therefore you can use the find() method to list users.

HINT: you can use the "passwd" utility to generate password hash.

Properties

int $idNumeric identifier of the user
string $nameLogin name
string $emailEmail address of the user
int $parentidThe parent user's id. Users can be organized in a tree.
boolean $activeTrue if user is allowed to log in
array $dataUser preferences in an associative array

Methods

has()Checks whether the given user has an ACE
grant()Grant ACE to the user
clear()Terminate access
login()Authenticate user with login name and password
logout()Log out user

PHPPE\RegistryPHPPE Pack

In most cases configuration file is enough for extensions. But sometimes extensions need to store a value as well, e.g.: timestamp of the lust run. Because PHPPE does not allow the webserver's user to write php files, it cannot change config.php, so it would need a different mechanism, and that's what Registry is for. Without a datasource, it will use local files to store changable configuration variables, uses 'registry' table otherwise.

Properties

None.

Methods

static set()Store a value for key in registry
static get()Retrieve a value for key from registry
static del()Remove a key from registry

PHPPE\DBPHPPE Pack

SQL Query Builder, built on top of datasource.

Properties

None.

Methods

static like()Convert user input into an sql like phrase
static select()Start to build a SELECT query
static update()Start to build an UPDATE statement
static insert()Start to build an INSERT statement
static replace()Start to build a REPLACE statement
static delete()Start to build a DELETE statement
static truncate()Start a TRUNCATE statement
table()Add plus table to the query with table multiplication
join()Join a table to the query
fields()Specify fields to query
where()Add where clause
orderBy()Sort records
groupBy()Group records
having()Same as where clause, but filters aggregated records
limit()Maximum number of records to return
offset()Number of records to skip at begining
sql(), __toString()Return SQL sentence string
execute()Execute the query/statement on current datasource
with()Execute the query/statement with a value set

Exceptions

Throws DBException.

PHPPE\EmailPHPPE Pack

Email builder and sender. Supports multipart messages and can speak SMTP directly (no dependency to third lib classes).

The backend is configured in phppe/config.php.

If you use "db" in "mailer", the messages will be sent through the backend given in realmailer.

Backends

smtp://[(user)[:(pass)@]](server)[:(port)][/(sender email address)]This is the default backend, speaks SMTP natively, no dependencies required.
mimeThis backend builds and returns the mime message but does not send any email.
logThis backend does not send any email either, it's just writes the log, for testing purposes.
mailUses PHP's built-in mail() function. Not recommended.
sendmailSends mails by calling sendmail through a pipe. Not recommended.
phpmailerUses the well-known PHPMailer class to send out the emails.
dbStores emails in database. In this case the cron minute background job will send out the mail with the backend configured in "realmailer".

Properties

None.

Methods

__constructor()Create an Email object
get()Returns Email object in JSON
to()Set recipient
replyTo()Set return address
cc()Set carbon copy address
bcc()Set hidden carbon copy address
subject()Set subject
message()Set message body using string
template()Set message body using a view template
attachFile()Add attachment from file
attachData()Add attachment from a string
send()Send the email

Exceptions

Throws EmailException.

PHPPE\GPIOPHPPE GPIO

Allows manipulation of Raspberry Pi GPIO ports from PHP, and also queries some useful property of the board.

This Extension unregisters itself if GPIO kernel interface cannot be used.

Setup

In order to get GPIO working, you'll have to add the webserver's user to the gpio group:

# addgroup www-data gpio

Also, you should add /sys/class/gpio and /proc/cpuinfo to open_basedir in your php.ini file.

Properties

array $pinsPin layout

Methods

static RPiPCB()Returns board revision number (for pin autodetection)
static temp()Returns board temperature in SI oC
static freq()Returns CPU frequency
static reset()Reset all pins to input mode
static mode()Set direction (in or out mode) for a pin
static get()Read level of a pin
static set()Set high or low level on a pin

Exceptions

Throws GPIOException.

JavaScript PluginsPHPPE Pack

Adds functionality to PHPPE JavaScript libraries.

Namespaceless functions

htmlspecialchars(text)Escape a string for html attributes, like in php.
urlencode(text)Escape a string for urls, like in php.
number_format(number, decimals, dec_point, thousands_sep)Format a number, like in php.
function_exists(functionname)Checks if a function exists. Name can contain namespace.
getpos(domElement)Returns the position and dimension of an element in {"x","y","w","h"}.
L(phrase)Translate a phrase

Temporary client side storage

pe.cookie

pe.cookie.get(name)Read the value of a cookie.
pe.cookie.set(name,value,nDays,path)Store a cookie.

Permanent client side storage (localStorage)

pe.store

pe.store.get(name)Read the value of a store item.
pe.store.set(name,value)Store an item. Passing null as value will remove the item.

Drag and Drop

pe.dnd

pe.dnd.drag(event,id,icon,size,endhook)Drag a DOM element represented as icon with size.
pe.dnd.drop(event,myfunc[,context])Mark a DOM element as drop area.
myfunc(what,context,event)Callback arguments for both myfunc and endhooks.

You can also use the data attribute version, add data-drag="id,icon,size,endhook" to draggable elements, and data-drop="callback[,context]" to drop areas.

Popup menu

pe.popup

pe.popup.open(triggerobj,popupid,deltax,deltay)Pops up the div with id of 'popupid'.
pe.popup.close()Hide the active pop up div.

Or use data-popup="popupid" on the trigger elements.

Responsive tables

pe.resptable

To make your table responsive, add "resptable" class to it.

Set Selection

pe.setsel

pe.setsel.search(id)Execute list filtering on a widget.

An intuitive multiple selection widget.

User Profile

pe.user

pe.user.profile(this)Pops up user profile page with preferences.

Cookie Alert

pe.cookiealert

pe.cookiealert.init({
"cookiename",
"message",
"moreurl",
"morelabel",
"acceptlabel"
})
Warning on cookies with a more button.

What You See is What You Get HTML editor

pe.wyswyg

pe.wyswyg.open(this,icons)Turns a textarea into a fully featured editor.

Alternatively add "wyswyg" class to your textarea element.

(PHPPE\Core)->run()

Synopsis

public run( string $app, string $action );

Execute a PHPPE Application.

Parameters

string $appOverride application
string $actionOverride action

Return value

None.

Examples

1
2
$phppe = include("public/index.php");
$phppe->run();

PHPPE\Core::lib()

Synopsis

public static lib( );
public static lib( string $name );
public static lib( string $name, string/object $service, string/array $dependencies );

This method is for dealing with libraries (services). Calling without argument or with exactly one argument is a query, applying more arguments define a new service.

Normally you'll never ever need to register a service. You should instead return an instance from your extension's init.php.

Parameters

string $nameName of the library / service
string/object $serviceName of the class (if it's static) or instance reference
string/array $dependenciesDependencies, comma separated string list or array

Return value

array of instancesWhen called without arguments, returns all service instances
instance or nullWhen called with a service name, returns instance for it
nullWhen called with at least two arguments, registers new service

Examples

1
2
3
4
5
6
7
8
//! query installed services
$all = Core::lib();
//! query a service instance
$gpio = Core::lib("GPIO");
//! add a new service with static class. Normally you don't need to do that
Core::lib("myService", "\PHPPE\myService");
//! add a new service with instance and dependency check. Normally you don't need to do that
Core::lib("myService", $myService, "Users,Email");

PHPPE\Core::isInst()

Synopsis

public static isInst( string $name );

Installation check, returns true if service or AddOn exists.

Parameters

string $nameName of the library / service / AddOn

Return value

booleanTrue if installed

Examples

1
2
//! check if a service is installed
$hasGpio = Core::isInst("GPIO");

PHPPE\Core::lang()

Synopsis

public static lang( string $name );

Loads a language dictionary into memory for a class (extension).

HINT: you can use the "lang" utility to generate dictionaries.

Parameters

string $nameName of the class / extension

Return value

None.

Examples

1
2
//! load a language dictionary
Core::lang("GPIO");

L()

Synopsis

L( string $phrase, ... );

Translate a text to user's language. This function is available almost everywhere: in PHP, in JavaScript and in view templates as well.

The dictionary has to be loaded with Core::lang().

You can pass arguments to the function if the translated text contains %s or %d. Note that JS version just substitutes strings, it does not format them.

Parameters

string $phrasePhrase to translate
mixed ...Variable number of arguments if phrase contains %s or %d.

Return value

stringTranslated text.

Examples

1
2
3
4
5
6
//! translate a keyword
L("myextension_mylabel");
//! translate direct text
L("Thank You!");
//! translate with arguments
L("%s logged in", $username);

PHPPE\Core::log()

Synopsis

public static log( string $weight, string $message, string $facility );

Logger. Don't forget that your messages most probably will be parsed by scripts, so use English messages if possible without translation.

When configured to log to syslog, no files will be written, and the message will be sent as:

(vhost name):PHPPE-(A|W|E|C|I|D)-(facility):(original message without line breaks)

It's easy to grep. If you also turn on trace, then the caller's filename and line will be logged as well.

Files

data/log

Parameters

string $weightCan be one of:
A - Audit (will be logged regardless to runlevel)
D - Debug (only logged at runlevel 3)
I - informational
W - Warning (some features will be disabled)
E - Error (some important part failed, but page can be still generated)
C - Critical (main part failed, the page cannot be generated)
NOTE: logging Critical message will stop PHP script execution.
string $messageThe message to log. Put in as much useful information as you can.
string $facilityWhich logfile to use. If not given, it will be autodetected after the extension name calling

Return value

None.

Examples

1
Core::log("D", "I'm running!", "myService");

See also

PHPPE\Core::isBtn()

Synopsis

public static isBtn( string $name );

Checks if the user is trying to save a form by pressing a button. This includes CSRF check as well.

HINT: this method is available as PHPPE\Filter\csrf filter.

Parameters

string $nameName of form to check, if not given, the first form is checked.

Return value

intThe serial number of the button pressed on the form, starts with 1. Zero means no button was pressed, or CSRF check failed.

Examples

1
2
3
4
5
6
7
//! typical in action handlers
if (Core::isBtn("myform")) {
    $myform = Core::req2obj("myform");
    ...
}
//! save which button was pressed
$btnNo = Core::isBtn("userform");

See also

PHPPE\Core::error()

Synopsis

public static error( );
public static error( string $message, string $field );

When called without arguments, returns error messages, otherwise adds a new error to the page or to a field.

HINT: if you want to display error messages in a view template, use <!include errorbox>

Parameters

string $messageThe error message
string $fieldIf the message is related to a field, it's name

Return value

array of messagesWhen called without arguments, returns all messages
nullWhen called with arguments, registers new message

Examples

1
2
3
4
5
6
//! query error messages
$errors = Core::error();
//! set an error message
Core::error("Something went wrong!");
//! set an error for a field
Core::error("Please fill out this field.", "myform.username");

See also

PHPPE\Core::isError()

Synopsis

public static isError( string $field );

Check if there was an error.

Parameters

string $fieldIf you are interested in a specific field, it's name

Return value

booleanTrue if there was an error

Examples

1
2
3
4
//! is there any error message?
$wasError = Core::isError();
//! is there a problem with a field?
$badInput = Core::isError("myform.username");

See also

PHPPE\Core::event()

Synopsis

public static event( string $type, array $context );

Triggers an event, and executes all event hook for it. You don't have to use this method, as events are triggered by Core.

Parameters

string $eventEvent type
array $contextOptional context. Each event type has different context.

Return value

arrayOptionally altered context

Examples

1
2
//! trigger event
list($app, $action) = Core::event("route", [$app, $action]);

See also

PHPPE\Core::validate()

Synopsis

public static validate( string $name, string $validator, boolean $required, array $arguments, array $attributes );

Adds extra validation rule to a field. Note that type of the field automaticaly adds validation.

Validator will be prefixed by "PHPPE\AddOn" and the resulting class' validate() method will be used.

Parameters

string $nameField name, like "myform.username".
string $validatorAddOn name that has the wanted validation
boolean $requiredTrue if value cannot be empty
array $argumentsArguments to the AddOn
array $attributesAttributes to the AddOn

Return value

None.

Examples

1
2
//! add extra validation to a field
Core::validate("myform.email", "filtermx");

See also

PHPPE\Core::req2arr(), PHPPE\Core::req2obj()

Synopsis

public static req2arr( string $form, array $rules );
public static req2obj( string $form, array $rules );

Get user input from dragon land with validation and return it in an associative array (or stdClass). Usually you don't have to worry about validation rules as PHPPE\View::template() will build the appropriate $rules array according to the fields used in the view.

NOTE: no need to pass rules directly, you can also use validate() to add them one-by-one.

Parameters

string $formName of the form, $_REQUEST key prefix.
array $rulesAdditional validation rules.

Return value

arrayOptionally altered context

Examples

1
2
3
4
5
//! typical in action handlers
if (Core::isBtn("myform")) {
    $myform = Core::req2arr("myform");
    ...
}

See also

PHPPE\Core::arr2str(), PHPPE\Core::obj2str()

Synopsis

public static arr2str( array $subject, string/array $exclude, string $separator );
public static obj2str( object $subject, string/array $exclude, string $separator );

Convert an associative array or object into a properly escaped string, appropriate for XML attributes or SQL queries.

Parameters

array/object $subjectWhat you want to convert
string/array $excludeComma separated list or array of keys to exclude.
string $separatorString to use between key=value pairs. Defaults to space.

Return value

stringProperly assembled string with escaped values.

Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
/* $obj => stdClass Object
   (
    [Id] => 123
    [Name] => Something
    [AnotherField] => good
    [Flag] => 1
   ) */

//! for SQL
Core::obj2str( $obj ) => "Id='123',Name='Something',AnotherField='good',Flag='1'"
Core::arr2str( $obj, "Id,Flag" ) => "Name='Something',AnotherField='good'"
//! for XML attributes
Core::obj2str( $obj, "", " ") => "Id='123' Name='Something' AnotherField='good' Flag='1'"

See also

PHPPE\Core::val2arr()

Synopsis

public static val2arr( string $expression, string $separator );

Get the value of a templater expression and convert it into an array.

Parameters

string $expressionA templater expression
string $separatorString to use to split if expression evaluates to a string. Defaults to comma.

Return value

arrayReturns an array.

Examples

1
2
3
4
5
//! $obj->field = [ "a", "b", "c" ]
$arr = Core::val2arr( "obj.field" );
//! $obj['field'] = "a;b;c"
$arr = Core::val2arr( "obj.field", ";" );
//! $arr = [ "a", "b", "c" ] in both cases

See also

PHPPE\Core::tre2arr()

Synopsis

public static tre2arr( array $subject, string $prefix );
public static tre2arr( array $subject, string $prefix, string $suffix );

Flat a tree stored in an array into a one-level array. It has two operation modes:
  1. without suffix it generates arrays for SELECT boxes.
  2. with suffix given, it generates arrays for XML trees.
See examples below.

Parameters

array $subjectTree array, children arrays in "_" as returned by PHPPE\DS::tree()
string $prefixPrefix string to represent recursion level. In select box mode it will be repeated, for XML trees it has to have a '%d' that will be substituted.
string $suffixSuffix for XML mode

Return value

arrayReturns a flat array.

Examples

1
2
3
4
5
//! option lists for select boxes
$arr1 = Core::tre2arr( $tree, "&nbsp;&nbsp;" );
//! XML mode, for AJAX loaded category trees
$arr2 = Core::tre2arr( $tree, "<div data-level=%d>", "</div>" );
//! note the differences in the 'name' item below:

print_r($tree); // recursive tree, input
Array
(
[0] => stdClass Object
  (
    [id] => 1
    [name] => item1
  )

[1] => stdClass Object
  (
    [id] => 2
    [name] => item2
    [_] => Array
      (
        [0] => stdClass Object
          (
            [id] => 21
            [name] => item2.1
            [_] => Array
              (
                [0] => stdClass Object
                  (
                    [id] => 211
                    [name] => item2.1.1
                  )
              )
          )

        [1] => stdClass Object
          (
            [id] => 22
            [name] => item2.2
          )

        [2] => stdClass Object
          (
            [id] => 23
            [name] => item2.3
          )
       )
    )

[2] => stdClass Object
  (
    [id] => 3
    [name] => item2
  )
)
print_r($arr1); // select box
Array
(
    [0] => stdClass Object
        (
            [id] => 1
            [name] => item1
        )

    [1] => stdClass Object
        (
            [id] => 2
            [name] => item2
        )

    [2] => stdClass Object
        (
            [id] => 21
            [name] => &nbsp;&nbsp;item2.1
        )

    [3] => stdClass Object
        (
            [id] => 211
            [name] => &nbsp;&nbsp;&nbsp;&nbsp;item2.1.1
        )

    [4] => stdClass Object
        (
            [id] => 22
            [name] => &nbsp;&nbsp;item2.2
        )

    [5] => stdClass Object
        (
            [id] => 23
            [name] => &nbsp;&nbsp;item2.3
        )

    [6] => stdClass Object
        (
            [id] => 3
            [name] => item2
        )
)
print_r($arr2); // XML
Array
(
    [0] => stdClass Object
        (
            [id] => 1
            [name] => item1
        )

    [1] => stdClass Object
        (
            [id] => 2
            [name] => item2
<div data-level=1>
        )

    [2] => stdClass Object
        (
            [id] => 21
            [name] => item2.1
<div data-level=2>
        )

    [3] => stdClass Object
        (
            [id] => 211
            [name] => item2.1.1
</div>
        )

    [4] => stdClass Object
        (
            [id] => 22
            [name] => item2.2
        )

    [5] => stdClass Object
        (
            [id] => 23
            [name] => item2.3
</div>
        )

    [6] => stdClass Object
        (
            [id] => 3
            [name] => item2
        )
)

See also

PHPPE\Core::bm()

Synopsis

public static bm( string $name );

Sets a benchmark point in code. Every point is measured relative to it's accessor point.

Parameters

string $nameUnique name.

Return value

None.

Examples

1
2
3
// ...do time consuming calculation here...
//! mark a spot in code
Core::bm("calculation");

See also

PHPPE\ClassMap::has()

Synopsis

public static has( string $class, string $method );

Checks if a class can be autoloaded or if it has a method.

Parameters

string $classClass name
string $methodMethod name

Return value

booleanTrue is class can be autoloaded (and if it has a method)

Examples

1
2
3
4
//! check if there's a class
ClassMap::has("Extension");
//! check if there's a class and also it has a given method
ClassMap::has("Extension", "init");

See also

PHPPE\ClassMap::ace()

Synopsis

public static ace( );

Return valid Access Control Entries.

Parameters

None.

Return value

arrayList of valid ACEs

Examples

1
$ace = ClassMap::ace();

See also

PHPPE\ClassMap::map()

Synopsis

public static map( );

Return classes along with the files they were defined in.

Parameters

None.

Return value

arrayClasses as keys and files as values

Examples

1
$map = ClassMap::map();

See also

(PHPPE\Model)->__construct()

Synopsis

new Model( int/string $id );
new Model( mixed $properties );

Loads object properties from the argument (if it's an array or object) or from database (if argument is an id).

Parameters

int/string $idIdentifier
mixed $propertiesAssociative array or object, initial properties

Return value

mixedA Model instance

Examples

1
2
3
4
//! load a model instance by id
$user= new Users(1);
//! load by arguments
$user= new Users(["email"=>"some@where.tld"]);

See also

(PHPPE\Model)->load()

Synopsis

public load( int $id );
public load( string $searchphrase, string $where, string $order );

Loads object properties from the first database record found.

NOTE: regardless which columns the database scheme has, it only loads the properties that are defined in the Model.

Parameters

int $idIdentifier
string $searchphraseString to search for
string $whereWhere clause
string $orderWhen using search, defines the ordering

Return value

booleanTrue on success

Examples

1
2
3
4
//! load a model instance by id
$user->load(1);
//! load by searching for a record
$user->load("me@localhost", "email=?", "created DESC");

See also

(PHPPE\Model)->save()

Synopsis

public save( boolean $new );

Saves a Model object into database. If the model has no identifier, it will be INSERTed, otherwise UPDATEd. With flag given it will INSERT the object with the given id.

Parameters

boolean $newForce INSERT of a new object with id

Return value

booleanTrue on success, the $obj->id porterty also updated on INSERT

Examples

1
2
3
4
5
//! save a model, INSERT or UPDATE depending on whether it has id
$user->save();
//! save as a new object with INSERT, despite it has an id and should be an UPDATE
$user->id = 1;
$user->save(true);

See also

PHPPE\Model::find()

Synopsis

public find( string $searchphrase, string $where, string $order, string $fields );

Finds instances of a model in database.

NOTE: unlike load(), this method will return all columns defined by the scheme.

Parameters

string $searchphraseString to search for
string $whereWhere clause, defaults to "id=?"
string $orderWhen using search, defines the ordering
string $fieldsWhich columns to return, defaults to all

Return value

arrayResults

Examples

1
2
3
4
//! get all users with all fields
$users = Users::find();
//! get email address for all users named Joe
$users = Users::find( DS::like("Joe"), "name like ?", "created DESC", "name,email");

See also

PHPPE\Http::url(), url()

Synopsis

public static url( string $app, string $action );

Generate a permanent url for the current (or other) application and it's action. It will use Core properties to do that, namely $core->sec and $core->base.

Parameters

string $appOverride application
string $actionOverride action

Return value

stringURL

Examples

1
2
url(); // => "http://localhost/myapp/myaction"
url("users", "add");  // => "http://localhost/users/add"

See also

PHPPE\Http::redirect()

Synopsis

public static redirect( string $url, boolean $save );

Redirects user. Without argument, to the current page or to the saved URL.

IMPORTANT: always use URL relative to base href for inside page reditections. Only start it by "http" when redirecting to another site.

Parameters

string $urlURL to redirect to.
boolean $saveSave the current URL for later redirection

Return value

None.

Examples

1
2
3
4
5
6
7
8
9
10
//! these two are identical when there's no saved URL
Http::redirect();
Http::redirect(Core::$core->app."/".Core::$core->action);
//! redirect to the home page
Http::redirect("/");
//! if user is not logged in, save the current URL and redirect to login page
if (!Core::$user->id)
  Http::redirect("login", true);
//! redirect to another site
Http::redirect("https://netbank.com/payment?return=".urlencode(url())."&shopid=1234");

See also

PHPPE\Http::get()

Synopsis

public static get( string $url, array $post, int $timeout );

Get content by making a HTTP request. This is better than file_get_contents() and CURL, because it handles redirections and keep track of cookie changes as well. Also it automatically passes the user's preferred language.

Parameters

string $urlURL to get.
array $postPost parameters in an associative array
int $timeoutTimeout in seconds, defaults to 3

Return value

stringContents of the HTTP response, without header.

Examples

1
2
3
4
//! make a GET request
$page = Http::get("https://webservice.org/api");
//! make a POST request
$page = Http::get("https://webservice.org/api", ["apikey"=>"1234", "func"=>"list"]);

See also

PHPPE\Http::route()

Synopsis

public static route( string $urlmask, string $class, string $method, string/array $filters);
public static route( stdClass( url => $urlmask, name => $class, action => $method, filters => $filters ) );
public static route( [ "url" => $urlmask, "name" => $class, "action" => $method, "filters" => $filters ] );
public static route( [ $urlmask => [ $class, $method, $filters ] ] );
public static route( [ $urlmask => [ $class, $method, $filters ], ... ] );

Add new URL route to the routing table. Call it from init.php or from extension init() method.

IMPORTANT: always use URL relative to base href.

Parameters

string $urlmaskRegular expression to catch URL. If it has pharenthesis, matched pattern will be passed to action handler.
string $classController class to handle the matched URL.
string $methodController method (action handler) to handle the URL.
string/array $filtersComma separated list of filters or ACEs checks on the route

Return value

None.

Examples

See URL routing.

See also

PHPPE\Http::urlMatch()

Synopsis

public static urlMatch( string $app, string $action, string $url );

Returns controller and action handler name for current (or given) URL.

Parameters

string $appDefault controller class (if no routes match)
string $actionDefault controller method (if no routes match)
string $urlURL to match, defaults to current URL

Return value

array [
 string $class,
 string $method,
 array $arguments
]
Controller according to matched URL. If there was a match, but failed due to a filter, $class will be set to "403"

Examples

1
list($app, $action, $args = Http::urlMatch("Content", "action", "url/to/match");

See also

PHPPE\DS::db()

Synopsis

public static db( );
public static db( string $pdodsn, object $instance );

When called without arguments, returns the current PDO instance. Otherwise connects a new database as datasource.

NOTE: the dsn given in configuration's 'db' key will be automatically connected during framework initialization.

Parameters

string $pdodsnPDO Data Source Name. Note: if otherwise not supported, you can append "@username:password"
object $instanceAny PDO compatible object associated with the dsn

Return value

PDO/nullPDO instance for current selector when called without arguments or null
intNewly allocated data source's selector

Examples

1
2
3
4
5
6
7
//! query current PDO instance
$pdo = DS::db();
//! connect new PDO instances
$selector = DS::db("sqlite:data/db.sqlite");
$selector = DS::db("mysql:host=localhost;dbname=db;charset=utf8@user:pass");
//! connect an arbitrary, but PDO compatible class
$selector = DS::db("salesforce:", $salesforce);

See also

PHPPE\DS::ds()

Synopsis

public static ds( );
public static ds( int $selector );

When called without arguments, returns the current datasource selector. With argument given selects a new datasource.

NOTE: You have to first connect data sources with db().

Parameters

int $selectorDatasource selector (as returned by db() call)

Return value

intCurrent (or newly selected) data source's selector

Examples

1
2
3
4
//! query current data source
$selector = DS::ds();
//! select a new data source
$selector = DS::ds(2);

See also

PHPPE\DS::like(), PHPPE\DB::like()

Synopsis

public static like( string $str );

Converts a user provided string into an sql-safe, search ready like phrase.

Parameters

string $strUser provided string

Return value

stringSql-safe like phrase

Examples

1
2
DS::like("search this"); // => "%search%this%"
DB::like("search this"); // => "%search%this%"

See also

PHPPE\DS::exec()

Synopsis

public static exec( string $sql, array $args );

Executes an sql query on current datasource.

NOTE: if the execution fails due to missing table, this method will look for the scheme, load it, and execute the query again transparently.

Parameters

string $sqlSql query or sentance with '?' placeholders
array $argsValues for the placeholders

Return value

arrayFor SELECT queries, result set in array of associative array records
intNumber of affected rows for all other sentances

Examples

1
2
3
$results = DS::exec("SELECT * FROM users");
$rowno = DS::exec("DELETE FROM users WHERE name like ?", [ "Joe" ]);
$rowno = DS::exec("INSERT INTO users SET ".Core::obj2str(Core::$user));

See also

PHPPE\DS::query()

Synopsis

public static query(string/array $fields, string $table, string $where, string $groupBy, string $orderBy, int $offset, int $limit, array $args );

Executes an sql SELECT query on current datasource. This is a low level method, you should use PHPPE\DB instead.

Parameters

string/array $fieldsComma separated list or array
string $tableTable or joins
string $whereWhere clause
string $groupByGroup by fields
string $orderByOrder by fields
int $offsetNumber of records to skip at the begining
int $limitMaximum number of records to return
array $argsValues for the placeholders

Return value

arrayResult set in array of associative array records

Examples

1
2
3
4
5
6
7
8
9
10
$results = DS::query(
    "a.*,b.topic",
    "forum_post a LEFT JOIN forum_topic b ON a.topicId=b.id",
    "a.userId=?",
    "b.id",
    "a.userId DESC",
    0,
    100,
    [ Core::$user ]
);

See also

PHPPE\DS::fetch()

Synopsis

public static fetch(string/array $fields, string $table, string $where, string $groupBy, string $orderBy, array $args );

Executes an sql SELECT query on current datasource and returns the first record. This is a low level method, you should use PHPPE\DB instead.

Parameters

string/array $fieldsComma separated list or array
string $tableTable or joins
string $whereWhere clause
string $groupByGroup by fields
string $orderByOrder by fields
array $argsValues for the placeholders

Return value

stdClassObject record

Examples

1
2
3
4
5
6
7
8
$record = (array)DS::fetch(
    "a.*,b.topic",
    "forum_post a LEFT JOIN forum_topic b ON a.topicId=b.id",
    "a.userId=?",
    "b.id",
    "a.userId DESC",
    [ Core::$user ]
);

See also

PHPPE\DS::field()

Synopsis

public static field(string/array $fields, string $table, string $where, string $groupBy, string $orderBy, array $args );

Executes an sql SELECT query on current datasource and returns the first column of the first record. This is a low level method, you should use PHPPE\DB instead.

Parameters

string/array $fieldsComma separated list or array
string $tableTable or joins
string $whereWhere clause
string $groupByGroup by fields
string $orderByOrder by fields
array $argsValues for the placeholders

Return value

stringValue of the first column of the first record

Examples

1
$time = DS::field("CURRENT_TIMESTAMP");

See also

PHPPE\DS::tree()

Synopsis

public static tree( string $sql, int $parentId );

Executes several sql queries on current datasource to return a tree (or sub-tree). You can use Core::tre2arr() to convert the result into an option list or XML tree.

Parameters

string $sqlSql query exactly one '?' placeholder to mark the place of parent id
int $parentIdParent Id for which the sub-tree is queried. 0 means return the whole tree

Return value

recursive arrayArray of associative array records, child branches in "_"

Examples

1
$subTree = DS::tree("SELECT id,parentId,name FROM category WHERE id!=parentId AND parentId=?", 2);

See also

PHPPE\DS::bill()

Synopsis

public static bill( );

Returns the time consumed by database queries in second with microsec precision.

Parameters

None.

Return value

floatTime consumed by database queries in second

Examples

1
2
3
4
$start = DS::bill();
//! do some database stuff here
DS::exec("DELETE FROM users");
$benchmark = DS::bill() - $start;

See also

PHPPE\Cache::set()

Synopsis

public static set(string $key, mixed $value, int $ttl, boolean $force );

Saves a value to cache for key.

Parameters

string $keyKey for reference
mixed $valueValue to be stored
int $ttlTime to live in cache
boolean $forceSave the value in cache even if Core::$core->nocache is set

Return value

booleanTrue on success

Examples

1
Cache::set("rssnews", $rssNews, 60);

See also

PHPPE\Cache::get()

Synopsis

public static get(string $key, boolean $force );

Retrive a value from cache for key.

Parameters

string $keyKey for reference
boolean $forceLook up cache even if Core::$core->nocache is set

Return value

mixedStored value or null on cache miss

Examples

1
$rssNews = Cache::set("rssnews");

See also

PHPPE\Assets::minify()

Synopsis

public static minify( string $subject, string $type );

Compress code by removing whitespace characters. If installed, it will use CSSMin and JSMin. Minifying can be turned off by setting Core::$core->nominify in configuration.

Parameters

string $subjectString to compress
string $typeKind, one of 'css', 'js' or 'php'

Return value

stringMinified subject. If type unrecodnized, the original string

Examples

1
2
3
4
$compressed = Assets::minify("
.mystyle1 {
    font-size: 16px;
}", "css");

PHPPE\View::assign()

Synopsis

public static assign( string $name, mixed $value );

Assign variable to templater.

NOTE: the controller class' properties can be referenced in templates without assigning, as well as Core::$core, Core::$user and Core::$client.

Parameters

string $nameName of the variable
mixed $valueValue to assign

Return value

None.

Examples

1
2
3
View::assign("myModel", $myModel);
//! then reference in templates as:
//! <!=myModel.name>

See also

PHPPE\View::css()

Synopsis

public static css( );
public static css( string $file );

When called without parameters, returns assigned stylesheets, add a new css to templater otherwise.

NOTE: use paths relative to your extension's css/ directory. For dynamic assets don't use '.php' extension

Parameters

string $fileName of the stylesheet to add

Return value

arrayWhen called without parameters, returns assigned stylesheets

Examples

1
2
$css = View::css();
View::css("mystyle.css");

See also

PHPPE\View::jslib()

Synopsis

public static jslib( );
public static jslib( string $file, string $initcode, int $priority );

When called without parameters, returns assigned JavaScript libraries, add a new library to templater otherwise.

NOTE: use paths relative to your extension's js/ directory. For dynamic assets don't use '.php' extension

Parameters

string $fileName of the library to add
string $initcodeCode to be executed on init event to initialize the library
int $priorityDefine in which order to be added.
0 = JavaScript framework libraries like jQuery
1-9 = JavaScript basic libraries like bootstrap
>9 = Normal JavaScript libraries

Return value

arrayWhen called without parameters, returns assigned libraries

Examples

1
2
$jslibs = View::jslib();
View::jslib("mylib.js", "mylib.init()");

See also

PHPPE\View::js()

Synopsis

public static js( string $function, string $code, boolean $append );

Adds a JavaScript function to output.

NOTE: always use append on 'init()' function!

Parameters

string $fileName of the function
string $codeJavaScript code. It will be minified automatically
boolean $appendIf true, the code will be added to existing function code

Return value

None.

Examples

1
2
View::js("myfunc(name)", "alert('Hello '+name+'!');");
View::js("init()", "alert('Hello World!');", true);

See also

PHPPE\View::template()

Synopsis

public static template( string $template, array $variables );

Load, parse and evaluate a template.

Parameters

string $templateName of the template
array $variablesAdditional view variables (name=>value) to assign

Return value

stringParsed template

Examples

1
$html = View::template("index");

See also

PHPPE\View::get()

Synopsis

public static get( string $template );

Load and return template as is, without parsing.

Parameters

string $templateName of the template

Return value

stringRaw, unparsed template, from cache is available

Examples

1
$tpl = View::get("index");

See also

PHPPE\View::getval()

Synopsis

public static getval( string $expression );

Evaluate a templater expression and return the result. Used by <!=...> tag.
In nested layout iterations, you can refer to fields of outter iteration with prefix parent..

What's more, you can use special variables:
IDX - iteration counter
ODD - true if iteration counter is odd
KEY - the key of the array you're iteration on
VALUE - the value of the array element you are currently iteration on

For nested itration blocks, you can use parent prefix, eg.: parent.IDX to get the index counter of the outer foreach.

Parameters

string $expressionTemplater expression

Return value

mixedEvaluated expression

Examples

1
$libs = View::getval("core.libs()");

In view templates:

<!=core.libs()>
<!=sprintf("%s / %s", parent.name, name)>

See also

PHPPE\View::e()

Synopsis

public static e( string $weight, string/array $message, string $facility );

Properly format an error message, depending on Core::$core->output.

Parameters

string $weightOne of A,W,E,C,I,D. See Core::log()
string/array $messageThe error message or more messages
string $facilityThe facility or extension that the error is generated for

Return value

stringFormatted message

Examples

1
echo( View::e("E", L("File not found"), "myService") );

See also

PHPPE\View::picture()

Synopsis

public static picture( string $source, string $target, int $maxWidth, int $maxHeight, boolean $crop, boolean $lossless, string $watermark, int $maxSize, int $minQuality );

Resize, crop and watermark image. Will reduce file size by decreasing quality to the given limit.

When crop, it will resize the image to the width or to the height which is smaller, then cut along the larger one. The resulting image's dimensions will be exactly maxWidth x maxHeight.

In resize mode it will resize the image to fit in maxWidht x maxHeight, but keeping original picture's aspect ratio, therefore the resulting image could be smaller in dimensions than maxWidth x maxHeight.

Parameters

string $sourceThe image's path and filename you want to resize/crop.
string $targetDestination path and filename.
int $maxWidthMaximum width of the new image.
int $maxHeightMaximum height of the new image.
boolean $cropIf true, it will crop the image, otherwise it resizes keeping aspect ratio. Defaults to false (resize)
boolean $losslessWhether to use lossless compression (png) on resuting image. If false, it will save jpeg. Defaults to true (png)
string $watermarkPath and filename of watermark logo. If given it has to be a semi-transparent png with alpha channel.
int $maxSizeMaximum file size of the resulting image in kilobytes. If it's bigger, picture() will reduce it's quality until file size or minimum quality requirements are met. Defaults to 8192 (8MiB)
int $minQualityMinimum image quality when reducing file size (1-10). Defaults to 5 (50%).

Return value

booleanTrue on success. On failure the original image will be simply copied as target.

Examples

1
2
3
4
5
$f = $_FILES["myFile"];
//! creating fullscreen jpeg image with watermark
View::picture($f["tmp_name"], "data/large/".$f["name"], 1024, 768, false, false, "data/companylogo.png" );
//! creating small (64k) thumbnail for it as well
View::picture($f["tmp_name"], "data/thumb/".$f["name"], 128, 128, true, true, "", 64 );

PHPPE\Tools::rmdir()

Synopsis

public static rmdir( string $directory );

Recursively remove a directory with all of it's contents.

Parameters

string $directoryDirectory to remove

Return value

booleanTrue on success

Examples

1
Tools::rmdir("data/cache");

See also

PHPPE\Tools::untar()

Synopsis

public static untar( string $archive, string $filename );
public static untar( string $archive, callback [ string $class, string $method ] );

Useful tool to deal with archive files. Autodetects file format and can handle PKZIP, tar, tar gz, tar bz2, cpio, cpio gz, cpio bz2 and pax.

When second argument is a string, it will look for the file in the archive and returns it's contents.

When second argument is an array with a class + method pair, it will call it for every file in the archive.

Parameters

string $archivePath and file of the archive
string $filenameName of the file to extract
callback [ $class, $method ]Callback method that will be called on every file in the archive.

Callback arguments

string $filenameName of the file
string $contentUncompressed content of the file

Return value

stringContents of the file or null

Examples

1
2
3
4
5
6
$json = Tools::untar("phppe3_core.tgz", "composer.json");
Tools::untar("sitebuild.zip", ["myClass", "saveFile"]);
//! and the callback in the class myClass
function saveFile($filename, $content) {
    file_put_contents(".tmp/extracted/".$filename, $content);
}

See also

PHPPE\Tools::ssh()

Synopsis

public static ssh( string $command, string $input, string $localCommand );
public static ssh( "rsync", array $dirs, string $remoteDir );

Execute a command on a remote server that's defined in current user's preferences in Core::$user->data['remote']. For details, see Extensions.

Special case when command is "rsync". In that case it will synchronise directories to a remote site. If you want to push all files, use PHPPE\Tools::copy().

Parameters

string $commandCommand to execute remotely
string $input / $dirsIf given, it will send to remote command's stdin. If command is "rsync", then an array of filenames to sync.
string $localCommand / $remoteDirIf given, it will be executed locally and it's output will be piped to the remote command's stdin through SSH tunnel. With "rsync", the directory to sync to on remote host.

Return value

stringOutput of the remote command

Examples

1
2
3
4
5
6
7
8
//! execute a command remotely and get it's output
$files = Tools::ssh("ls -la");
//! save a file remotely
Tools::ssh("cat >composer.json", file_get_contents("composer.json") );
//! pipe local command's standard output to remote command's standard input
Tools::ssh("cat >installedby.txt", "", "whoami");
//! synchronize directories securely
Tools::ssh("rsync", [ "/usr/git/myRepo", "/var/www" ], "/var/production/www");

See also

PHPPE\Tools::copy()

Synopsis

public static copy( string/array $files, string $destination );

Copy files to a remote server that's defined in current user's preferences in Core::$user->data['remote']. For details, see Extensions.

With copy you can push whole directories to the remote site. If you want to lower the load, use PHPPE\Tools::ssh('rsync').

Parameters

string/array $filesPath and filename of the files to copy
string $destinationDestination directory on remote server.

Return value

stringEmpty string if everything went well, error message otherwise

Examples

1
2
3
4
//! copy a file to remote server
$error = Tools::copy("composer.json");
//! copy more files to remote server to a specific directory
$error = Tools::copy(["data/something", "data/sqlite.db"], "data/incoming");

See also

PHPPE\Tools::bg()

Synopsis

public static bg( string $className, string $methodName, mixed $argument, integer $interval );

Execute a background job in every nth second using webserver's rights.

Parameters

string $classNameName of the class to call
string $methodNameName of the method to call
mixed $argumentAny arbitrary data that is passed to the method.
integer $intervalRunning interval in secs.

Return value

None.

Examples

1
2
//! call $myClass->myMethod('cleanup','me') every 5 secs.
\PHPPE\Tools::bg( "myClass", "myMethod", [ "cleanup", "me"  ], 5 /*secs*/ );

See also

(PHPPE\Users)->has()

Synopsis

final public has( string/array $ace );

Checks if the user has the given Access Control Entry.

Parameters

string/array $acePipe separated list or array of Access Control Entries

Return value

booleanTrue if user has the access

Examples

1
$loggedin = Core::$user->has("loggedin");

See also

(PHPPE\Users)->grant()

Synopsis

final public grant( string/array $ace );

Grant access to the user.

Parameters

string/array $acePipe separated list or array of Access Control Entries

Return value

None.

Examples

1
Core::$user->grant("webadm|siteadm");

See also

(PHPPE\Users)->clear()

Synopsis

final public clear( string/array $ace );

Revoke access from the user.

Parameters

string/array $acePipe separated list or array of Access Control Entries

Return value

None.

Examples

1
Core::$user->clear("webadm|siteadm");

See also

(PHPPE\Users)->login()PHPPE Pack

Synopsis

public login( string $username, string $password );

Authenticate user.

Parameters

string $usernameName of the user
string $passwordPassword of the user

Return value

booleanTrue if authentication was successful

Examples

1
2
3
if (Core::$user->login("admin", "changeme") ) {
    //! user logged in successfully
}

See also

(PHPPE\Users)->logout()PHPPE Pack

Synopsis

public logout( );

Log out user.

Parameters

None.

Return value

None.

Examples

1
Core::$user->logout();

See also

PHPPE\Registry::set()PHPPE Pack

Synopsis

public static set( string $key, mixed $value );

Sets a value for key in registry.

Parameters

string $keyKey to use
mixed $valueValue to store

Return value

booleanTrue if value stored successfully

Examples

1
Registry::set("myService_lastRun", time() );

See also

PHPPE\Registry::get()PHPPE Pack

Synopsis

public static get( string $key, mixed $default );

Get a value for key from registry.

Parameters

string $keyKey for which to get value
mixed $defaultDefault value if key not found

Return value

mixedValue or null

Examples

1
$lastrun = Registry::get("myService_lastRun", 0);

See also

PHPPE\Registry::del()PHPPE Pack

Synopsis

public static del( string $key );

Remove a key and it's value from registry.

Parameters

string $keyKey to delete

Return value

None.

Examples

1
Registry::del("myService_lastRun");

See also

PHPPE\DB::select()PHPPE Pack

Synopsis

public static select( string $table, string $alias );

Create a SELECT query sentance.

Parameters

string $tableName of the table to work with
string $aliasAlias for the table

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::select("forum_topic", "f")
    ->fields("f.*")
    ->execute();

See also

PHPPE\DB::update()PHPPE Pack

Synopsis

public static update( string $table, string $alias );

Create an UPDATE sentance.

Parameters

string $tableName of the table to work with
string $aliasAlias for the table

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::update("forum_topic", "f")
    ->fields( ["f.id", "f.title"])
    ->execute([    3,  "New title"]);

See also

PHPPE\DB::insert()PHPPE Pack

Synopsis

public static insert( string $table, string $alias );

Create an INSERT INTO sentance.

Parameters

string $tableName of the table to work with
string $aliasAlias for the table

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::insert("forum_topic", "f")
    ->fields( ["f.id", "f.title"])
    ->execute([    3,  "New title"]);

See also

PHPPE\DB::replace()PHPPE Pack

Synopsis

public static replace( string $table, string $alias );

Create a REPLACE INTO sentance.

Parameters

string $tableName of the table to work with
string $aliasAlias for the table

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::replace("forum_topic", "f")
    ->fields( ["f.id", "f.title"])
    ->execute([    3,  "New title"]);

See also

PHPPE\DB::delete()PHPPE Pack

Synopsis

public static delete( string $table, string $alias );

Create a DELETE sentance.

Parameters

string $tableName of the table to work with
string $aliasAlias for the table

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::delete("forum_topic")
    ->where( [["id", "=", 3]])
    ->execute();

See also

PHPPE\DB::truncate()PHPPE Pack

Synopsis

public static truncate( string $table );

Create a TRUNCATE sentance.

Parameters

string $tableName of the table to work with
string $aliasAlias for the table

Return value

DBSQL sentance object

Examples

1
2
$result = DB::truncate("forum_topic")
    ->execute();

See also

(PHPPE\DB)->table()PHPPE Pack

Synopsis

public table( string $table, string $alias );

Add a new table multiplication to a sentance.

NOTE: use JOIN instead of table multiplication if possible as they are less resource consuming.

Parameters

string $tableName of the table to work with
string $aliasAlias for the table

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::select("forum_topic", "ft")
    ->table( "forum_posts", "fp")
    ->execute();

See also

(PHPPE\DB)->join()PHPPE Pack

Synopsis

public join( string $type, string $table, string $condition, string $alias );

Add a new table by joining it to a sentance.

Parameters

string $typeOne of 'INNER', 'CROSS', 'LEFT', 'RIGHT'.'OUTER', 'NATURAL LEFT', 'NATURAL RIGHT', 'NATURAL LEFT OUTER', 'NATURAL RIGHT OUTER'
string $tableName of the table to work with
string $conditionJoin condition
string $aliasAlias for the table

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::select("forum_posts", "fp")
    ->join( "LEFT", "forum_topic", "fp.topicId=ft.id", "ft")
    ->execute();

See also

(PHPPE\DB)->fields()PHPPE Pack

Synopsis

public fields( string/array $fields );

Add fields to a sentance. If you have field names in array keys, you'll need with().

Parameters

string/fields $fieldsComma separated list or array of fields to add. Their values should be passed as arguments with execute().

Return value

DBSQL sentance object

Examples

1
2
3
4
5
6
7
$result = DB::update("forum_topic", "f")
    ->fields( ["f.id", "f.title"])
    ->execute([    3,  "New title"]);
// note the with() method
$result = DB::update("forum_topic", "f")->with(
            [  "f.id" => 3,
               "f.title" => "New title"  ]);

See also

(PHPPE\DB)->where()PHPPE Pack

Synopsis

public where( string/array $wheres, string $operator );

Add WHERE clause(s) to a sentance. If you want to filter after grouping, you should use having().

Parameters

string/array $wheresWhere clase expression(s) to add
string $operatorOperator, one of 'AND' or 'OR'. Defaults to 'AND'

Return value

DBSQL sentance object

Where clauses

stringSimpliest form, like 'id=?'
array of stringsMore simple form clauses concatenated by the operator, like [ 'id=?', 'name=?' ]
array of arraysSeparated form clauses concatenated by operator, like [ ['id', '=', '?'], ['name', '=', $value] ]
Here the 2nd item can be one of: '=', '!=', '<', '<=', '>', '>=', 'LIKE', 'RLIKE', 'IS NULL', 'IS NOT NULL'
And the 3rd value will be quoted automatically if it's not a placeholder, and if 2nd argument is a LIKE, will be also converted with DB::like().
NOTE: the [[ field, operator, value ]] form will quote the value for you

Examples

1
2
3
$result = DB::delete("forum_topic")
    ->where( [["id", "=", 3]])
    ->execute();

See also

(PHPPE\DB)->orderBy()PHPPE Pack

Synopsis

public orderBy( string/array $fields );

Add ORDER BY fields to a sentance.

Parameters

string/fields $fieldsComma separated list or array of fields to order by

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::select("forum_topic", "f")
    ->orderBy( ["f.id", "f.title DESC"] )
    ->execute();

See also

(PHPPE\DB)->groupBy()PHPPE Pack

Synopsis

public groupBy( string/array $fields );

Add GROUP BY fields to a sentance.

Parameters

string/fields $fieldsComma separated list or array of fields to group by

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::select("forum_topic", "f")
    ->groupBy( ["f.category"] )
    ->execute();

See also

(PHPPE\DB)->having()PHPPE Pack

Synopsis

public having( string/array $wheres, string $operator );

Add HAVING clause(s) to a sentance.

Parameters

string/array $wheresWhere clase expression(s) to add
string $operatorOperator, one of 'AND' or 'OR'. Defaults to 'AND'

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::select("forum_topic")
    ->having( [["id", "=", 3]])
    ->execute();

See also

(PHPPE\DB)->limit()PHPPE Pack

Synopsis

public limit( int $limit );

Add LIMIT to a sentance. Set the maximum number of rows returned.

Parameters

int $limitMaximum number of rows

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::select("forum_topic", "f")
    ->limit( 100 )
    ->execute();

See also

(PHPPE\DB)->offset()PHPPE Pack

Synopsis

public offset( int $offset );

Add OFFSET to a sentance. Skip the first given number rows returned.

Parameters

int $offsetNumber of rows to skip

Return value

DBSQL sentance object

Examples

1
2
3
$result = DB::select("forum_topic", "f")
    ->limit( 100 )->offset( 10 )
    ->execute();

See also

(PHPPE\DB)->sql()PHPPE Pack

Synopsis

public sql( );

Convert SQL sentance object into a string.

NOTE: this method is also called by the magic __toString() method

Parameters

None.

Return value

stringSQL sentance string

Examples

1
2
3
4
$sql = DB::delete("forum_topic")
    ->where( [["id", "=", 3]])
    ->sql();
echo ( DB::delete("forum_topic") );

See also

(PHPPE\DB)->execute()PHPPE Pack

Synopsis

public execute( array $arguments, int $selector );

Execute the SQL sentance on a datasource. If you have a key value array, you'll need with()

Parameters

array $argumentsValues for the placeholders. Field names passed with fields().
string $selectorExecute on a specific datasource rather than the current one

Return value

arrayIf the sentance was a SELECT query, the result set
intOtherwise the number of affected rows

Examples

1
2
3
$result = DB::select("forum_topic", "ft")
    ->table( "forum_posts", "fp")
    ->execute();

See also

(PHPPE\DB)->with()PHPPE Pack

Synopsis

public with( array $values, int $selector );

Execute the SQL sentance on a datasource with given values. The with() method basicaly is the same as calling fields() and execute().

Parameters

array $valuesField names in array keys
string $selectorExecute on a specific datasource rather than the current one

Return value

arrayIf the sentance was a SELECT query, the result set
intOtherwise the number of affected rows

Examples

1
2
3
4
5
6
7
$result = DB::update("forum_topic", "f")->with(
            [  "f.id" => 3,
               "f.title" => "New title"  ]);
// note the fields() method
$result = DB::update("forum_topic", "f")
    ->fields( ["f.id", "f.title"])
    ->execute([    3,  "New title"]);

See also

(PHPPE\Email)PHPPE Pack

Synopsis

$email = new Email( );
$email = new Email( string $saved );

Creates a new Email object.

Parameters

string $savedEmail dump previously saved by get().

Return value

EmailEmail object

Examples

1
$email = new Email();

See also

(PHPPE\Email)->getPHPPE Pack

Synopsis

public get( );

Dumps and Email object to be stored in database. You can recreate the Email object by providing this dump to a new instance.

Parameters

None.

Return value

stringJSON encoded reprezentasion of the Email object

Examples

1
2
$email = new Email();
$dump = $email->get();

See also

(PHPPE\Email)->toPHPPE Pack

Synopsis

public to( string $address );

Set primary recipient of the email.

Parameters

string $addressEmail address

Return value

EmailEmail object

Examples

1
$email->to("SomeBody <somebody@somewhere.com>");

See also

(PHPPE\Email)->replyToPHPPE Pack

Synopsis

public replyTo( string $address );

Set reply address of the email.

Parameters

string $addressEmail address

Return value

EmailEmail object

Examples

1
$email->replyTo("SomeBody <somebody@somewhere.com>");

See also

(PHPPE\Email)->ccPHPPE Pack

Synopsis

public cc( string $address );

Set carbon copy address of the email.

Parameters

string $addressEmail address

Return value

EmailEmail object

Examples

1
$email->cc("SomeBody <somebody@somewhere.com>");

See also

(PHPPE\Email)->bccPHPPE Pack

Synopsis

public bcc( string $address );

Set the hidden carbon copy address of the email.

Parameters

string $addressEmail address

Return value

EmailEmail object

Examples

1
$email->bcc("SomeBody <somebody@somewhere.com>");

See also

(PHPPE\Email)->subjectPHPPE Pack

Synopsis

public subject( string $subject );

Set subject for the email.

Parameters

string $subjectSubject. Never leave it empty.

Return value

EmailEmail object

Examples

1
$email->subject("Something");

See also

(PHPPE\Email)->messagePHPPE Pack

Synopsis

public message( string $message );

Set message body for the email.

Parameters

string $messageRaw email body.

Return value

EmailEmail object

Examples

1
$email->message("Dear Santa,\n\nI was a good boy!");

See also

(PHPPE\Email)->templatePHPPE Pack

Synopsis

public template( string $template, array $arguments );

Set message body for the email using a view template.

Parameters

string $templateTemplate to use to generate email body.
array $argumentsArguments for the template. Refer them with email. prefix.

Return value

EmailEmail object

Examples

1
$email->template("email_order");

See also

(PHPPE\Email)->attachFilePHPPE Pack

Synopsis

public attachFile( string $file, string $mimetype );

Attach a local file to the email.

Parameters

string $filePath and filename.
string $mimetypeMime type of the file. If not given, it will be autodetected.

Return value

EmailEmail object

Examples

1
$email->attachFile("data/invoices/1234.pdf");

See also

(PHPPE\Email)->attachDataPHPPE Pack

Synopsis

public attachData( string $data, string $mimetype, string $filename );

Attach a file to the email from memory.

Parameters

string $dataFile contents in memory.
string $mimetypeMime type of the file. Defaults to 'application/octet-stream'.
string $filenameFilename to use for the attachment.

Return value

EmailEmail object

Examples

1
2
3
4
ob_start();
imagepng($img);
$image = ob_get_clean();
$email->attachData($image, "image/png", "welcome.png");

See also

(PHPPE\Email)->sendPHPPE Pack

Synopsis

public send( string $backend );

Send out the email.

If backend is "db", the email will be stored in database, and "cron minute" will send it out for real using the backend given in realmailer.

Parameters

string $backendEmail backend. If not given, the one given in configuration will be used.

Return value

booleanTrue on success

Examples

1
$email->send("phpmailer");

See also

PHPPE\GPIO::RPiPCBPHPPE GPIO

Synopsis

public static RPiPCB( );

Return Raspberry Pi Board Revision number from /proc/cpuinfo.

Parameters

None.

Return value

intBoard Revision number

Examples

1
$pcb = GPIO::RPiPCB();

See also

PHPPE\GPIO::tempPHPPE GPIO

Synopsis

public static temp( );

Return Raspberry Pi's temperature from /sys/class/thermal/thermal_zone0.

Parameters

None.

Return value

floatBoard temperature in SI oC.

Examples

1
$SITemp = GPIO::temp();

See also

PHPPE\GPIO::freqPHPPE GPIO

Synopsis

public static freq( );

Return Raspberry Pi's CPU frequency from /sys/devices/system/cpu/cpu0/cpufreq.

Parameters

None.

Return value

floatCPU frequency.

Examples

1
$freq = GPIO::freq();

See also

PHPPE\GPIO::resetPHPPE GPIO

Synopsis

public static reset( );

Reset all GPIO ports to input mode and unexport them from userspace.

Parameters

None.

Return value

None.

Examples

1
GPIO::reset();

See also

PHPPE\GPIO::modePHPPE GPIO

Synopsis

public static mode( int $pin, string $direction);

Reset all GPIO ports to input mode and unexport them from userspace.

Parameters

int $pinPin number
string $directionThe string 'in' or 'out'. Defaults to 'out'.

Return value

GPIOGPIO instance

Examples

1
GPIO::mode(1, "out")->mode(2, "out");

See also

PHPPE\GPIO::setPHPPE GPIO

Synopsis

public static set( int $pin, boolean $value);

Write to a GPIO port.

Parameters

int $pinPin number
boolean $valueSet port to high on true. Defaults to true.

Return value

GPIOGPIO instance

Examples

1
GPIO::mode(1, "out")->set(1, true);

See also

PHPPE\GPIO::getPHPPE GPIO

Synopsis

public static get( int $pin);

Read a GPIO port's current level.

Parameters

int $pinPin number

Return value

booleanTrue if the port level is high, false if it's low.

Examples

1
$pressed = GPIO::get(1);

See also