Open-source at Alley: What we were up to in 2022

colorful ai-generated heart

Open-source software is central to Alley’s mission and essential to our work. In the last few months, we’ve published several new open-source packages for PHP, JavaScript, and WordPress developers, each of which is rooted in our day-to-day experiences meeting the needs of our clients. 

We’re delighted to highlight some of these new packages and invite you to take them for a spin in your own projects. We release new software regularly, so, as always, you can follow our GitHub, Packagist, and NPM profiles to see what we’re up to.

PHP packages

traverse/reshape

traverse() and reshape() are companion functions for safely deconstructing and reconstructing arrays and objects. traverse() deconstructs the given data and offers conveniences such as the ability to select multiple values in one call and shortcut syntax for accessing nested values. reshape() reconstructs and offers additional features like automatically determining the properties in the new shape and the ability to return a mix of arrays and objects.

$arr = [
	'apples' => [
		'red' => [
			'gala',
			'mcintosh',
		],
		'green' => [
			'granny_smith',
		],
	],
];

$green = \Alley\traverse($arr, 'apples.green');
// ['granny_smith']

$red = \Alley\traverse($arr, 'apples.red');
// ['gala', 'mcintosh']

[$red, $green] = \Alley\traverse($arr, ['apples.red', 'apples.green']);
// ['gala', 'mcintosh'], ['granny_smith']

$sweet = \Alley\traverse($arr, 'apples.green.sweet');
// NULL

Browse on GitHub or install from Packagist.

Laminas Validator Extensions

This package provides additional validation classes for the Laminas Validator framework, including “fast fail” and “any” validator chains, decorators for negating validators and providing custom messages, and a custom base validation class that simplifies writing custom validators.

$valid = new \Alley\Validator\ValidatorByOperator('REGEX', '/^foo/');
$valid->isValid('foobar'); // true

$valid = new \Alley\Validator\ValidatorByOperator('NOT IN', ['bar', 'baz']);
$valid->isValid('bar'); // false

$valid = new \Alley\Validator\ValidatorByOperator('!==', 42);
$valid->isValid(43); // true

Browse on GitHub or install from Packagist.

WordPress packages

Match Blocks

match_blocks() is a function for finding blocks in post content or raw block HTML using parameters such as the block name, inner HTML, attributes, or index. It has a wide range of uses, like finding blocks in content for removal or promotion, or in WP-CLI scripts that audit blocks in post content.

// Find all paragraph blocks in a post:
$grafs = \Alley\WP\match_blocks( $post, [ 'name' => 'core/paragraph' ] );

// Include paragraphs in inner blocks:
$grafs = \Alley\WP\match_blocks(
	$post,
	[
		'flatten' => true,
		'name'    => 'core/paragraph',
	]
);

// Get all paragraphs and headings:
$blocks = \Alley\WP\match_blocks(
	'<!-- wp:paragraph -->…',
	[
		'name' => [ 'core/heading', 'core/paragraph' ],
	]
);

// Get all images credited to the Associated Press:
$images = \Alley\WP\match_blocks(
	$post,
	[
		'attrs' => [
			[
				'key'      => 'credit',
				'value'    => '/(The )?Associated Press/i',
				'operator' => 'REGEX',
			],
			[
				'key'   => 'credit',
				'value' => 'AP',
			],
			'relation' => 'OR',
		],
		'name'  => 'core/image',
	]
);

Browse on GitHub or install from Packagist.

Caper

Caper provides a fluent interface for distributing post type, taxonomy, or generic primitive capabilities to roles. Its fluid syntax makes it easy to create granular permission schemes across multiple post types, taxonomies, and roles while remaining readable and maintainable.

Alley\WP\Caper::grant_to( 'editor' )->primitives( 'edit_theme_options' );

Alley\WP\Caper::deny_to( 'administrator' )->primitives( 'manage_options' );

Alley\WP\Caper::grant_to( 'author' )->caps_for( 'page' );

Alley\WP\Caper::deny_to( 'editor' )->caps_for( 'category' );

Alley\WP\Caper::grant_to( 'editor' )->caps_for( 'category' )->only( 'delete_terms' );

Alley\WP\Caper::deny( 'author' )->caps_for( 'page' )->except( 'delete_posts' );

Browse on GitHub or install from Packagist.

WP Bulk Task

This package provides a library to make it easier to run bulk tasks against a WordPress database in a performant way. It includes functionality to search through a WordPress database for posts using WP_Query arguments and keeps a cursor of its location within the database in case it is interrupted and needs to start again.

( new \Alley\WP_Bulk_Task\Bulk_Task( 'bananaify' ) )->run(
	[ 
		'post_status' => 'publish', 
	],
	function ( $post ) {
		if ( str_contains( $post->post_content, 'apple' ) ) {
			$new_value = str_replace( 'apple', 'banana', $post->post_content );
			
			if ( ! empty( $assoc_args['dry-run'] ) ) {
				\WP_CLI::log( 'Old post_content: ' . $post->post_content );
				\WP_CLI::log( 'New post_content: ' . $new_value );
			} else {
				$post->post_content = $new_value;
				wp_update_post( $post );
			}
		}
	}
);

Browse on GitHub or install from Packagist.

Find One

The find_one() family of functions are wrappers for common WordPress retrieval functions like get_posts() or get_terms() that reduce the return value of those retrieval functions into a single result.

$person = Alley\WP\find_one_post(
	[
		'meta_key'   => 'twitter',
		'meta_value' => '@potatomaster',
		'post_type'  => 'person',
	]
); // ?WP_Post

$category = Alley\WP\find_one_term(
	[
		'slug'     => 'potatomaster',
		'taxonomy' => 'category',
	]
); // ?WP_Term

Browse on GitHub or install from Packagist.

Filter Side Effects

Filter side effects allow you to hook into a WordPress filter and use it like an action when a call to apply_filters() signals that some behavior needs to occur, but no convenient action exists to run it.

Browse on GitHub or install from Packagist.

WordPress plugins

Elasticsearch Extensions

This WordPress plugin makes complex and repetitive operations with Elasticsearch easier by simplifying common tasks like adding aggregations and filtering indexable post types, taxonomies, and post meta in an implementation-agnostic way.

add_action(
	'elasticsearch_extensions_config',
	function ( $es_config ) {
		$es_config
			->restrict_post_types( [ 'post', 'page' ] )
			->enable_empty_search()
			->enable_post_type_aggregation()
			->enable_taxonomy_aggregation( 'category' )
			->enable_taxonomy_aggregation( 'post_tag' );
	}
);

Browse on GitHub.

Create WordPress Plugin

This is a skeleton WordPress plugin that can scaffold a WordPress plugin. This template includes a base plugin file, autoloaded PHP files, unit tests powered by Mantle, frontend assets compiled via Webpack, and continuous integration via GitHub Actions. Actions are configured to test the plugin and also build it for release.

Browse on GitHub.

NPM packages

@alleyinteractive/block-editor-tools

This package contains a set of modules to aid in building features for the WordPress block editor.

Browse on GitHub or install from npm.

@alleyinteractive/stylelint-config

This set of stylelint configuration rules extends the stylelint-config-sass-guidelines intended for use with SCSS syntax.

Browse on GitHub or install from npm

Other packages

@alleyinteractive/hubot-code-review

hubot-code-review is a Hubot script for GitHub code review on Slack. Simply drop a GitHub pull request URL into a room, and Hubot adds the pull request to the room’s queue. Each room has its own queue.

Browse on GitHub or install from npm.