Create custom exception handler


Exception handling is used to change the normal flow of the code execution if a specified error (exceptional) condition occurs. This condition is called an exception.

PHP has an exception model similar to that of other programming languages. Exceptions are important and provides a better control over error handling.


There are following functions which can be used from Exception class.
  • getMessage()- message of exception
  • getCode() - code of exception
  • getFile() - source filename
  • getLine() - source line
  • getTrace() - n array of the backtrace()
  • getTraceAsString() - formated string of trace


<?php
try {
    $error = 'Always throw this error';
    throw new Exception($error);

    // Code following an exception is not executed.
    echo 'Never executed';

} catch (Exception $e) {
    echo 'Caught exception: ',  $e->getMessage(), "\n";
}

// Continue execution
echo 'Hello World';
?>

In the above example $e->getMessage function is uded to get error 
message. 


All errors and warnings should be logged. Based on the severity of an error, notifications should be sent out to other systems/teams. So that you can better gauge its severity, PHP provides several built-in error levels to describe the nature of an error. Each level is represented by an integer value and named constant which can be used by the programmer.  

The following table is generated from http://php.net/manual/en/errorfunc.constants.php





You can extend PHP's built in Exception class and create custom Exception handler for more flexibility.

Here I have created a custom exception handler

<?php
error_reporting(E_ALL);
// error_reporting(E_ALL | E_STRICT);  // From PHP 5.4.0 E_STRICT is a part of E_ALL

class NoticeException extends Exception { 
    public function __toString() {
        return  "NOTICE :: $this->message";
    }
}

class WarningException extends Exception { 
    public function __toString() {
        return  "WARNING :: $this->message";
    }
}

class StrictException extends Exception {
    public function __toString() {
        return  "STRICT :: $this->message";
    }
}

class UserThrownException extends Exception {
    public function __toString() {
        return "USER ERROR :: $this->message";
    }
}

class UserDepricatedException extends Exception {
    public function __toString() {
        return "USER DEPRICATED ERROR :: $this->message";
    }
}

class OtherErrorException extends Exception {
    public function __toString() {
        return  "OTHER ERROR :: $this->message";
    }
}

function pc_error_handler($errno,$error,$file,$line,$context) {
    $message = "[$error][FILE : $file][LINE : $line]";
    
    if($errno == E_WARNING) {
        throw new WarningException($message);
    } else if($errno == E_NOTICE) {
        throw new NoticeException($message);
    } else if($errno == E_STRICT) {
        throw new StrictException($message);
    } else if($errno == E_USER_ERROR) {
        throw new UserThrownException($message);
    } else if($errno == E_USER_DEPRECATED) {
        throw new UserDepricatedException($message);
    } else {
        throw new OtherErrorException($message);
    }
}

Working with Apple Push Notification


What is Apple Push Notification Service

The Apple Push Notification Service is a service created by Apple Inc., it uses push technology through a constantly open IP connection to forward notifications from the servers of third party applications to the Apple devices; such notifications may include badges, sounds or custom text alerts.

As a provider, you need to communicate with the Apple Push Notification Service (APNS) to send the messages that are then pushed to the phone. This is necessary so that the device only needs to maintain 1 connection to the APNS, helping to reduce battery usage.

Basic Structure
  1. You connect to the APNS using your unique SSL certificate
  2. Cycle through the messages you want to send 
  3. Construct the payload for each message
  4. Disconnect from APNS

Device Token

Each push message must be “addressed” to a specific device. This is achieved by using a unique deviceToken generated by APNS within your iPhone application. Once this token has been retrieved, you need to store it on your server.


Implementation

Here is a sample implementation of Apple Push Notification Service through PHP

Create apns.php

<?php

// authentication
$host = "localhost";
$user = "db_username";
$pass = "db_password";
$dbname = "db_name";

// create connection with database
$con = mysql_connect($host,$user,$pass);

// check whether database connection is successful 
if (!$con) {
// if connection not successful then stop the script and show the error
die('Could not connect to database: ' . mysql_error());
} else {
// if database connection successful then select the database
mysql_select_db($dbname, $con);
}

