Posted (Updated ) in PHP

With the release of Laravel 5, there have been a bunch of backwards incompatible changes, new features added as well as the usual influx of new users so I thought I’d take the time to redo my basic to-do application with Laravel 5. The app covers a wide range of concepts, links to relevant learning material where possible and should make for a great introduction to the framework.

This tutorial is relatively long so I’ve broken it up into multiple posts.

  1. Part 1 – Installation, Database and Routes
  2. Part 2 – Listing Projects and Tasks
  3. Part 3 – Create/Edit/Delete
  4. Part 4 – Validation

The source for each part can also be found on GitHub.

Today will cover installation, configuration, artisan, migration, seeding and routes.

Read More »

Posted (Updated ) in PHP

Laravel 4 contained a Form helpers package called HTML but it was removed from 5 to cut down on cruft. HTML is still available as a first party package though so below are instructions on getting it back.

First require it in:

1
composer require "illuminate/html":"5.0.*"

Next up add the service provider and aliases. Open /config/app.php and update as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
'providers' => [
	...
 
	'Illuminate\Html\HtmlServiceProvider',
],
 
'aliases' => [
 
	...
 
	'Form'=> 'Illuminate\Html\FormFacade', 
	'Html'=> 'Illuminate\Html\HtmlFacade',
],

That’s it. To confirm it’s working use the following:

1
2
3
php artisan tinker
> Form::text('foo')
"<input name=\"foo\" type=\"text\">"

Read More »

Posted in PHP

Those of you with MAMP who work with laravel will be very familiar with this dreaded message when attempting to php artisan tinker:

Boris REPL not supported. Needs readline, posix, and pcnt extensions.

Inside where I explain how to install these tools.

Read More »

Posted (Updated ) in PHP

If you want your WordPress site to handle large traffic spikes, loading as many assets as possible from a third party server is a no-brainer. One of the ways to do this is by pointing all asset URLs to a subdomain and having that subdomain load through a service like CloudFront using origin pull. This is done in 3 steps:

  1. Create the CloudFront distribution
  2. Add the subdomain to your DNS
  3. The use of a poorly documented WordPress constant.

In this tutorial I’ll be moving all my sites assets from www.flynsarmy.com to static.flynsarmy.com.

Read More »

Posted in PHP

I’ve been wanting to use iron.io queues with Illuminate/Queue and despite claiming otherwise in its readme, this doesn’t actually work with the provided example code – instead simply resulting in

Fatal error: Uncaught exception ‘ReflectionException’ with message ‘Class encrypter does not exist’ in /vendor/illuminate/container/Illuminate/Container/Container.php on line 485

I’ve tried numerous times to get a fix from Taylor and ignored/given the runaround every time. I even submitted a pull request with the solution but it was closed without merge or explanation.

Anyway, to ACTUALLY use iron.io with illuminate/queue outside of laravel, the following lines of code are required:

1
2
3
4
5
6
$queue->getContainer()->bind('encrypter', function() {
	return new Illuminate\Encryption\Encrypter('foobar');
});
$queue->getContainer()->bind('request', function() {
	return new Illuminate\Http\Request();
});

Drop them below $queue->addConnection and you’re good to go. Here’s complete example file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
require 'vendor/autoload.php';
 
use Illuminate\Queue\Capsule\Manager as Queue;
use Carbon\Carbon;
 
$queue = new Queue;
 
$queue->addConnection(require __DIR__.'/config/queue.php');
 
// Make this Capsule instance available globally via static methods... (optional)
$queue->setAsGlobal();
 
$date = Carbon::now()->addMinutes(2);
$queue->getContainer()->bind('encrypter', function() {
	return new Illuminate\Encryption\Encrypter('foobar');
});
$queue->getContainer()->bind('request', function() {
	return new Illuminate\Http\Request();
});
Queue::push($date, 'EmailTest', array('foo' => 'bar'));
 
