Apple Push Notification Services Tutorial: Part 3/3

Step 3: Build up your database to store user information

Now we need a MySQL database to store all user information so that we know where to send the Push Notifications!

  1. Build up database to receive user information

-> Create a database and a user for that database.
-> Update the apns.php file and sendMessage.php file with the database information (username, pwd, database name, host, etc.)

We need three tables: “apns_devices”, “apns_device_history” and “apns_messages”.

We will actually need only “apns_devices” and “apns_device_history”, but since this tutorial is based on Easy APNS, we must also include everyting and consider all dependencies…!

 

Advertisement:

white,pages,yellow,tracker,search,find,gps,mobile,411,ringtone,business,911,likes,follower,locator

The first iPhone App ever that finds out who’s calling you before you answer the phone.


 
Create “apns_devices”:

 CREATE TABLE IF NOT EXISTS `apns_devices` (
  `pid` int(9) unsigned NOT NULL AUTO_INCREMENT,
  `appname` varchar(255) NOT NULL,
  `appversion` varchar(25) DEFAULT NULL,
  `deviceuid` char(40) NOT NULL,
  `devicetoken` char(64) NOT NULL,
  `devicename` varchar(255) NOT NULL,
  `devicemodel` varchar(100) NOT NULL,
  `deviceversion` varchar(25) NOT NULL,
  `pushbadge` enum('disabled','enabled') DEFAULT 'disabled',
  `pushalert` enum('disabled','enabled') DEFAULT 'disabled',
  `pushsound` enum('disabled','enabled') DEFAULT 'disabled',
  `development` enum('production','sandbox') CHARACTER SET latin1 NOT NULL DEFAULT 'production',
  `status` enum('active','uninstalled') NOT NULL DEFAULT 'active',
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`pid`),
  UNIQUE KEY `appname` (`appname`,`appversion`,`deviceuid`),
  KEY `devicetoken` (`devicetoken`),
  KEY `devicename` (`devicename`),
  KEY `devicemodel` (`devicemodel`),
  KEY `deviceversion` (`deviceversion`),
  KEY `pushbadge` (`pushbadge`),
  KEY `pushalert` (`pushalert`),
  KEY `pushsound` (`pushsound`),
  KEY `development` (`development`),
  KEY `status` (`status`),
  KEY `created` (`created`),
  KEY `modified` (`modified`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Store unique devices' AUTO_INCREMENT=428 ;

Create “apns_device_history”:

 CREATE TABLE IF NOT EXISTS `apns_device_history` (
  `pid` int(9) unsigned NOT NULL AUTO_INCREMENT,
  `appname` varchar(255) NOT NULL,
  `appversion` varchar(25) DEFAULT NULL,
  `deviceuid` char(40) NOT NULL,
  `devicetoken` char(64) NOT NULL,
  `devicename` varchar(255) NOT NULL,
  `devicemodel` varchar(100) NOT NULL,
  `deviceversion` varchar(25) NOT NULL,
  `pushbadge` enum('disabled','enabled') DEFAULT 'disabled',
  `pushalert` enum('disabled','enabled') DEFAULT 'disabled',
  `pushsound` enum('disabled','enabled') DEFAULT 'disabled',
  `development` enum('production','sandbox') CHARACTER SET latin1 NOT NULL DEFAULT 'production',
  `status` enum('active','uninstalled') NOT NULL DEFAULT 'active',
  `archived` datetime NOT NULL,
  PRIMARY KEY (`pid`),
  KEY `devicetoken` (`devicetoken`),
  KEY `devicename` (`devicename`),
  KEY `devicemodel` (`devicemodel`),
  KEY `deviceversion` (`deviceversion`),
  KEY `pushbadge` (`pushbadge`),
  KEY `pushalert` (`pushalert`),
  KEY `pushsound` (`pushsound`),
  KEY `development` (`development`),
  KEY `status` (`status`),
  KEY `appname` (`appname`),
  KEY `appversion` (`appversion`),
  KEY `deviceuid` (`deviceuid`),
  KEY `archived` (`archived`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Store unique device history' AUTO_INCREMENT=1916 ;

Create “apns_messages”:

 CREATE TABLE IF NOT EXISTS `apns_messages` (
  `pid` int(9) unsigned NOT NULL AUTO_INCREMENT,
  `fk_device` int(9) unsigned NOT NULL,
  `message` varchar(255) NOT NULL,
  `delivery` datetime NOT NULL,
  `status` enum('queued','delivered','failed') CHARACTER SET latin1 NOT NULL DEFAULT 'queued',
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`pid`),
  KEY `fk_device` (`fk_device`),
  KEY `status` (`status`),
  KEY `created` (`created`),
  KEY `modified` (`modified`),
  KEY `message` (`message`),
  KEY `delivery` (`delivery`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Messages to push to APNS' AUTO_INCREMENT=21 ;

That’s it! Now we have everything we need.

I have to mention, that for this tutorial I gathered information from all over the web:

Easy APNS
Inspired by Scott Forstall

 

I hope you enjoyed this tutorial. Please feel free to comment and ask questions and tell me where I can improve this tutorial for you to make it as easy as possible.

Here a download link to all the files required for this project (apns.php, sendMessage.php, simplepush.php and all dependent files)

Download: apns.zip

All the best,

Gabe

 

30 comments

  1. Jack Brown says:

    This method using UDID doesn’t work in IOS 6…

    • admin says:

      Hey Jack

      Apple may have deprecated the UDID but you can use your own custom UDID instead of the original one. Each device has a unique physical MAC address.

      I will post soon this workaround here.

      Thanks for your comment!

      Gabe

      • Jack Brown says:

        What lines of code would I have to replace?

        Would it just be:

        NSString *deviceUuid;

        I’m quite desperate to get my update submitted to the app store tonight,

        Thanks,

        Jack.

        • Ilnaz says:

          Your remark about using ENUM as inrtgeity checking is incorrect (at least by default). Infact, here’s an excerpt from the MySQL documentation.The value may also be the empty string ( ) or NULL under certain circumstances: * If you insert an invalid value into an ENUM (that is, a string not present in the list of allowed values), the empty string is inserted instead as a special error value. This string can be distinguished from a normal’ empty string by the fact that this string has the numerical value 0. More about this later. * If an ENUM is declared NULL, NULL is also a legal value for the column, and the default value is NULL. If an ENUM is declared NOT NULL, the default value is the first element of the list of allowed values.Here’s an example to prove this:mysql> show create table foo;+ -+ -+| Table | Create Table |+ -+ -+| foo | CREATE TABLE `foo` ( `some_enum` enum( one’,’two’) NOT NULL default one’) TYPE=MyISAM |+ -+ -+1 row in set (0.00 sec)mysql> select * from foo;Empty set (0.00 sec)mysql> insert into foo set some_enum=’three';Query OK, 1 row affected (0.00 sec)mysql> select * from foo;+ +| some_enum |+ +| |+ +1 row in set (0.00 sec)This works as intended and as reflected by the MySQL documentation.

      • admin says:

        Just posted the solution here: http://iosapplove.com/archive/2013/01/uidevice-uniqueidentifier-property-is-deprecated-what-now/

        Don’t forget to adapt the line 227 of the file “class_APNS.php”

        => else if(strlen($deviceuid)!=40) $this->_triggerError(‘ERROR: Device ID must be 40 characters in length.’, E_USER_ERROR);

        should be now:
        else if(strlen($deviceuid)!=32) $this->_triggerError(‘ERROR: Device ID must be 32 characters in length.’, E_USER_ERROR);

        That’s it!

        • Jack Brown says:

          Thanks, seem to be getting directed to a login_success.php page, which i think is something to do with my passphrase.

          • admin says:

            Sorry, forgot to mention that. You can replace “login_success.php” with whatever error page you want. You’re right, if you are being redirected, you have to check the passphrase or just leave it out for testing to make sure you’re not doing anything else wrong.

          • Kiran says:

            Your remark about using ENUM as inrttgiey checking is incorrect (at least by default). Infact, here’s an excerpt from the MySQL documentation.The value may also be the empty string ( ) or NULL under certain circumstances: * If you insert an invalid value into an ENUM (that is, a string not present in the list of allowed values), the empty string is inserted instead as a special error value. This string can be distinguished from a normal’ empty string by the fact that this string has the numerical value 0. More about this later. * If an ENUM is declared NULL, NULL is also a legal value for the column, and the default value is NULL. If an ENUM is declared NOT NULL, the default value is the first element of the list of allowed values.Here’s an example to prove this:mysql> show create table foo;+ -+ -+| Table | Create Table |+ -+ -+| foo | CREATE TABLE `foo` ( `some_enum` enum( one’,’two’) NOT NULL default one’) TYPE=MyISAM |+ -+ -+1 row in set (0.00 sec)mysql> select * from foo;Empty set (0.00 sec)mysql> insert into foo set some_enum=’three';Query OK, 1 row affected (0.00 sec)mysql> select * from foo;+ +| some_enum |+ +| |+ +1 row in set (0.00 sec)This works as intended and as reflected by the MySQL documentation.

          • admin says:

            @Kiran
            Thank you for pointing this out! I’m not a MySQL specialist, so I cannot confirm what you wrote – it sounds reasonable though!
            I don’t know if in this case here it is really necessary and I don’t know if you would ever run into this problem you mentioned.

            Nice hint!

            Thanks
            Gabe

      • Yes, it is deprecated, i use : “identifierForVendor”

        // Get the users Device Model, Display Name, Unique ID, Token & Version Number
        UIDevice *dev = [UIDevice currentDevice];
        //NSString *deviceUuid = dev.uniqueIdentifier;

        NSString *deviceUuid = [[[UIDevice currentDevice] identifierForVendor] UUIDString];

    • Bajinder says:

      This is *not* a very good use for ENUM. Having to alter table every time you add a new value is *not* a good idea. This requires a user with eltvaeed privileges to be able to access the database on behalf of an unprivileged user, which is *not* a good idea.Your example is a typical example of when ENUM should *not* be used. Doing ALTER table each time you need to add or change a solicitation-type makes the table unavailable for other use.ENUM is suited for applications where the possible values rarely or never change. For example gender, truth, smoking preference, etc. Building your application in such a way that tables need to regularly be altered is not fantastically extensible, as that can take inordinately long on large tables.This seems an ideal case of when *not* to use ENUM.

  2. Jack Brown says:

    Have using command at terminal line to create the file with no encryption but still getting this error? Can you confirm that this is correct:

    private $certificate = ‘/my/website/www/pushservce/apns-production.pem';
    private $sandboxCertificate = ‘/my/website/www/pushservice/apns-production-cert.pem’

    Is that correct?

    • admin says:

      Are you testing for production or for development?
      Maybe you have a Typo there: private $certificate = ‘/my/website/www/pushservce/apns-production.pem’;

      it’s “pushservce”… shouldn’t it be “pushservice”?

      Also, you can try this:
      private $certificate = ‘apns-production.pem';

      … and put the ‘apns-production.pem’ file into the same folder as the class_APNS.php so that you are sure it works. Then try to put it in the desired folder and change the path.

      Also, did you read the “readme” file? There are a few instructions there you can follow to make sure everything is set up the right way!

      Good luck

  3. Shirl says:

    You’ve captured this perfectly. Thanks for tkiang the time!

  4. FatsoLow says:

    im having issue with the php , encounter error as below

    #!/usr/bin/php
    Warning: require_once(class_mysqli.php) [function.require-once]: failed to open stream: No such file or directory in /home/adios/public_html/uat/pushservice/apns.php on line 19

    Fatal error: require_once() [function.require]: Failed opening required ‘class_mysqli.php’ (include_path=’.:/usr/lib/php:/usr/local/lib/php’) in /home/adios/public_html/uat/pushservice/apns.php on line 19

    • admin says:

      Hey FatsoLow

      Did you check if mysqli is installed? (not mysql, but mysqli(==mysql improved))… sounds to me you must first install mysql on your server and configure it, or, if this is not a choice, you can use mysql instead of mysql… just replace ‘mysqli’ everywhere with ‘mysql’ like that:

      mysql_connect() -> mysqli_connect()
      mysql_query() -> mysqli_query()
      mysql_fetch_array() -> mysqli_fetch_array()

      Keep in mind though, that mysqli is in my opinion much better, not only because of performance and security. And maybe you’re just faster installing and configuring mysqli on your server than changing all the code.

      Cheers

      Gabe

      • FatsoLow says:

        already replace with mysql, it still doesnt work, im making my own connectDB.php now, insert new device ID into the table is working already, now im trying to convert other method to my connectDB.php for testing.

  5. FatsoLow says:

    in the simplepush.php;

    line 22: stream_context_set_option($ctx, ‘ssl’, ‘local_cert’, ‘ck.pem’);

    i think the user has to change the ck.pem to their own .pem name.

    line 27: ‘ssl://gateway.push.apple.com:2195′, $err,

    change the ssl to the sandbox ssl instead of using the production ssl (for testing)

  6. Qaiser Abbas says:

    I have been experiencing alot of issues

    here is latest one

    Fatal error: ERROR: Device Token must be 64 characters in length. 1) APNS::__construct -> File: apns.php (line 31) 2) APNS::_registerDevice -> File: class_APNS.php (line 173) 3) APNS::_triggerError -> File: class_APNS.php (line 228) in /Applications/XAMPP/xamppfiles/htdocs/apns/class_APNS.php on line 472

    • admin says:

      OK, don’t worry… this error happens in the file class_APNS.php in the line 228 =>

      else if(strlen($devicetoken)!=64) $this->_triggerError(‘ERROR: Device Token must be 64 characters in length.’, E_USER_ERROR);

      Which means your devicetoken has not the length of 64 characters… do you use some sort of own UDID or the original one which is deprecated ([UIDevice uniqueIdentifier])? The former one has 40 characters… not 64! This is why you get the error maybe…

      I wrote another blog post how to avoid the deprecated one here:
      http://iosapplove.com/archive/2013/01/uidevice-uniqueidentifier-property-is-deprecated-what-now/

      So you might consider dig into that first…

      Good luck!

      Gabe

  7. Qaiser Abbas says:

    I have followed all step

    Very Nice

    But still getting one last error

    That is not adding device info to data table

    NSURL *url = [[NSURL alloc] initWithScheme:@”http” host:host path:urlString];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    NSLog(@”Register URL: %@”, url);
    NSLog(@”Return Data: %@”, returnData);

    Here it returns Null

    ??
    i dont know why its happening

    but when i paste same url in the browser and hit enter it works and device info added to table

    • admin says:

      Hi Quaiser

      Please try to add this:

      urlString = [urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];

      before:

      NSURL *url = [[NSURL alloc] initWithScheme:@”http” host:host path:urlString];

      And try again… maybe that’s the problem.

      Cheers

  8. Sven says:

    Hi everyone, i always get the error “Pushnotification was NOT sent!” Any idea what i´m doing wrong? Here is the code…

    <?php
    session_start();
    //$_SESSION['passphrase'] = $_POST['passphrase'];
    $_SESSION['message'] = $_POST['message'];

    // Put your private key's passphrase here if needed:
    $passphrase = "xxx";

    // Put your alert message here:
    $message = $_POST['message'];

    if(strlen($message) < 1) {

    header ("Refresh: 4; sendMessage.php");
    exit("You must enter a message! You are being redirected…” . PHP_EOL);
    }

    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////

    $ctx = stream_context_create();
    stream_context_set_option($ctx, ‘ssl’, ‘local_cert’, ‘apns-production.pem’);
    stream_context_set_option($ctx, ‘ssl’, ‘passphrase’, $passphrase); // use this if you are using a passphrase

    // Open a connection to the APNS server
    $fp = @stream_socket_client(
    ‘ssl://gateway.push.apple.com:2195′, $err,
    $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);

    if (!$fp) {
    header (“Refresh: 4; login_success.php”);
    exit(“Could not connect to server. Please check the passphrase. You are being redirected…” . PHP_EOL);
    }

    //echo ‘Verbunden mit Apple Pushnotification Server’ . PHP_EOL;

    // Create the payload body
    $body[‘aps’] = array(
    ‘alert’ => $message,
    ‘sound’ => ‘default’
    );

    // Encode the payload as JSON
    $payload = json_encode($body);

    $errCounter = 0;

    // LOOP AND SEND TO ALL DEVICES FROM DATABASE

    /*
    CHANGE THIS!
    */
    $host=”rdbms.strato.de”; // Host name
    $username=”xxxx”; // Mysql username
    $password=”xxxx”; // Mysql password
    $db_name=”xxxx”; // Database name

    // Connect to server and select databse.
    mysql_connect(“$host”, “$username”, “$password”)or die(“cannot connect”);
    mysql_select_db(“$db_name”)or die(“cannot select DB”);

    $sql=”SELECT devicetoken, deviceuid, devicename, appversion FROM (SELECT devicetoken, deviceuid, devicename, appversion FROM apns_devices AS t WHERE pushalert = ‘enabled’ AND development = ‘production’ AND status = ‘active’ ORDER BY appversion DESC) AS apns_devices GROUP BY deviceuid ORDER BY appversion DESC”;
    $result1=mysql_query($sql);
    $bodyError = ”;

    while ($deviceToken = mysql_fetch_array($result1)) {

    // Build the binary notification
    $msg = chr(0) . pack(‘n’, 32) . pack(‘H*’, ”.$deviceToken[0].”) . pack(‘n’, strlen($payload)) . $payload;

    // Send it to the server
    $result = fwrite($fp, $msg, strlen($msg));

    $bodyError .= ‘result: ‘.$result.’, devicetoken: ‘.$deviceToken[0].”;

    if(!$result) {
    $errCounter = $errCounter + 1;

    // send an email to your address if you want to know if something went wrong…
    // mail(‘YOUR EMAIL ADDRESS’, ‘FORTIMO PUSH APNS FAILED’, ‘result: ‘.$result.’, devicetoken: ‘.$deviceToken[0]);
    }
    }

    // send an email to your address if you want to know if everything went well…
    mail(‘xxx@xxx.com’, ‘PUSH SENT’, $bodyError);

    if ($errCounter > 0)
    header (“Refresh: 1; sendMessage.php?error=1″);
    else
    header (“Refresh: 1; sendMessage.php?error=2″);

    // Close the connection to the server
    fclose($fp);
    ?>

  9. mariam says:

    i am getting Missing Sandbox Certificate .. no clue why?

    • mariam says:

      this is the error i get .. no clue why ??

      Fatal error: ERROR: Missing Sandbox Certificate. 1) APNS::__construct -> File: apns.php (line 31) 2) APNS::checkSetup -> File: class_APNS.php (line 147) 3) APNS::_triggerError -> File: class_APNS.php (line 197) in ~~/APNS-services/class_APNS.php on line 473

  10. Greg says:

    Very good tutorial, using this I get a successful notification that the push was successful and get an email, however my iphone never receives the push.

  11. Hi there,
    Thanks a lot for that super tuto. I’m not a specialist at all on PHP/MySQL so I have a problem ! :-)
    The app pass the registration, the return data works but i have no record in the MySQL database.
    Note : I use a real server by my ISP.
    Any idea welcome and thankyou in advance for help !

  12. Metzger_Soft says:

    Hi everyone,
    great tuto thanks.
    I can’t register any device in my database hower I don’t receive any error message ?
    Return data returns a lot of numbers.
    Could someone help please ?
    Thanks in advance

Leave a Reply

Your email address will not be published. Required fields are marked *


*