A Craft-aware array_walk() method, plus some super-convenient console commands, to easily call Craft service methods on a collection of elements or values.

4.0.1 2022-05-16 08:14 UTC

This package is auto-updated.

Last update: 2024-12-16 13:58:45 UTC


README

Provides a Craft-aware array_walk() method, plus some super-convenient console commands, allowing you to easily call Craft service methods on a collection of elements or values.

by Michael Rog

TL;DR.

You want to perform some action, in bulk, on a bunch of elements.

Maybe you want to do it ad-hoc with an easy console command.

Or, maybe you want to do it in one line of PHP, in a custom component.

And you want to do this with several different sets of elements, and with several different bulk actions, without needing to write new code every time.

Walk gives you a special Craft-aware array_walk method to apply a Craft component method to each element in a list.

Installation

Install with Composer by running composer require topshelfcraft/walk from your project directory.

Then, visit the Settings > Plugins page of the CP, and click to Install the Walk plugin.

Walk a what?

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 components 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 component method on each item in a list.

Yes! This will save me minutes! How do I start?

First, you need to have a callable in mind. A callable is the method 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 method in one of Craft's native components: 'component.method'
  • a method in a plugin's module components: 'plugin.component.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.

Technically, you can supply any valid PHP callable object to the craftyArrayWalk method. But if you already have a callable object in-hand, you probably don't have much need for this plugin. The advantage that this plugin's method provides over the native array_walk is that it recognizes those special callable strings, which represent methods in Craft.

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 ]] )

I need an example.

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 = Entry::find()->all();

foreach ($myEntries as $entry)
{
    Craft::$app->getElements->saveElement($entry);
}

But this is tighter:

WalkHelper::craftyArrayWalk($myEntries, 'elements.saveElement');

In both examples, each Entry in the query will be re-saved.

Nifty... but I don't want to write any PHP. What about some console commands?

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 job/criteria combo.

So, without further ado, I give you... the walk CLI command.

./craft walk [list] [callable] --[options]=blah --asJob

So, let's break that down:

  • [list] is:
    • an element type identifier (assets, entries, etc.)
    • an element IDs identifier (assetIds, entryIds, etc.)
    • a "count" directive (countAssets, countEntries, 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) --asJob option... I'll get to that later.
  • The order of options is arbitrary.

If you want to re-save all your blog entries...

./craft walk entries --section=blog --limit=null elements.saveElement

If you have a custom service method, and you want to run it once on each user:

./craft walk users --limit=null myPlugin.myComponent.myMethod

What if your custom method takes an element ID rather than an element object?

./craft walk entryIds myModule.myComponent.methodThatTakesAnId

Tada!

Or, perhaps you want to get a count of elements in a criteria, without actually doing anything to them?

./craft walk countEntries --section=blog

Command options

The following Element Criteria attributes can be set via CLI option:

  • id
  • limit (a number, or null for no limit)
  • title
  • slug
  • relatedTo
  • source
  • sourceId
  • kind
  • filename
  • folderId
  • size
  • group
  • groupId
  • authorGroup
  • authorGroupId
  • authorId
  • locale
  • section
  • status

(...plus, for Commerce Orders...)

  • dateOrdered
  • datePaid
  • email
  • gatewayId
  • hasPurchasables
  • isCompleted
  • isPaid
  • isUnpaid
  • orderStatus
  • orderStatusId
  • customerId

Does it work with custom element types?

The Walk CLI command provides easy shorthands for Craft's built-in element types. However, you can use Walk with any element type by supplying its [fully-qualified] class name, along with the [fully-qualified] class name of the associated element query.

./craft walk/elements "mynamespace\elements\MyElementClass" --queryClass="mynamespace\elements\db\MyElementQuery" myPlugin.myComponent.myMethod

./craft walk/element-ids "craft\commerce\elements\Donation" --queryClass="craft\commerce\elements\db\DonationQuery" myPlugin.myComponent.methodThatTakesAnId

./craft walk/count "craft\elements\Entry" --queryClass="craft\elements\db\EntryQuery" elements.saveElement

Oh, and you said something about Jobs...?

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 the Craft queue...

There are a couple ways Walk might help.

1. Schedule callables as Jobs using the CLI

You can use the special --asJob command option to schedule each walk step as a Job:

./craft walk entries elements.saveElement --asJob

./craft walk entryIds myModule.someComponent.aMethod --asJob

This will still invoke the callable once for each element or ID in the criteria, but it will do so from inside a Job — 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 these show up as CallOnElement and CallOnValue jobs.

2. Use a Job as a callable via the PHP methods

WalkHelper::spawnJobs(MyJob::class, $elementsOrIds, $settings = [], $valParam = 'elementId')

The spawnJobs method can be used to schedule one instance of a specified job per element or ID in the provided set.

The job is specified by full class name, just as if you were using Craft::$app->queue->push().

You can supply an array of extra settings in the $settings argument, which will be applied to each job.

The specified job should expect to receive an elementId 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 $valParam argument.

Can I use this stuff with any array? Does it have to be elements/IDs?

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, 'myPlugin.myService.processEmailAddress');

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 WalkController. You'll find it pretty easy to copy/paste your way to success!

This is great! I still have questions.

Ask a question on StackExchange, and ping me with a URL via email or Discord.

What are the system requirements?

Craft 3.0+ and PHP 7.0+

I found a bug.

Please open a GitHub Issue, submit a PR to the 3.x.dev branch, or just email me.

Contributors: