A Craft-aware variant of the array_walk()
method, plus some console commands for extra convenience, to easily apply Craft service/helper/Task methods to a collection of elements.
by Michael Rog
You want to perform some action, in bulk, on a bunch of elements.
Maybe you want to do it with an easy console command.
Or, maybe you want to do it in one line of PHP, in a custom plugin.
And you want to do this with several sets of elements, and with several different bulk actions, without needing to write new code every time.
Walk gives you methods to apply any Craft service method, helper method, or Task to each element in a list.
Drop the walk
directory into your Craft plugins directory, visit the Settings page of the CP, and click to Install the Walk plugin.
Applying a method to every item in a list is known as "walking" an array. In fact, PHP provides a method — array_walk()
— to do just that.
However, PHP's array_walk
method isn't aware of how Craft's services and helpers are set up, and it doesn't know anything about Elements.
So, where PHP's array_walk
is useful for applying a PHP method to each item in an array, this plugin provides a Craft-aware walk function — craftyArrayWalk()
— to run a Craft service method, helper method, or Task on each item in a list.
First, you need to have a callable in mind. A callable is the method/task that will be run on each item in the list.
A callable is specified by its name as a string. With this plugin, a callable can be:
- a Craft service method:
'service.method'
- a helper method from the Craft namespace:
'SomeHelper::method'
- a Craft task:
'DoSomethingTask'
(more on this later...) - any public static method in the root namespace:
'SomeClass::method'
- any accessible custom function:
'myCustomMethod'
- any native PHP function, e.g.
'strtolower'
Second, you need a list of stuff (an array).
Then, just call the craftyArrayWalk()
method, like this:
$success = WalkHelper::craftyArrayWalk($elements, $callable);
It works pretty much just like the native array_walk
method:
- The callable is run once for each item in the array.
- For each step, the item is passed as the first parameter to the method.
- The array index is passed as the second parameter to the method.
- The array item is passed by reference, meaning if you change the variable inside the method, it will be changed in the source array.
craftyArrayWalk()
returns a boolean:true
if successful,false
if there was an issue.
You can also provide extra some custom data if needed, by adding a third parameter, like this:
$success = WalkHelper::craftyArrayWalk($elements, $callable, $userdata);
The $userdata
will be passed as the third parameter to the callable method on each step.
So, in technical terms, a callable method has the following signature:
public function myMethod( $element [, $index [, $userdata ]] )
Let's start with an example of PHP's native array_walk
:
You could do this:
$myArray = ["Michael", "Aaron", "Andrew", "Brad", "Brandon"]
foreach ($myArray as $key => $value)
{
$myArray[$key] = strtolower($value);
}
But this is way prettier:
array_walk($myArray, 'strtolower');
These two examples accomplish the same thing: Afterwards, each string item in the array will have been transformed to lowercase.
Now let's look at a Crafty example.
You could do this:
$myEntries = craft()->elements->getCriteria(ElementType::Entry)->find();
foreach ($myEntries as $entry)
{
craft()->entries->saveEntry($entry);
}
But this is tighter:
WalkHelper::craftyArrayWalk($myEntries, 'entries.saveEntry');
In both examples, each Entry in the criteria will be re-saved.
That's actually why I wrote this plugin: I needed a fast, convenient way to do a bunch of indexing/re-saving, preferably from the CLI, without having to write a new custom command for each task/criteria combo.
So, without further ado, I give you... the walk
CLI command.
php yiic walk [list] [callable] --[options]=blah --asTask
So, let's break that down:
[list]
is either an element type identifier (assets
,entries
, etc.), or an element IDs identifier (assetIds
,entryIds
, etc.).[callable]
is the method/task you want to run on each item, as described above.- There are several supported
[options]
, described below. - The special (optional)
--asTask
option... I'll get to that later. - The order of options is unimportant.
If you want to re-save all your blog entries...
php yiic walk entries --section=blog --limit=null entries.save
If you have a custom service method, and you want to run it once on each user:
php yiic walk users --limit=null myService.myMethod
What if your custom method takes an element ID rather than an element model?
php yiic walk entryIds SomeHelper:methodThatOperatesOnAnId
Tada!
The following Element Criteria attributes can be set via CLI option:
id
limit
(by default,7
)title
slug
relatedTo
source
sourceId
kind
filename
folderId
size
group
groupId
authorGroup
authorGroupId
authorId
locale
section
status
completed
isPaid
isUnPaid
orderStatusId
Say you have a lot of elements... or your callable methods are performance-intensive... or you need things to keep running even if your CLI connection is closed... or you just prefer to schedule things one-at-a-time using Tasks...
There are a few ways Walk might help.
You can use the special --asTask
command option to schedule each walk step as a Task:
php yiic walk entries entries.saveEntry --asTask
php yiic walk entryIds MyHelper::myMethod --asTask
This will still invoke the callable once for each element or ID in the criteria, but it will do so from inside a Task — i.e. one request per element/step.
This is nice if you want to keep track of the queue progress, or if you want to be able to conveniently re-run any steps that fail due to an error.
In the Control Panel sidebar (or Task Manager), these show up as CallOnElement
and CallOnId
tasks.
You can also use any Task (from Craft or any installed plugin) as a callable in its own right:
php yiic walk assetIds ModifyMyAssetTask
This example would schedule a ModifyMyAsset
task for each element or ID in the criteria.
The specified task should expect to receive an id
setting containing the element ID.
WalkHelper::spawnTasks('ModifyMyAsset', $elementsOrIds, $settings = [], $idParam = 'id')
The spawnTasks
method can be used to schedule one instance of a specified task per element or ID in the provided set.
The task is specified by class name, without the "Task" suffix, just as if you were using craft()->tasks->createTask()
.
You can supply an array of extra settings in the $settings
argument, which will be applied to each task.
The specified task should expect to receive an id
setting containing the element ID. Alternatively, you can also change the name of the setting that will receive the ID of the element, using the $idParam
argument.
The console commands are designed to perform bulk actions on sets of Elements or IDs.
However, if you're feeling clever, you can use the craftyArrayWalk()
helper method, with any array.
For example, if you have an array of email addresses, and a processEmailAddress
service method that takes an email address as its first argument...
$success = WalkHelper::craftyArrayWalk($emailAddresses, 'myService.processEmailAddress');
Or if you have that same list of email addresses and a custom ProcessEmailAddressTask that expects an email address in the emailAddress
setting...
WalkHelper::spawnTasks('ProcessEmailAddress', $emailAddresses, $settings = [], $idParam = 'emailAddress')
If you want to make your own console command that walks through some other set (i.e. not Elements or Element IDs), just check out the source code of the WalkCommand. You'll find it pretty easy to copy/paste your way to success!
Okay, why not. You can use the walk count
command for that. Make sure to specify the Element Type using an option, or as an inline argument:
php yiic walk count entries --section=blog
php yiic walk count --type=assets --size=">42M"
Ask a question on StackExchange, and ping me with a URL via email or Slack.
Craft 2.5+ and PHP 7.0+
Please open a GitHub Issue, submit a PR to the dev
branch, or just email me.
- Plugin development: Michael Rog / @michaelrog
- Icon: Margot Nadot, via The Noun Project