// get the id, token from database
$result = mysql_query("SELECT id,token FROM `device_tokens` ORDER BY id");

//Setup notification message
$body = array();
$body['aps'] = array('alert' => 'This is push message');
$body['aps']['notifurl'] = 'http://www.mydomain.com';
$body['aps']['badge'] = 2;

//Setup stream (connect to Apple Push Server)
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'passphrase', 'password_for_apns.pem_file');
stream_context_set_option($ctx, 'ssl', 'local_cert', 'apns_pem_certificate.pem');
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
    stream_set_blocking ($fp, 0); 
// This allows fread() to return right away when there are no errors. But it can also miss errors during 
//  last  seconds of sending, as there is a delay before error is returned. Workaround is to pause briefly 
// AFTER sending last notification, and then do one more fread() to see if anything else is there.

if (!$fp) {
//ERROR
 echo "Failed to connect (stream_socket_client): $err $errstrn";

} else {

// Keep push alive (waiting for delivery) for 90 days
$apple_expiry = time() + (90 * 24 * 60 * 60); 

// Loop thru tokens from database
while($row = mysql_fetch_array($result)) {
$apple_identifier = $row["id"];
$deviceToken = $row["token"];
$payload = json_encode($body);
            
// Enhanced Notification
$msg = pack("C", 1) . pack("N", $apple_identifier) . pack("N", $apple_expiry) . pack("n", 32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n", strlen($payload)) . $payload; 
            
// SEND PUSH
fwrite($fp, $msg);

// We can check if an error has been returned while we are sending, but we also need to 
// check once more after we are done sending in case there was a delay with error response.
checkAppleErrorResponse($fp); 
}

// Workaround to check if there were any errors during the last seconds of sending.
// Pause for half a second. 
// Note I tested this with up to a 5 minute pause, and the error message was still available to be retrieved
usleep(500000); 

checkAppleErrorResponse($fp);

echo 'Completed';

mysql_close($con);
fclose($fp);

}

// FUNCTION to check if there is an error response from Apple
// Returns TRUE if there was and FALSE if there was not
function checkAppleErrorResponse($fp) {

//byte1=always 8, byte2=StatusCode, bytes3,4,5,6=identifier(rowID). 
// Should return nothing if OK.

//NOTE: Make sure you set stream_set_blocking($fp, 0) or else fread will pause your script and wait 
// forever when there is no response to be sent.

$apple_error_response = fread($fp, 6);

if ($apple_error_response) {

// unpack the error response (first byte 'command" should always be 8)
$error_response = unpack('Ccommand/Cstatus_code/Nidentifier', $apple_error_response); 

if ($error_response['status_code'] == '0') {
$error_response['status_code'] = '0-No errors encountered';

} else if ($error_response['status_code'] == '1') {
$error_response['status_code'] = '1-Processing error';

} else if ($error_response['status_code'] == '2') {
$error_response['status_code'] = '2-Missing device token';

} else if ($error_response['status_code'] == '3') {
$error_response['status_code'] = '3-Missing topic';

} else if ($error_response['status_code'] == '4') {
$error_response['status_code'] = '4-Missing payload';

} else if ($error_response['status_code'] == '5') {
$error_response['status_code'] = '5-Invalid token size';

} else if ($error_response['status_code'] == '6') {
$error_response['status_code'] = '6-Invalid topic size';

} else if ($error_response['status_code'] == '7') {
$error_response['status_code'] = '7-Invalid payload size';

} else if ($error_response['status_code'] == '8') {
$error_response['status_code'] = '8-Invalid token';

} else if ($error_response['status_code'] == '255') {
$error_response['status_code'] = '255-None (unknown)';

} else {
$error_response['status_code'] = $error_response['status_code'].'-Not listed';

}

echo '<br><b>+ + + + + + ERROR</b> Response Command:<b>' . $error_response['command'] . '</b>&nbsp;&nbsp;&nbsp;Identifier:<b>' . $error_response['identifier'] . '</b>&nbsp;&nbsp;&nbsp;Status:<b>' . $error_response['status_code'] . '</b><br>';

echo 'Identifier is the rowID (index) in the database that caused the problem, and Apple will disconnect you from server. To continue sending Push Notifications, just start at the next rowID after this Identifier.<br>';

return true;
}
       
return false;
}

?>

Setup Cronjob

While you want to send huge amount of push notification, then it would be better to add to crontab.

Open crontab to add a new cronjob
$ crontab -e

Add a cronjob (Assume you want to run the cron every 5 minutes)
*/5 * * * * myuser /usr/bin/php -f /absolute/path/to/apns.php fetch > /usr/local/apns/apns.log 2>&1 &

Here, the > operator used to redirect the standard output generated by the process to a suitable location, while the & operator sends the process to the background and returns the control to the calling process.

You can discard the output by sending it to /dev/null

It is also a good practice to keep track of id of the process you execute in the background. You can retrive it via the $! operator and store it in a variable for future use.

How to create custom PHP extension?

Required Tools

In order to create custom PHP extension, requires the following build tools
  • C/C++ compiler (For example, GCC).
  • phpize which is a shell script to prepare PHP extension for compiling.
  • php-config which is a simple shell script for obtaining information about the installed PHP configuration.
On RPM based systems, required packages can be installed using following command:
$ yum install gcc php php-devel

On Debian based systems, required packages can be installed using following command:
$ apt-get install gcc php php5-dev

Create config.m4 file 

PHP_ARG_ENABLE(my_extension, whether to enable my extension, 
[ --enable-my-extension Enable my extension])
if test "$PHP_MY_EXTENSION" = "yes"; then
 AC_DEFINE(HAVE_MY_EXTENSION, 1, [Whether you have my extension])
 PHP_NEW_EXTENSION(my_extension, my_custom_extension.c, $ext_shared)
fi


The config.m4 file for an extension tells the UNIX build system 
  • what configure options your extension supports
  • what external libraries and includes you require
  • what source your require

This config.m4 stores the basic configuration data used by the PHP 
to compile your custom extension.

Create my_custom_extension.c 

#ifdef HAVE_CONFIG_H

#include "config.h"
#endif
#include "php.h"

#define PHP_MY_EXTENSION_VERSION "1.0"
#define PHP_MY_EXTENSION_EXTNAME "my_extension"

extern zend_module_entry my_extension_module_entry;
#define phpext_my_extension_ptr &my_extension_module_entry
// declaration of a custom my_function()
PHP_FUNCTION(my_function);
// list of custom PHP functions provided by this extension
// set {NULL, NULL, NULL} as the last record to mark the end of list
static function_entry my_functions[] = {
PHP_FE(my_function, NULL)
{NULL, NULL, NULL}};

// the following code creates an entry for the module and registers it with Zend.
zend_module_entry my_extension_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_MY_EXTENSION_EXTNAME,
my_functions,
NULL, // name of the MINIT function or NULL if not applicable
NULL, // name of the MSHUTDOWN function or NULL if not applicable
NULL, // name of the RINIT function or NULL if not applicable
NULL, // name of the RSHUTDOWN function or NULL if not applicable
NULL, // name of the MINFO function or NULL if not applicable
#if ZEND_MODULE_API_NO >= 20010901
PHP_MY_EXTENSION_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
ZEND_GET_MODULE(my_extension)
// implementation of a custom my_function()
PHP_FUNCTION(my_function) {
RETURN_STRING("A line from my custom php extension", 1);
}

Building the extension

$ phpize
$ ./configure
$ make install
  • The phpize command will generate additional configuration  files, required to compile your PHP extension 
  • you can reverse this process anytime by typing the “phpize –clean” command
  • After running each of these commands, you should have a my_custom_extension.so file in PHP extensions directory.
If don’t know the name of that directory, you can check it’s name by typing:
$ php-config | grep extension-dir

Configure php.ini

The last thing you need to do, is to add the following line to your php.ini to load your extension on PHP startup:
extension=my_extension.so

Testing the extension

You can test your PHP extension by typing the following command:
$ php -r "echo my_function();"

If everything was fine, the output will be:
A line from my custom php extension