class EmailTest
{
	public function fire($job, $data)
	{
		mail('my@email.com', 'hiya', $data['foo']);
	}
}

Read More »

Posted (Updated ) in Javascript, PHP

Imagine you’re running a WordPress Multisite subfolder instance (let’s call it MyNetwork) with a bunch of blogs like so:

mynetwork.com
mynetwork.com/foo
mynetwork.com/bar

Each blog needs to keep track of its page views (including the main blog) but you’d also like page view aggregated statistics for your entire network. This can be done and is surprisingly easy with Google Analytics views and filters. Just follow the below steps and you’ll be on your way.

Read More »

Posted (Updated ) in PHP

Update 2015-05-02: Now working for WP 4.2

In a project today we needed to attach a meta value to a comment based on the user commenting, then filter the comments list (both frontend and backend) by this value. The task turned out to be surprisingly difficult and the comment actions/filters are notoriously badly documented in WP so I thought I’d document the process here.

1. Attach a meta value as a visitor comments

A simple comment_post action (not documented) hook does the trick here.

1
2
3
4
5
6
/**
 * Adds a meta value to a comment as one is created
 */
add_action( 'comment_post', function($comment_id) {
	add_comment_meta( $comment_id, 'my_meta_key', 'my_meta_value' );
});

 

2. Filter the comments by meta (4.2+)

Until 4.2 this was done a little differently (See the bottom of this post for that code) however due to recent changes we’re now forced to use comments_clauses action like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * Limits displayed comments on front and backend to just those with the
 * specified meta key/value pair.
 *
 * @param array $pieces
 * @param WP_Comment_Query $query
 * @return array
 */
add_action('comments_clauses', function(array $pieces, WP_Comment_Query $query) {
    global $wpdb;
 
    $meta_query = new WP_Meta_Query();
    $meta_query->parse_query_vars([
        'meta_key' => 'my_meta_key',        'meta_value' => 'my_meta_value',    ]);
 
    if ( !empty($meta_query->queries) )
    {
        $meta_query_clauses = $meta_query->get_sql( 'comment', $wpdb->comments, 'comment_ID', $query );
 
        if ( !empty($meta_query_clauses) )
        {
            $pieces['join'] .= $meta_query_clauses['join'];
 
            if ( $pieces['where'] )
                $pieces['where'] .= ' AND ';
            // Strip leading 'AND'.
            $pieces['where'] .= preg_replace( '/^\s*AND\s*/', '', $meta_query_clauses['where'] );
 
            if ( !$query->query_vars['count'] )
            {
                $pieces['groupby'] = "{$wpdb->comments}.comment_ID";
            }
        }
    }
 
    return $pieces;
}, 10, 2);

 

3. Making get_comments_number() work

get_comments_number() method commonly used in your themes comments.php file will return the total number of comments associated with a post. We need it filtered by our above meta query. Here’s an inefficient but effective way to make that happen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * Filter get_comments_number() correctly by our meta query.
 *
 * @param int $count
 * @param int $post_id
 * @return int
 */
add_filter('get_comments_number', function($count, $post_id) {
    $query = new WP_Comment_Query(['post_id' => $post_id]);
 
    // Frontend users only see approved comments
    if ( !is_admin() )
        $comment_query['status'] = 'approve';
 
    return sizeof($query->get_comments());
}, 10, 2);

 

Conclusion

That should be it! If anything doesn’t work as expected let me know in the comments below.

 

Bonus: Filter the comments by meta (<4.2)

Filtering comments by meta in <4.2 is done in two steps. One for admin, and one for frontend.

Admin

This can be done with pre_get_comments action hook (again, not documented – but I did find a usage example here).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * Limit visible comments to just those for the current subdomain.
 * Due to a bug in WP this will work for all backend users but only for 
 * logged out users on the frontend.
 */
