<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[BuddyPress][7813] trunk: Object caching for the more frequently used items in bp-notifications</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://buddypress.trac.wordpress.org/changeset/7813">7813</a></dd>
<dt>Author</dt> <dd>boonebgorges</dd>
<dt>Date</dt> <dd>2014-02-06 20:33:50 +0000 (Thu, 06 Feb 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Object caching for the more frequently used items in bp-notifications

Several notifications queries are made on every pageload:
- an unread count to set up navigation
- an unread count for the toolbar
- the notification items themselves for the toolbar

These three queries actually result in the identical SQL query, so we can
eliminate two of them by caching the value by the pageload. With persistent
caching, the performance benefits are obviously even greater.

The chosen technique for caching here is 'all_for_user_'. This makes
invalidation fairly straightforward, because we can simply bust the
'all_for_user_x' cache whenever a save or delete takes place on a notification
item that is related to user_x. For more complex parts of the notification
component (such as the notifications page itself), if caching is desired,
it'll probably make sense to split the query and cache individual items.

Fixes <a href="http://buddypress.trac.wordpress.org/ticket/5377">#5377</a></pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbpnotificationsbpnotificationsclassesphp">trunk/bp-notifications/bp-notifications-classes.php</a></li>
<li><a href="#trunkbpnotificationsbpnotificationsfunctionsphp">trunk/bp-notifications/bp-notifications-functions.php</a></li>
<li><a href="#trunkbpnotificationsbpnotificationsloaderphp">trunk/bp-notifications/bp-notifications-loader.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkbpnotificationsbpnotificationscachephp">trunk/bp-notifications/bp-notifications-cache.php</a></li>
<li><a href="#trunkteststestcasesnotificationsfunctionsphp">trunk/tests/testcases/notifications/functions.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkbpnotificationsbpnotificationscachephp"></a>
<div class="addfile"><h4>Added: trunk/bp-notifications/bp-notifications-cache.php (0 => 7813)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-notifications/bp-notifications-cache.php                                (rev 0)
+++ trunk/bp-notifications/bp-notifications-cache.php   2014-02-06 20:33:50 UTC (rev 7813)
</span><span class="lines">@@ -0,0 +1,41 @@
</span><ins>+<?php
+
+/**
+ * Functions related to notifications caching.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+
+/**
+ * Invalidate 'all_for_user_' cache when saving.
+ *
+ * @since BuddyPress (2.0.0)
+ *
+ * @param BP_Notification_Notification $n Notification object.
+ */
+function bp_notifications_clear_all_for_user_cache_after_save( BP_Notifications_Notification $n ) {
+       wp_cache_delete( 'all_for_user_' . $n->user_id, 'bp_notifications' );
+}
+add_action( 'bp_notification_after_save', 'bp_notifications_clear_all_for_user_cache_after_save' );
+
+/**
+ * Invalidate the 'all_for_user_' cache when deleting.
+ *
+ * @since BuddyPress (2.0.0)
+ *
+ * @param int $args Notification deletion arguments.
+ */
+function bp_notifications_clear_all_for_user_cache_before_delete( $args ) {
+       // Pull up a list of items matching the args (those about te be deleted)
+       $ns = BP_Notifications_Notification::get( $args );
+
+       $user_ids = array();
+       foreach ( $ns as $n ) {
+               $user_ids[] = $n->user_id;
+       }
+
+       foreach ( array_unique( $user_ids ) as $user_id ) {
+               wp_cache_delete( 'all_for_user_' . $user_id, 'bp_notifications' );
+       }
+}
+add_action( 'bp_notification_before_delete', 'bp_notifications_clear_all_for_user_cache_before_delete' );
</ins></span></pre></div>
<a id="trunkbpnotificationsbpnotificationsclassesphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-notifications/bp-notifications-classes.php (7812 => 7813)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-notifications/bp-notifications-classes.php      2014-02-06 19:38:35 UTC (rev 7812)
+++ trunk/bp-notifications/bp-notifications-classes.php 2014-02-06 20:33:50 UTC (rev 7813)
</span><span class="lines">@@ -122,6 +122,8 @@
</span><span class="cx">          );
</span><span class="cx">          $data_format = array( '%d', '%d', '%d', '%s', '%s', '%s', '%d' );
</span><span class="cx"> 
</span><ins>+               do_action_ref_array( 'bp_notification_before_save', array( &$this ) );
+
</ins><span class="cx">           // Update
</span><span class="cx">          if ( ! empty( $this->id ) ) {
</span><span class="cx">                  $result = self::_update( $data, array( 'ID' => $this->id ), $data_format, array( '%d' ) );
</span><span class="lines">@@ -139,6 +141,8 @@
</span><span class="cx">                  $retval   = $wpdb->insert_id;
</span><span class="cx">          }
</span><span class="cx"> 
</span><ins>+               do_action_ref_array( 'bp_notification_after_save', array( &$this ) );
+
</ins><span class="cx">           // Return the result
</span><span class="cx">          return $retval;
</span><span class="cx">  }
</span><span class="lines">@@ -673,6 +677,8 @@
</span><span class="cx">  public static function delete( $args = array() ) {
</span><span class="cx">          $where = self::get_query_clauses( $args );
</span><span class="cx"> 
</span><ins>+               do_action( 'bp_notification_before_delete', $args );
+
</ins><span class="cx">           return self::_delete( $where['data'], $where['format'] );
</span><span class="cx">  }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkbpnotificationsbpnotificationsfunctionsphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-notifications/bp-notifications-functions.php (7812 => 7813)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-notifications/bp-notifications-functions.php    2014-02-06 19:38:35 UTC (rev 7812)
+++ trunk/bp-notifications/bp-notifications-functions.php       2014-02-06 20:33:50 UTC (rev 7813)
</span><span class="lines">@@ -121,10 +121,17 @@
</span><span class="cx"> function bp_notifications_get_notifications_for_user( $user_id, $format = 'string' ) {
</span><span class="cx"> 
</span><span class="cx">  // Setup local variables
</span><del>-       $bp                    = buddypress();
-       $notifications         = BP_Notifications_Notification::get( array(
-               'user_id' => $user_id
-       ) );
</del><ins>+        $bp = buddypress();
+
+       // Get notifications out of the cache, or query if necessary
+       $notifications = wp_cache_get( 'all_for_user_' . $user_id, 'bp_notifications' );
+       if ( false === $notifications ) {
+               $notifications = BP_Notifications_Notification::get( array(
+                       'user_id' => $user_id
+               ) );
+               wp_cache_set( 'all_for_user_' . $user_id, $notifications, 'bp_notifications' );
+       }
+
</ins><span class="cx">   $grouped_notifications = array(); // Notification groups
</span><span class="cx">  $renderable            = array(); // Renderable notifications
</span><span class="cx"> 
</span><span class="lines">@@ -499,9 +506,13 @@
</span><span class="cx">  }
</span><span class="cx"> 
</span><span class="cx">  // Get the notifications, and count them
</span><del>-       $notifications = BP_Notifications_Notification::get( array(
-               'user_id' => $user_id,
-       ) );
</del><ins>+        $notifications = wp_cache_get( 'all_for_user_' . $user_id, 'bp_notifications' );
+       if ( false === $notifications ) {
+               $notifications = BP_Notifications_Notification::get( array(
+                       'user_id' => $user_id,
+               ) );
+               wp_cache_set( 'all_for_user_' . $user_id, $notifications, 'bp_notifications' );
+       }
</ins><span class="cx"> 
</span><span class="cx">  $count = ! empty( $notifications ) ? count( $notifications ) : 0;
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkbpnotificationsbpnotificationsloaderphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-notifications/bp-notifications-loader.php (7812 => 7813)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-notifications/bp-notifications-loader.php       2014-02-06 19:38:35 UTC (rev 7812)
+++ trunk/bp-notifications/bp-notifications-loader.php  2014-02-06 20:33:50 UTC (rev 7813)
</span><span class="lines">@@ -49,6 +49,7 @@
</span><span class="cx">                  'buddybar',
</span><span class="cx">                  'template',
</span><span class="cx">                  'functions',
</span><ins>+                       'cache',
</ins><span class="cx">           );
</span><span class="cx"> 
</span><span class="cx">          parent::includes( $includes );
</span></span></pre></div>
<a id="trunkteststestcasesnotificationsfunctionsphp"></a>
<div class="addfile"><h4>Added: trunk/tests/testcases/notifications/functions.php (0 => 7813)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/tests/testcases/notifications/functions.php                                (rev 0)
+++ trunk/tests/testcases/notifications/functions.php   2014-02-06 20:33:50 UTC (rev 7813)
</span><span class="lines">@@ -0,0 +1,55 @@
</span><ins>+<?php
+
+/**
+ * @group notifications
+ */
+class BP_Tests_Notifications_Functions extends BP_UnitTestCase {
+       public function test_cache_invalidation_all_for_user_on_save() {
+               $u = $this->create_user();
+               $n1 = $this->factory->notification->create( array(
+                       'component_name' => 'groups',
+                       'user_id' => $u,
+               ) );
+               $n2 = $this->factory->notification->create( array(
+                       'component_name' => 'messages',
+                       'user_id' => $u,
+               ) );
+
+               // prime cache
+               $count = bp_notifications_get_unread_notification_count( $u );
+
+               // just to be sure...
+               $this->assertEquals( 2, $count, 'Cache count should be 2 before invalidation.' );
+
+               // Trigger invalidation via save
+               $n3 = $this->factory->notification->create( array(
+                       'component_name' => 'messages',
+                       'user_id' => $u,
+               ) );
+
+               $this->assertFalse( wp_cache_get( 'all_for_user_' . $u, 'bp_notifications' ) );
+       }
+
+       public function test_cache_invalidation_all_for_user_on_delete() {
+               $u = $this->create_user();
+               $n1 = $this->factory->notification->create( array(
+                       'component_name' => 'groups',
+                       'user_id' => $u,
+               ) );
+               $n2 = $this->factory->notification->create( array(
+                       'component_name' => 'messages',
+                       'user_id' => $u,
+               ) );
+
+               // prime cache
+               $count = bp_notifications_get_unread_notification_count( $u );
+
+               // just to be sure...
+               $this->assertEquals( 2, $count, 'Cache count should be 2 before invalidation.' );
+
+               // delete
+               BP_Notifications_Notification::delete( array( 'id' => $n1, ) );
+
+               $this->assertFalse( wp_cache_get( 'all_for_user_' . $u, 'bp_notifications' ) );
+       }
+}
</ins></span></pre>
</div>
</div>

</body>
</html>