Some values differ per machine and must not stand in the code: the database password, an API key, a debug switch. padEnv reads them from the server's environment or from a .env file beside the code, so the same application runs unchanged on a laptop and on the production server, and the secrets never reach git. The cache keeps what is slow to make from one request to the next, and a rate limit counts how often a visitor tries.

Environment values

padEnv ( 'KEY', $default ) looks in three places and answers the first that has the key: the real environment of the server (getenv, $_ENV, $_SERVER - what Apache's SetEnv or PHP-FPM's env[...] sets), then the .env in the application's root - apps/shop/.env - then _common's .env for an application that runs with _common, then .env in the PAD home. Without the key it answers the default - a closure as the default is called. This manual has a .env of its own:

MANUAL_SITE_NAME="PAD manual"
MANUAL_PAGE_SIZE=20
MANUAL_DEBUG=false
PHP
fragments/environment_and_cache_1.php
HTML
fragments/environment_and_cache_1.pad
Result
<?php

// apps/manual/.env holds MANUAL_SITE_NAME,
// MANUAL_PAGE_SIZE and MANUAL_DEBUG.

$site = padEnv ( 'MANUAL_SITE_NAME', 'My site' );
$rows = padEnv ( 'MANUAL_PAGE_SIZE', 25 );
$debug = padEnv ( 'MANUAL_DEBUG', TRUE ) ? 'on' : 'off';
$maps = padEnv ( 'MANUAL_MAPS_KEY', 'not set' );

?>
<ul>
<li>Site: {$site}</li>
<li>Rows a page: {$rows}</li>
<li>Debugging: {$debug}</li>
<li>Maps key: {$maps}</li>
</ul>
  • Site: PAD manual
  • Rows a page: 20
  • Debugging: off
  • Maps key: not set

The engine loads its library before it reads any configuration, so the place a secret is needed first can ask for it - _config/config.php:

$padSqlPassword = padEnv ( 'padSqlPassword' );
$padSqlUser     = padEnv ( 'padSqlUser', 'shop' );

The .env file

# a comment
APP_NAME=Shop                     a bare value, trimmed - # after a space starts a comment
export DB_HOST=localhost          export in front, as a shell script writes it
GREETING="Hello\nWorld"           double quotes: \n \r \t \" \\ \$ and ${OTHER}
PATTERN='${not} \n expanded'      single quotes: the text exactly as written
DEBUG=false                       true, false, null and empty are TRUE, FALSE, NULL and ''
NOTE="two
lines"                            a quoted value may run over several lines

The cache

A slow query, a remote service, a computed report: padRemember ( $key, $ttl, $callback ) answers what is kept under the key, or runs the callback, keeps its answer for $ttl seconds and answers that. padCachePut, padCacheGet, padCacheHas and padCacheForget do the same by hand.

PHP
fragments/environment_and_cache_2.php
HTML
fragments/environment_and_cache_2.pad
Result
<?php

// The first request of the next ten minutes runs the callback;
// the others get what it left in the cache, without asking the
// source again.

$rates = padRemember ( 'manual-rates', 600, function () {
return [ [
'code' => 'EUR', 'rate' => '1.00' ],
[
'code' => 'USD', 'rate' => '1.08' ],
[
'code' => 'GBP', 'rate' => '0.86' ] ];
} );

padCachePut ( 'manual-notice',
'Maintenance on Sunday night',
3600 );

$notice = padCacheGet ( 'manual-notice', 'No notice' );
$nothing = padCacheGet ( 'manual-nothing-kept',
'Nothing kept under this key' );

?>
<ul>
{rates}<li>{$code}: {$rate}</li>{/rates}
</ul>
<p>{$notice}</p>
<p>{$nothing}</p>
  • EUR: 1.00
  • USD: 1.08
  • GBP: 0.86

Maintenance on Sunday night

Nothing kept under this key

Rate limits

padRateLimit ( $key, $maxAttempts, $decaySeconds ) counts a hit and answers TRUE while the key stays within the limit - five attempts a minute - and FALSE over it. The first hit starts a window of $decaySeconds; when it ends, the key starts from nothing again. A login form, a contact form, an expensive search: key it on the visitor, or on the account that is tried.

PHP
fragments/environment_and_cache_3.php
HTML
fragments/environment_and_cache_3.pad
Result
<?php

// Five login attempts a minute. A page of your own keys it on
// the visitor - 'login:' . $_SERVER ['REMOTE_ADDR'] - this
// example on a key of its own, so every reader of this page
// starts from nothing.

$key = 'login:' . padRandomString ( 8 );
$tries = [];

for (
$try = 1; $try <= 7; $try++ ) {
$allowed = padRateLimit ( $key, 5, 60 );
$tries [] = [ 'try' => $try,
'answer' => $allowed ? 'allowed' : 'too many' ];
}

$left = padRateLimitRemaining ( $key, 5 );
$minutes = ceil ( padRateLimitAvailableIn ( $key ) / 60 );

padRateLimitClear ( $key );

?>
<ul>
{tries}<li>Attempt {$try}: {$answer}</li>{/tries}
</ul>
<p>{$left} attempts left - try again in {$minutes} minute.</p>
  • Attempt 1: allowed
  • Attempt 2: allowed
  • Attempt 3: allowed
  • Attempt 4: allowed
  • Attempt 5: allowed
  • Attempt 6: too many
  • Attempt 7: too many

0 attempts left - try again in 1 minute.

functionwhat it does
padEnv ( $key, $default = NULL )the value of an environment key - the real environment, then the application's .env, then _common's, then the PAD home's .env, else the default
padCacheGet ( $key, $default = NULL )the value kept under the key, else the default
padCachePut ( $key, $value, $ttl = 3600 )keeps the value for $ttl seconds - NULL for ever; TRUE when it is kept
padCacheHas ( $key )whether the key holds a value that has not expired
padCacheForget ( $key )removes the key; TRUE when it held a value
padCacheFlush ()removes every entry of this application, rate limits included
padRemember ( $key, $ttl, $callback )the kept value, or the callback's answer - kept for $ttl seconds
padRateLimit ( $key, $maxAttempts, $decaySeconds = 60 )counts a hit: TRUE within the limit, FALSE over it
padRateLimitRemaining ( $key, $maxAttempts )the hits left in the current window
padRateLimitAvailableIn ( $key )the seconds until the current window ends
padRateLimitClear ( $key )ends the window - the key starts from nothing