add_action('pre_get_comments', function($query) {
	// meta_query is already an instance of WP_Comment_Query but other 
	// query_vars may have already been added, so re-initialize it
	$query->meta_query = new WP_Comment_Query();
 
	$query->query_vars['meta_key'] = 'my_meta_key';
	$query->query_vars['meta_value'] = 'my_meta_value';
 
	$query->meta_query->parse_query_vars( $query->query_vars );
});

Frontend

As the pre_get_comments comment mentions, there is a bug in WP whereby logged in users will not have the above filter applied, so we need to use another hook for those.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * WP doesn't support pre_get_comments for logged in users, so for those
 * we need to manually remove all the comments that don't have  our meta
 * key
 *
 * See https://core.trac.wordpress.org/ticket/27018#ticket
 */
add_filter('comments_array', function($comments, $post_id) {
	global $wpdb, $user_ID;
 
	// If there's no user_ID and no commenter then the pre_get_comments filter
	// applied correctly and we don't need to do anything
	// See comments_template() method
	$commenter = wp_get_current_commenter();
	$comment_author = $commenter['comment_author']; // Escaped by sanitize_comment_cookies()
 
	if ( $user_ID || !empty($comment_author) )
		return;
 
	// Grab comment IDs for this current area
	$area_comment_ids = $wpdb->get_col($wpdb->prepare("
		SELECT $wpdb->comments.comment_ID
		FROM $wpdb->comments
		JOIN $wpdb->commentmeta ON $wpdb->commentmeta.comment_id=$wpdb->comments.comment_ID
		WHERE $wpdb->commentmeta.meta_key=%s AND $wpdb->commentmeta.meta_value=%s
	", 'my_meta_key', 'my_meta_value'));
 
	// Strip out any comments not belonging to the current area
	foreach ( $comments as $key => $comment )
		if ( !in_array($comment->comment_ID, $area_comment_ids) )
			unset($comments[$key]);
 
	return $comments;
}, 10, 2);

The above seems really messy and inefficient to me but I can’t think of a better way. It’ll grab all comments we want to show and filter out any others.

Read More »

Posted (Updated ) in PHP

Welcome back to my simple to-do application tutorial for Laravel 4. This tutorial is relatively long so I’ve broken it up into multiple posts.

  1. Part 1 – Installation, Database and Routes
  2. Part 2 – Listing Projects and Tasks
  3. Part 3 – Create/Edit/Delete
  4. Part 4 – Validation and Slugs

So far we’ve learned how to install and set up Laravel, set up some project and task nested resources and display them to the user. Create, edit and delete functionality has also been implemented. In this chapter we’ll finish things off by adding form validation and slug management.

Read More »

Posted (Updated ) in PHP

Welcome back to my simple to-do application tutorial for Laravel 4. This tutorial is relatively long so I’ve broken it up into multiple posts.

  1. Part 1 – Installation, Database and Routes
  2. Part 2 – Listing Projects and Tasks
  3. Part 3 – Create/Edit/Delete
  4. Part 4 – Validation and Slugs

So far we’ve learned how to install and set up Laravel, set up some project and task resources and display them to the user. In this chapter we’ll learn how to use Laravel 4 Generator’s form helpers and set up create, edit and delete pages/actions for our resources.

 

Before you Begin

If you’re a Laracasts member, watch the videos RESTful Forms and Create/Edit Forms. These videos explain far better and in far more detail the concepts below. If you’re serious about learning Laravel I’d highly recommend you sign up.

Read More »

Posted (Updated ) in PHP

Welcome back to my simple to-do application tutorial for Laravel 4. This tutorial is relatively long so I’ve broken it up into multiple posts.

  1. Part 1 – Installation, Database and Routes
  2. Part 2 – Listing Projects and Tasks
  3. Part 3 – Create/Edit/Delete
  4. Part 4 – Validation and Slugs

So far we have a working database complete with test data and a bunch of routes for displaying, editing and deleting our projects and tasks. In this second chapter I’ll cover controllers, models (with relationships), views (including the blade templating language and layouts) and route model binding.

Read More »