I’ll keep this list updated.
stop()
We use this function to gracefully stop the program in place of exit
.
<?php
/**
* Stop and maybe redirect
*
* @param string|int|null $redirect If a URL string is provided, redirect while exiting.
* If integer 400 (bad request), 404 (page not found), 422 (Unprocessable Entity) or 500 (internal server error) is provided, then the http status code is thrown while exiting.
*/
function stop ($redirect = null) {
// if you have stuff to do before stopping, do it here
if (!empty($redirect)) {
if (is_string($redirect)) {
header('Location: ' . $redirect);
} elseif (in_array(intval($redirect), [400,404,422,500])) {
http_response_code($redirect);
}
}
exit;
}
stop('//sqkhor.com'); // stop and redirect
stop(404); // stop and throw error 404
We used to close all DB connections within this function, but it’s seemed no longer required to close manually, so sometimes we just do some data logging here.
is_num() and is_id()
It’s funny that in PHP, most integers are actually passed in as strings. For example, when you get it from $_GET
, $_POST
or from MySQL.
If you forcefully convert it to INT when validating, you might accidentally approve invalid data such as “123abc”. You could use is_numeric()
but this will also let decimals like 123.45 slip through.
Here’s a simple function that we always use to validate an integer input:
<?php
/**
* Finds whether a variable is an integer or an integer string.
* Please note that this function only validates, no type-conversion.
*
* @param mixed $value The variable being evaluated.
*
* @return bool true if var is an integer or an integer string, false otherwise.
*/
function is_num ($value):bool {
return intval($value) == $value;
}
The catch is that this function would accept negative numbers, zero-padded numbers and numbers with exponent, which shouldn’t be a valid MySQL auto-increment ID.
<?php
is_num(123); // true
is_num('123'); // true
is_num('00123'); // true (not valid ID)
is_num(0); // true (not quite a valid ID)
is_num(123.45); // false
is_num('123.45'); // false
is_num(1234e5); // true (not valid ID)
is_num('1234e5'); // true (not valid ID)
is_num(1234e-5); // false
is_num(1000e-3); // true (not valid ID)
is_num('1000e-3'); // true (not valid ID)
is_num(-123); // true (not valid ID)
is_num('-123'); // true (not valid ID)
is_num('123abc'); // false
For a stricter ID validation, you could use this less-elegant regex solution:
<?php
/**
* Finds whether a variable is a valid ID.
*
* @param mixed $value The variable being evaluated.
* @param bool $accept_zero Whether or not 0 is accepted.
*
* @return bool true if var is a valid ID, false otherwise.
*/
function is_id ($value, bool $accept_zero = false):bool {
if (!preg_match("/^\d+$/", "$value")) return false;
if (("$value")[0] == '0') return ($accept_zero && strlen("$value") == 1);
return true;
}
Most ID in MySQL start from 1, but sometimes when you design a <select>
menu, you might put ‘0’ as an option to represent ‘none’ or ‘other’. This is where you apply true in $accept_zero
.
filter_join()
This is purpose-made for our own use.
Our websites are in multiple languages, therefore there are circumstances where we need to join 2 strings of different languages (eg. product name), and it gets complicated when we allow either string to be empty.
Hence, this function will remove the empty one before joining/imploding:
<?php
/**
* Filter away empty content and join(implode) the rest with separator.
*
* @param string $separator The glue to concatenate all $content
* @param string ...$content The strings to be filtered and concatenated
*
* @return string Joined content
*/
function filter_join ($separator = '', ...$content):string {
return implode($separator, array_filter($content));
}
// Example use case: joining Chinese and English product names
filter_join(' - ', $product->get_name('zh'), $product->get_name('en'));
// Example of what it returns
filter_join(' - ', '给爱丽丝调音', 'Tuning Alice'); // 给爱丽丝调音 - Tuning Alice
filter_join(' - ', '', 'Black Friday Sales'); // Black Friday Sales
You could also add $content = array_map(‘trim’, $content)
before return.
array_make_key()
How good would it be if the data we fetch from database could be indexed by the ID!
That’s why we created this function for our own convenience:
<?php
/**
* Use a column as the array keys.
* If $value_column is defined, that column will be used as the array values.
*
* @param array $array The array to be processed
* @param int|string $key_column The column that will be used as the key
* @param int|string|bool $value_column If defined, the resulting array will use this column as its values
* @return array Returns the recombined array
*/
function array_make_key (array $array, $key_column, $value_column = false):array {
$values = ($value_column === false) ? array_values($array) : array_column($array, $value_column);
return array_combine(array_column($array, $key_column), $values);
}
Let’s say we have this set of data from our database:
Let’s say we have this set of data from our database:
<?php
$products = array(
[
'id' => 73,
'name' => '风云人物郑小强',
'isbn' => '978-983-3738-94-6',
],
[
'id' => 92,
'name' => '神奇望远镜',
'isbn' => '978-967-5439-12-4',
],
[
'id' => 169,
'name' => '羽毛',
'isbn' => '978-967-0370-96-5',
],
[
'id' => 184,
'name' => '木屋',
'isbn' => '978-967-0564-14-2',
],
);
By running array_make_key($products, ‘id’, ‘name’)
the resulting array will be:
array (
73 => '风云人物郑小强',
92 => '神奇望远镜',
169 => '羽毛',
184 => '木屋',
)
return_json()
This is mainly for AJAX calls:
<?php
/**
* Print a JSON and exit
*/
function return_json ($data) {
global $engine;
if (ob_get_length()) ob_clean();
if (!headers_sent()) header('Content-Type: application/json');
echo json_encode($data);
exit; // We replaced exit with stop() mentioned above
}
This script will try its best to clear the output buffer before printing JSON. You could now call return_json([‘success’ => 0, ‘error’ => “Invalid input”])
and the program will stop right there.
And here’s some template scripts that you could modify from:
Registering Autoload
The script below will load your PHP file automatically by converting whatever in ‘use’ syntax to a path.
<?php
define('MY_NAMESPACE', "YourNameSpace"); // Replace this with your namespace.
define('CLASS_POSTFIX_AND_EXT', ".class.php"); // The way you name your file. In this case 'User.class.php' contains the User class.
spl_autoload_register(function ($class) {
$namespace = MY_NAMESPACE . "\\";
if (strpos($class, $namespace) !== 0) return;
$file = __DIR__.DIRECTORY_SEPARATOR; // Replace __DIR__ with a root directory constant instead if you have any
$file.= str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen($namespace)));
$file.= CLASS_POSTFIX_AND_EXT;
if (is_file($file)) {
require_once($file);
}
});
Use case: Assume that my namespace is ‘SqKhor’ and my file name ends with ‘.class.php’:
<?php
use SqKhor\Utility\Email; // will load "/Utility/Email.class.php"
Reading JSON from Javascript Fetch API
Even in PHP 8, when you send a POST request via the new Javascript Fetch API with ‘application/json’ as content type (and Axios, which sends JSON by default too), you wouldn’t be able to read it directly from $_POST.
This is because PHP only reads the form data in url-encoded format.
To solve this problem, we have had this function included so that PHP could merge the JSON input to $_POST.
<?php
function accept_json_data () {
if (!empty($_SERVER['CONTENT_TYPE']) && preg_match("/application\/json/i", $_SERVER['CONTENT_TYPE'])) {
if ($php_input = json_decode(trim(file_get_contents("php://input")), true)) {
$_POST = array_merge_recursive($_POST, $php_input);
}
}
}
accept_json_data();
Please note that this is considered a polyfill, and we shall remove this function in future PHP versions when it finally supports POST data in JSON format.