PHP Array Iteration: Traversing and Processing Data Efficiently
Learning Objectives
- Master PHP array operations
- Work with different array types
- Use array functions effectively
- Manipulate complex data structures
The Power of Array Iteration in Web Development
Welcome to our deep dive into PHP array iteration! As we continue our journey through PHP's array capabilities, we now focus on how to efficiently traverse and process array data. Iteration—the ability to systematically access each element of an array—is one of the most fundamental operations in programming, especially in web development where we constantly need to process lists of data.
Think of array iteration like walking through a warehouse filled with products. You need an organized way to visit each item, examine it, perhaps modify it, and then move on to the next one. In web development, those "items" might be user records, blog posts, product listings, or any collection of data that powers your website or application.
When working with WordPress, which is built on PHP, mastering array iteration becomes even more crucial. From processing custom post types to handling plugin options, from manipulating widget data to working with WooCommerce products—array iteration is an essential skill that you'll use daily as a WordPress developer.
Understanding Iteration: Concepts and Terminology
Before diving into specific techniques, let's establish a clear understanding of what happens during array iteration:
What Is Array Iteration?
Array iteration is the process of accessing each element in an array, one at a time, in a systematic way. This allows you to:
- Read values stored in the array
- Modify elements as needed
- Perform consistent operations on each item
- Filter or transform the data
Key Terms in Array Iteration
- Current Element: The array element currently being processed during iteration
- Current Key: The key or index associated with the current element
- Internal Array Pointer: A position indicator that PHP maintains to track which array element is currently being processed
- Iterative Statement: A programming construct (like a loop) that handles the process of moving from one element to the next
- Callback Function: A function that gets applied to each element during some forms of iteration
Iteration vs. Traversal
While often used interchangeably, there's a subtle difference:
- Traversal refers to simply visiting each element in sequence
- Iteration typically implies both traversal and performing some operation on each element
In practice, PHP developers use "iteration" for both concepts.
Basic Loop Iteration Techniques
Let's start with the fundamental loop-based methods for iterating through arrays in PHP:
foreach Loop: The PHP Developer's Best Friend
The foreach loop is the most elegant and commonly used method for array iteration in PHP. It was specifically designed for traversing arrays and objects, making it the preferred choice in most situations.
// Basic foreach loop (values only)
$fruits = ["Apple", "Banana", "Cherry", "Dragon fruit"];
foreach ($fruits as $fruit) {
echo $fruit . " is a delicious fruit.<br>";
}
// Outputs:
// Apple is a delicious fruit.
// Banana is a delicious fruit.
// Cherry is a delicious fruit.
// Dragon fruit is a delicious fruit.
// foreach with both keys and values
$user = [
"name" => "John Doe",
"email" => "john@example.com",
"age" => 28,
"city" => "New York"
];
foreach ($user as $key => $value) {
echo "User's " . $key . " is " . $value . "<br>";
}
// Outputs:
// User's name is John Doe
// User's email is john@example.com
// User's age is 28
// User's city is New York
Advantages of foreach:
- Clean, readable syntax specifically designed for arrays
- Automatically handles both indexed and associative arrays
- Easy access to both keys and values
- No need to know array size or structure in advance
- Automatically resets the array pointer when finished
Best Practices with foreach:
- Use descriptive variable names that reflect the content (e.g.,
$productrather than$item) - When you need both keys and values, consistently order them as
foreach ($array as $key => $value) - If you only need values, use the simpler
foreach ($array as $value)syntax - Consider using
&reference when you need to modify array elements
Modifying Array Elements During Iteration
You can modify array elements during iteration by using the & reference operator:
// Modifying array elements using reference
$numbers = [1, 2, 3, 4, 5];
// Double each number
foreach ($numbers as &$number) {
$number *= 2;
}
// Don't forget to unset the reference after the loop
unset($number);
print_r($numbers);
// Outputs: Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 [4] => 10 )
Important: When using references with foreach, always unset the reference variable after the loop. Otherwise, it will continue to reference the last array element, which can cause unexpected bugs if you reuse the variable name.
Real-World Application: Processing WordPress Posts
// Get recent WordPress posts
$recent_posts = get_posts([
'post_type' => 'post',
'posts_per_page' => 5,
'orderby' => 'date',
'order' => 'DESC'
]);
// Process each post
foreach ($recent_posts as $post) {
// Setup post data (makes template tags work)
setup_postdata($post);
echo '<div class="post">';
echo '<h3><a href="' . get_permalink($post) . '">' . get_the_title($post) . '</a></h3>';
echo '<div class="meta">Posted on: ' . get_the_date('F j, Y', $post) . '</div>';
echo '<div class="excerpt">' . get_the_excerpt($post) . '</div>';
echo '</div>';
}
// Always reset post data after the loop
wp_reset_postdata();
for Loop: When You Need More Control
The for loop is a more traditional construct that works well for indexed arrays when you need precise control over the iteration process.
// Basic for loop with indexed array
$colors = ["Red", "Green", "Blue", "Yellow", "Purple"];
$count = count($colors);
for ($i = 0; $i < $count; $i++) {
echo "Color at position " . $i . " is " . $colors[$i] . "<br>";
}
// Outputs:
// Color at position 0 is Red
// Color at position 1 is Green
// Color at position 2 is Blue
// Color at position 3 is Yellow
// Color at position 4 is Purple
// Iterating in reverse order
for ($i = $count - 1; $i >= 0; $i--) {
echo "Color at position " . $i . " is " . $colors[$i] . "<br>";
}
// Outputs:
// Color at position 4 is Purple
// Color at position 3 is Yellow
// Color at position 2 is Blue
// Color at position 1 is Green
// Color at position 0 is Red
// Stepping through every other element
for ($i = 0; $i < $count; $i += 2) {
echo "Every other color: " . $colors[$i] . "<br>";
}
// Outputs:
// Every other color: Red
// Every other color: Blue
// Every other color: Purple
Advantages of for loops:
- Finer control over iteration (step size, direction, starting/ending points)
- Access to the current index at all times
- Works well when you need to process elements based on their position
- Useful for partial array traversal (skipping elements or specific ranges)
Limitations of for loops:
- Works best with numerically indexed arrays, not ideal for associative arrays
- Requires knowing the array size in advance
- More prone to off-by-one errors (e.g., incorrect boundary conditions)
- More complex syntax compared to
foreach
Real-World Application: Building a Responsive WordPress Gallery
// Get image attachments for a gallery
$images = get_attached_media('image', $post_id);
$image_count = count($images);
// Generate responsive gallery
echo '<div class="gallery">';
echo '<div class="gallery-row">';
for ($i = 0; $i < $image_count; $i++) {
$image = array_values($images)[$i];
$image_url = wp_get_attachment_image_url($image->ID, 'medium');
$image_title = get_the_title($image->ID);
// Start a new row every 3 images
if ($i > 0 && $i % 3 == 0) {
echo '</div><div class="gallery-row">';
}
// Output image with custom size based on position
$size_class = ($i % 3 == 1) ? 'large' : 'normal';
echo '<div class="gallery-item ' . $size-class . '">';
echo '<img src="' . esc_url($image_url) . '" alt="' . esc_attr($image_title) . '">';
echo '<div class="image-caption">' . esc_html($image_title) . '</div>';
echo '</div>';
}
echo '</div>'; // Close last row
echo '</div>'; // Close gallery container
while and do-while Loops: Alternative Approaches
While not as commonly used for array iteration, while and do-while loops can be useful in specific scenarios, especially when working with PHP's array pointer functions.
// while loop with array pointer functions
$countries = ["USA", "Canada", "UK", "Australia", "Germany"];
// Reset internal pointer to the beginning
reset($countries);
// Iterate using while and each() (Note: each() is deprecated in PHP 7.2+)
while ($country = current($countries)) {
echo "Current country: " . $country . ", Key: " . key($countries) . "<br>";
next($countries);
}
// Outputs:
// Current country: USA, Key: 0
// Current country: Canada, Key: 1
// Current country: UK, Key: 2
// Current country: Australia, Key: 3
// Current country: Germany, Key: 4
// A better alternative using while with list() and each() (PHP < 7.2)
$cities = ["New York" => "USA", "Toronto" => "Canada", "London" => "UK"];
reset($cities);
while (list($city, $country) = each($cities)) {
echo "City: " . $city . " is in " . $country . "<br>";
}
// Outputs:
// City: New York is in USA
// City: Toronto is in Canada
// City: London is in UK
// Modern approach using while
$fruits = ["Apple", "Banana", "Cherry"];
reset($fruits);
while (key($fruits) !== null) {
echo "Fruit: " . current($fruits) . "<br>";
next($fruits);
}
// Outputs:
// Fruit: Apple
// Fruit: Banana
// Fruit: Cherry
// do-while example
$numbers = [5, 10, 15, 20];
reset($numbers);
$sum = 0;
do {
$current = current($numbers);
if ($current !== false) {
$sum += $current;
}
next($numbers);
} while ($current !== false);
echo "Sum of numbers: " . $sum; // Outputs: Sum of numbers: 50
Important: The each() function is deprecated as of PHP 7.2 and removed in PHP 8.0. For modern PHP development, prefer foreach over while loops with array pointer functions.
When to Use while Loops for Array Iteration:
- When you need complex exit conditions that can't be easily expressed in a for/foreach loop
- When working with older PHP codebases that rely on array pointer functions
- When you need to manipulate the array pointer directly (rare in modern PHP)
- When combining array iteration with other conditions
Real-World Application: Processing Records Until a Condition Is Met
// Process user submissions until finding an approved one or reaching the end
function find_first_approved_submission($submissions) {
reset($submissions);
while (key($submissions) !== null) {
$submission = current($submissions);
if ($submission['status'] === 'approved') {
return $submission;
}
next($submissions);
}
return false; // No approved submission found
}
// Sample usage
$user_submissions = [
['id' => 1, 'title' => 'First attempt', 'status' => 'rejected'],
['id' => 2, 'title' => 'Second attempt', 'status' => 'pending'],
['id' => 3, 'title' => 'Third attempt', 'status' => 'approved'],
['id' => 4, 'title' => 'Fourth attempt', 'status' => 'approved']
];
$approved = find_first_approved_submission($user_submissions);
if ($approved) {
echo "Found approved submission: " . $approved['title'];
} else {
echo "No approved submissions found.";
}
Working with Array Pointer Functions
PHP provides a set of functions for manipulating the internal array pointer, which can be useful for specific iteration scenarios. While these functions are less commonly used in modern PHP (with foreach being preferred), understanding them provides deeper insight into how PHP manages array traversal.
Core Array Pointer Functions
These functions allow you to move and control the internal array pointer:
reset() - Move Pointer to First Element
The reset() function rewinds the internal array pointer to the first element and returns its value.
$colors = ["red", "green", "blue", "yellow"];
// Move pointer somewhere in the array
next($colors);
next($colors);
// Reset pointer to beginning
$first_color = reset($colors);
echo "First color: " . $first_color; // Outputs: First color: red
current() - Get Current Element
The current() function returns the value of the array element that's currently pointed to by the internal pointer.
$fruits = ["apple", "banana", "cherry"];
reset($fruits); // Ensure pointer is at beginning
next($fruits); // Move to second element
$current_fruit = current($fruits);
echo "Current fruit: " . $current_fruit; // Outputs: Current fruit: banana
key() - Get Current Key
The key() function returns the key of the array element that's currently pointed to by the internal pointer.
$user = [
"name" => "John Doe",
"email" => "john@example.com",
"age" => 28
];
reset($user);
next($user);
echo "Current key: " . key($user); // Outputs: Current key: email
next() - Move to Next Element
The next() function advances the internal array pointer one position and returns the value at the new position, or false if there are no more elements.
$numbers = [10, 20, 30, 40];
reset($numbers);
echo "First: " . current($numbers) . "<br>"; // Outputs: First: 10
$second = next($numbers);
echo "Second: " . $second . "<br>"; // Outputs: Second: 20
$third = next($numbers);
echo "Third: " . $third . "<br>"; // Outputs: Third: 30
$fourth = next($numbers);
echo "Fourth: " . $fourth . "<br>"; // Outputs: Fourth: 40
$beyond = next($numbers);
var_dump($beyond); // Outputs: bool(false)
prev() - Move to Previous Element
The prev() function moves the internal array pointer one position backward and returns the value at the new position, or false if there are no previous elements.
$letters = ["A", "B", "C", "D"];
reset($letters);
next($letters);
next($letters); // Pointer is now at "C"
echo "Current: " . current($letters) . "<br>"; // Outputs: Current: C
$previous = prev($letters);
echo "Previous: " . $previous; // Outputs: Previous: B
end() - Move to Last Element
The end() function moves the internal array pointer to the last element and returns its value.
$weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
$last_day = end($weekdays);
echo "Last weekday: " . $last_day; // Outputs: Last weekday: Friday
each() - Get Current Key/Value Pair and Advance Pointer (Deprecated)
The each() function returns the current key/value pair and advances the internal pointer. This function is deprecated as of PHP 7.2 and removed in PHP 8.0.
// Note: Do not use this in modern PHP code
$person = [
"name" => "Jane Smith",
"age" => 30,
"city" => "Chicago"
];
reset($person);
$first_pair = each($person);
print_r($first_pair);
/* Outputs:
Array (
[1] => Jane Smith
[value] => Jane Smith
[0] => name
[key] => name
)
*/
Practical Scenario: Custom Database Query Iterator
Here's an example of using array pointer functions to create a custom database result iterator that processes rows one at a time:
/**
* Processes large database query results efficiently
* by iterating one row at a time rather than loading
* all results into memory at once
*/
function process_large_result_set($query) {
global $wpdb;
// Execute query and get results
$results = $wpdb->get_results($query, ARRAY_A);
if (empty($results)) {
return false;
}
// Initialize array pointer
reset($results);
// Process each row
while (key($results) !== null) {
$row = current($results);
// Process the current row (example operation)
$row['processed'] = true;
$row['timestamp'] = current_time('mysql');
// Save processed data
$wpdb->update(
'processed_data_table',
$row,
['id' => $row['id']]
);
// Move to next row
next($results);
// Optional: Add a small delay to prevent server overload
usleep(10000); // 10ms delay
}
return true;
}
PHP's Built-in Array Iteration Functions
Beyond basic loops, PHP provides a powerful set of built-in functions designed specifically for array iteration. These functions can often make your code more concise and readable while handling common iteration patterns.
Basic Built-in Iteration Functions
array_walk() - Apply a Function to Each Element
The array_walk() function applies a user-defined function to each element of an array in place. It's useful when you need to modify array elements or perform an action for each element.
// Formatting product prices with array_walk()
$products = [
"Laptop" => 999.99,
"Smartphone" => 499.50,
"Headphones" => 129.95
];
function format_price(&$price, $product) {
$price = "$" . number_format($price, 2);
echo "The price of " . $product . " is " . $price . "<br>";
}
array_walk($products, 'format_price');
// Outputs:
// The price of Laptop is $999.99
// The price of Smartphone is $499.50
// The price of Headphones is $129.95
print_r($products);
// Array ( [Laptop] => $999.99 [Smartphone] => $499.50 [Headphones] => $129.95 )
Using Additional Parameters
// Add tax to prices using array_walk() with additional parameter
$products = [
"Laptop" => 999.99,
"Smartphone" => 499.50,
"Headphones" => 129.95
];
function add_tax(&$price, $product, $tax_rate) {
$tax_amount = $price * $tax_rate;
$price += $tax_amount;
$price = round($price, 2);
echo $product . " with " . ($tax_rate * 100) . "% tax: $" . $price . "<br>";
}
$tax_rate = 0.08; // 8% tax
array_walk($products, 'add_tax', $tax_rate);
// Outputs:
// Laptop with 8% tax: $1079.99
// Smartphone with 8% tax: $539.46
// Headphones with 8% tax: $140.35
Using Anonymous Functions (Closures)
// Using anonymous function with array_walk()
$users = [
"user1" => ["name" => "John", "email" => "john@example.com", "active" => true],
"user2" => ["name" => "Jane", "email" => "jane@example.com", "active" => false],
"user3" => ["name" => "Bob", "email" => "bob@example.com", "active" => true]
];
array_walk($users, function(&$user, $user_id) {
// Add a last login timestamp to each user
$user['last_login'] = $user['active'] ? date('Y-m-d H:i:s') : 'Never logged in';
// Generate a display string
$user['display'] = $user['name'] . " (" . $user['email'] . ")";
});
// Display modified user data
foreach ($users as $user_id => $user_data) {
echo $user_id . ": " . $user_data['display'] . " - Last login: " . $user_data['last_login'] . "<br>";
}
Real-World Application: Processing WordPress Meta Fields
// Get all post meta for a specific post
$post_id = 123;
$post_meta = get_post_meta($post_id);
// Process each meta field
array_walk($post_meta, function(&$meta_values, $meta_key) use ($post_id) {
// WordPress stores meta values as arrays, even for single values
if (count($meta_values) === 1) {
// Convert to a single value instead of array
$meta_values = $meta_values[0];
}
// Process specific meta types
if (strpos($meta_key, 'date_') === 0 && !empty($meta_values)) {
// Format dates
$meta_values = date('F j, Y', strtotime($meta_values));
} elseif ($meta_key === '_thumbnail_id' && !empty($meta_values)) {
// Get image URL for featured image
$meta_values = wp_get_attachment_url($meta_values);
} elseif ($meta_key === '_product_price' && !empty($meta_values)) {
// Format prices
$meta_values = '$' . number_format((float)$meta_values, 2);
}
});
// Now $post_meta contains processed values
array_walk_recursive() - Apply a Function Recursively
The array_walk_recursive() function applies a user-defined function to each element of an array, including elements in nested arrays. It's perfect for processing multi-dimensional arrays.
// Sanitize all string values in a nested array structure
$form_data = [
"personal" => [
"name" => "John < Script > Doe",
"email" => "john@example.com"
],
"address" => [
"street" => "123 Main St.",
"city" => "New York",
"details" => [
"apartment" => "Apt. 4B",
"buzzer" => "402"
]
],
"comments" => "This is a < test > comment"
];
// Sanitize all string values
array_walk_recursive($form_data, function(&$value) {
if (is_string($value)) {
$value = htmlspecialchars($value);
}
});
// Now all string values are sanitized
print_r($form_data);
/* Outputs sanitized values like:
Array (
[personal] => Array (
[name] => John < Script > Doe
[email] => john@example.com
)
...
)
*/
Important: array_walk_recursive() only processes leaf nodes (values that aren't arrays). It won't apply the callback to arrays themselves, only to their non-array values.
Real-World Application: Preparing WordPress Options for Display
// Get a complex theme options array
$theme_options = get_option('theme_options');
// Prepare all values for display
function prepare_for_display(&$value) {
if (is_string($value)) {
// Sanitize HTML and prepare for display
$value = esc_html($value);
} elseif (is_numeric($value) && strpos($value, '.') !== false) {
// Format decimal numbers
$value = number_format((float)$value, 2);
} elseif ($value === '1' || $value === 1) {
// Convert boolean-like values to Yes/No
$value = 'Yes';
} elseif ($value === '0' || $value === 0 || $value === '') {
$value = 'No';
}
}
// Safely prepare all values for display
if (is_array($theme_options)) {
array_walk_recursive($theme_options, 'prepare_for_display');
}
// Now all values are safe for display
foreach ($theme_options as $section => $settings) {
echo '<h3>' . ucfirst($section) . ' Settings</h3>';
echo '<ul>';
if (is_array($settings)) {
foreach ($settings as $key => $value) {
echo '<li><strong>' . str_replace('_', ' ', ucfirst($key)) . ':</strong> ';
if (is_array($value)) {
echo implode(', ', $value);
} else {
echo $value;
}
echo '</li>';
}
}
echo '</ul>';
}
array_map() - Transform All Elements
The array_map() function applies a callback function to each element of an array and returns a new array with the transformed values. Unlike array_walk(), it doesn't modify the original array but creates a new one.
// Transform an array of numbers using array_map()
$numbers = [1, 2, 3, 4, 5];
// Square each number
$squared = array_map(function($n) {
return $n * $n;
}, $numbers);
print_r($squared);
// Outputs: Array ( [0] => 1 [1] => 4 [2] => 9 [3] => 16 [4] => 25 )
// Process an array of strings
$names = ["john doe", "jane smith", "robert johnson"];
// Capitalize each name
$formatted_names = array_map('ucwords', $names);
print_r($formatted_names);
// Outputs: Array ( [0] => John Doe [1] => Jane Smith [2] => Robert Johnson )
Processing Multiple Arrays Simultaneously
// Combine multiple arrays with array_map()
$first_names = ["John", "Jane", "Bob"];
$last_names = ["Doe", "Smith", "Johnson"];
$ages = [28, 34, 42];
// Combine into full user profiles
$users = array_map(function($first, $last, $age) {
return [
"name" => $first . " " . $last,
"email" => strtolower($first) . "@example.com",
"age" => $age
];
}, $first_names, $last_names, $ages);
print_r($users);
/* Outputs:
Array (
[0] => Array (
[name] => John Doe
[email] => john@example.com
[age] => 28
)
[1] => Array (
[name] => Jane Smith
[email] => jane@example.com
[age] => 34
)
[2] => Array (
[name] => Bob Johnson
[email] => bob@example.com
[age] => 42
)
)
*/
Real-World Application: Preparing WordPress Posts for an API Response
// Get recent posts
$recent_posts = get_posts([
'post_type' => 'post',
'posts_per_page' => 10,
'orderby' => 'date',
'order' => 'DESC'
]);
// Transform posts for API response
$api_posts = array_map(function($post) {
// Get the featured image if available
$thumbnail_id = get_post_thumbnail_id($post->ID);
$thumbnail_url = $thumbnail_id ?
wp_get_attachment_image_url($thumbnail_id, 'medium') : '';
// Get the primary category
$categories = get_the_category($post->ID);
$primary_category = !empty($categories) ? $categories[0]->name : '';
// Build API-friendly post object
return [
'id' => $post->ID,
'title' => html_entity_decode(get_the_title($post)),
'slug' => $post->post_name,
'date' => get_the_date('c', $post),
'excerpt' => get_the_excerpt($post),
'category' => $primary_category,
'image' => $thumbnail_url,
'url' => get_permalink($post)
];
}, $recent_posts);
// Return as JSON
wp_send_json($api_posts);
array_filter() - Filtering Elements
The array_filter() function filters elements of an array using a callback function and returns a new array with the elements that pass the test. If no callback is provided, it removes empty elements.
// Basic filtering of even numbers
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Get only even numbers
$even_numbers = array_filter($numbers, function($n) {
return $n % 2 === 0;
});
print_r($even_numbers);
// Outputs: Array ( [1] => 2 [3] => 4 [5] => 6 [7] => 8 [9] => 10 )
// Note that original keys are preserved
// Filter out empty/falsy values (no callback needed)
$mixed_values = [0, null, false, '', 'hello', 42, [], ['item']];
$non_empty = array_filter($mixed_values);
print_r($non_empty);
// Outputs: Array ( [4] => hello [5] => 42 [7] => Array ( [0] => item ) )
Using Filter Flags (PHP 5.6+)
// Filter by keys, values, or both
$user = [
"id" => 123,
"name" => "John Doe",
"email" => "john@example.com",
"inactive" => false,
"role" => "subscriber",
"meta" => ["signup_date" => "2023-01-15"]
];
// Filter by keys (get only fields starting with "e")
$e_fields = array_filter($user, function($key) {
return strpos($key, 'e') === 0;
}, ARRAY_FILTER_USE_KEY);
print_r($e_fields);
// Outputs: Array ( [email] => john@example.com )
// Filter by both key and value
$important_fields = array_filter($user, function($value, $key) {
// Keep fields that are not arrays and don't start with "i"
return !is_array($value) && strpos($key, 'i') !== 0;
}, ARRAY_FILTER_USE_BOTH);
print_r($important_fields);
// Outputs: Array ( [id] => 123 [name] => John Doe [email] => john@example.com
// [role] => subscriber )
Real-World Application: Filtering WordPress Content
// Get all published posts
$all_posts = get_posts([
'post_type' => 'post',
'posts_per_page' => -1,
'post_status' => 'publish'
]);
// Filter posts with specific criteria:
// 1. Has featured image
// 2. At least 300 words in content
// 3. Published within the last month
$featured_posts = array_filter($all_posts, function($post) {
// Check for featured image
$has_thumbnail = has_post_thumbnail($post->ID);
// Count words in content
$word_count = str_word_count(strip_tags($post->post_content));
// Check publish date (within last month)
$publish_date = strtotime($post->post_date);
$one_month_ago = strtotime('-1 month');
$is_recent = $publish_date >= $one_month_ago;
return $has_thumbnail && $word_count >= 300 && $is_recent;
});
// Reindex array keys
$featured_posts = array_values($featured_posts);
echo "Found " . count($featured_posts) . " featured posts.";
array_reduce() - Reducing to a Single Value
The array_reduce() function iteratively applies a callback function to the elements of an array to reduce it to a single value. This is perfect for sums, averages, concatenations, or any operation that combines multiple values.
// Sum an array of numbers
$numbers = [10, 20, 30, 40, 50];
$sum = array_reduce($numbers, function($carry, $item) {
return $carry + $item;
}, 0); // The 0 is the initial value
echo "Sum: " . $sum; // Outputs: Sum: 150
// Find the maximum value
$max = array_reduce($numbers, function($carry, $item) {
return ($item > $carry) ? $item : $carry;
}, PHP_INT_MIN);
echo "Maximum: " . $max; // Outputs: Maximum: 50
// Join array elements with a separator
$words = ["PHP", "is", "awesome"];
$sentence = array_reduce($words, function($carry, $word) {
return $carry . ($carry ? ' ' : '') . $word;
}, '');
echo $sentence; // Outputs: PHP is awesome
Complex Reduction Operations
// Calculate statistics from an array
$scores = [85, 92, 78, 95, 88, 76, 90, 84];
$stats = array_reduce($scores, function($carry, $score) {
$carry['count']++;
$carry['total'] += $score;
if ($score > $carry['max']) {
$carry['max'] = $score;
}
if ($score < $carry['min']) {
$carry['min'] = $score;
}
return $carry;
}, [
'count' => 0,
'total' => 0,
'max' => PHP_INT_MIN,
'min' => PHP_INT_MAX
]);
// Calculate the average
$stats['average'] = $stats['total'] / $stats['count'];
print_r($stats);
/* Outputs:
Array (
[count] => 8
[total] => 688
[max] => 95
[min] => 76
[average] => 86
)
*/
Real-World Application: Calculating WordPress E-commerce Statistics
// Get recent orders from WooCommerce
$orders = wc_get_orders([
'limit' => 100,
'status' => 'completed',
'date_created' => '>' . date('Y-m-d', strtotime('-30 days'))
]);
// Calculate sales statistics
$sales_stats = array_reduce($orders, function($stats, $order) {
// Increment order count
$stats['total_orders']++;
// Add order total
$order_total = $order->get_total();
$stats['total_revenue'] += $order_total;
// Track maximum order value
if ($order_total > $stats['max_order']) {
$stats['max_order'] = $order_total;
$stats['max_order_id'] = $order->get_id();
}
// Count total items sold
$items = $order->get_items();
$item_count = 0;
foreach ($items as $item) {
$item_count += $item->get_quantity();
// Track product sales by ID
$product_id = $item->get_product_id();
if (!isset($stats['products'][$product_id])) {
$stats['products'][$product_id] = [
'name' => $item->get_name(),
'quantity' => 0,
'revenue' => 0
];
}
$stats['products'][$product_id]['quantity'] += $item->get_quantity();
$stats['products'][$product_id]['revenue'] += $item->get_total();
}
$stats['total_items'] += $item_count;
// Calculate average items per order
$stats['avg_items'] = $stats['total_items'] / $stats['total_orders'];
// Calculate average order value
$stats['avg_order'] = $stats['total_revenue'] / $stats['total_orders'];
return $stats;
}, [
'total_orders' => 0,
'total_revenue' => 0,
'total_items' => 0,
'max_order' => 0,
'max_order_id' => 0,
'avg_items' => 0,
'avg_order' => 0,
'products' => []
]);
// Sort products by quantity sold
arsort($sales_stats['products']['quantity']);
// Get top 5 products
$top_products = array_slice($sales_stats['products'], 0, 5, true);
$sales_stats['top_products'] = $top_products;
// Display the sales report
echo '<h2>30-Day Sales Report</h2>';
echo '<p>Total Orders: ' . $sales_stats['total_orders'] . '</p>';
echo '<p>Total Revenue: $' . number_format($sales_stats['total_revenue'], 2) . '</p>';
echo '<p>Average Order Value: $' . number_format($sales_stats['avg_order'], 2) . '</p>';
echo '<p>Items Sold: ' . $sales_stats['total_items'] . '</p>';
Advanced Iteration Techniques
Let's explore some more sophisticated approaches to array iteration that can help you handle complex data structures and optimize performance.
Iterator Objects and SPL Iterators
PHP's Standard PHP Library (SPL) provides specialized iterator classes that offer more control over the iteration process. These are particularly useful for advanced scenarios where custom iteration logic is needed.
Using ArrayIterator
// Create an ArrayIterator
$fruits = ["Apple", "Banana", "Cherry", "Dragon fruit"];
$iterator = new ArrayIterator($fruits);
// Basic iteration
while ($iterator->valid()) {
echo "Current fruit: " . $iterator->current() . "<br>";
$iterator->next();
}
// Outputs:
// Current fruit: Apple
// Current fruit: Banana
// Current fruit: Cherry
// Current fruit: Dragon fruit
// Seeking to a specific position
$iterator->rewind(); // Reset to beginning
$iterator->seek(2); // Jump to index 2
echo "Fruit at position 2: " . $iterator->current() . "<br>"; // Outputs: Fruit at position 2: Cherry
// Modifying values
$iterator->rewind();
while ($iterator->valid()) {
$key = $iterator->key();
$value = $iterator->current();
// Convert to uppercase
$iterator[$key] = strtoupper($value);
$iterator->next();
}
// Check the modified array
$modified_fruits = $iterator->getArrayCopy();
print_r($modified_fruits);
// Outputs: Array ( [0] => APPLE [1] => BANANA [2] => CHERRY [3] => DRAGON FRUIT )
Using FilterIterator
// Custom FilterIterator to select only even numbers
class EvenNumbersIterator extends FilterIterator {
public function accept() {
return $this->current() % 2 === 0;
}
}
// Create base iterator
$numbers = new ArrayIterator([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
// Create filtered iterator
$even_numbers = new EvenNumbersIterator($numbers);
// Iterate over even numbers only
foreach ($even_numbers as $key => $value) {
echo "Even number at position $key: $value<br>";
}
// Outputs:
// Even number at position 1: 2
// Even number at position 3: 4
// Even number at position 5: 6
// Even number at position 7: 8
// Even number at position 9: 10
Real-World Application: WordPress Custom Post Iterator
/**
* Custom Iterator for WordPress Posts
* Filters out posts that don't meet specific criteria
*/
class FeaturedPostIterator extends FilterIterator {
public function accept() {
$post = $this->current();
// Only accept posts that:
// 1. Have a featured image
// 2. Are not password protected
// 3. Have at least 100 words of content
if (!has_post_thumbnail($post->ID)) {
return false;
}
if ($post->post_password !== '') {
return false;
}
$word_count = str_word_count(strip_tags($post->post_content));
if ($word_count < 100) {
return false;
}
return true;
}
}
// Usage in a WordPress template
function display_featured_posts() {
// Get recent posts
$recent_posts = get_posts([
'post_type' => 'post',
'posts_per_page' => 20,
'orderby' => 'date',
'order' => 'DESC'
]);
// Create iterator
$posts_iterator = new ArrayIterator($recent_posts);
$featured_iterator = new FeaturedPostIterator($posts_iterator);
// Display only posts that pass the filter
echo '<div class="featured-posts">';
foreach ($featured_iterator as $post) {
setup_postdata($post);
echo '<article class="post">';
echo '<a href="' . get_permalink($post) . '">';
echo get_the_post_thumbnail($post, 'medium');
echo '<h3>' . get_the_title($post) . '</h3>';
echo '</a>';
echo '<div class="excerpt">' . get_the_excerpt($post) . '</div>';
echo '</article>';
}
echo '</div>';
wp_reset_postdata();
}
Using Generators for Memory-Efficient Iteration
Generators provide a powerful way to iterate over large datasets without loading everything into memory at once. They're perfect for processing large files or database results.
Basic Generator Example
// Generator function that yields numbers from a range
function rangeGenerator($start, $end, $step = 1) {
for ($i = $start; $i <= $end; $i += $step) {
yield $i;
}
}
// Use the generator
foreach (rangeGenerator(1, 10, 2) as $number) {
echo "Number: $number<br>";
}
// Outputs:
// Number: 1
// Number: 3
// Number: 5
// Number: 7
// Number: 9
Yielding Key-Value Pairs
// Generator that yields transformed data with keys
function userDataGenerator($users) {
foreach ($users as $user) {
$key = $user['id'];
$value = [
'name' => $user['first_name'] . ' ' . $user['last_name'],
'email' => strtolower($user['email']),
'active' => (bool)$user['active']
];
yield $key => $value;
}
}
// Sample user data
$users = [
['id' => 101, 'first_name' => 'John', 'last_name' => 'Doe', 'email' => 'JOHN@example.com', 'active' => 1],
['id' => 102, 'first_name' => 'Jane', 'last_name' => 'Smith', 'email' => 'JANE@example.com', 'active' => 0],
['id' => 103, 'first_name' => 'Bob', 'last_name' => 'Johnson', 'email' => 'BOB@example.com', 'active' => 1]
];
// Process users with the generator
foreach (userDataGenerator($users) as $id => $user) {
echo "User #$id: {$user['name']} ({$user['email']}) - ";
echo $user['active'] ? 'Active' : 'Inactive';
echo "<br>";
}
// Outputs:
// User #101: John Doe (john@example.com) - Active
// User #102: Jane Smith (jane@example.com) - Inactive
// User #103: Bob Johnson (bob@example.com) - Active
Real-World Application: Processing Large WordPress Database Tables
/**
* Generator function to efficiently process large WordPress database tables
* by fetching rows in batches
*/
function largeTableGenerator($table_name, $batch_size = 100, $where = '') {
global $wpdb;
// Count total rows (optional, but helpful for progress tracking)
$count_query = "SELECT COUNT(*) FROM $table_name";
if (!empty($where)) {
$count_query .= " WHERE $where";
}
$total_rows = $wpdb->get_var($count_query);
// Process in batches
$offset = 0;
$processed = 0;
while ($processed < $total_rows) {
// Fetch a batch of rows
$query = "SELECT * FROM $table_name";
if (!empty($where)) {
$query .= " WHERE $where";
}
$query .= " LIMIT $offset, $batch_size";
$results = $wpdb->get_results($query, ARRAY_A);
// Stop if no more results
if (empty($results)) {
break;
}
// Yield each row
foreach ($results as $row) {
yield $row;
$processed++;
}
// Update offset for next batch
$offset += $batch_size;
}
}
// Usage example: Process a large comments table
function process_spam_comments() {
$table = $wpdb->comments;
$generator = largeTableGenerator(
$table,
200, // 200 rows per batch
"comment_approved = 'spam'"
);
$processed = 0;
$deleted = 0;
// Start timer
$start_time = microtime(true);
// Process comments
foreach ($generator as $comment) {
$processed++;
// Perform processing - e.g., delete comments older than 30 days
$comment_date = strtotime($comment['comment_date']);
$thirty_days_ago = strtotime('-30 days');
if ($comment_date < $thirty_days_ago) {
wp_delete_comment($comment['comment_ID'], true);
$deleted++;
}
// Show progress every 1000 comments
if ($processed % 1000 === 0) {
echo "Processed $processed comments, deleted $deleted...<br>";
// Flush output buffer to show progress
ob_flush();
flush();
}
}
$time_taken = microtime(true) - $start_time;
echo "Completed processing $processed spam comments.<br>";
echo "Deleted $deleted old spam comments.<br>";
echo "Time taken: " . round($time_taken, 2) . " seconds.";
}
Recursive Iteration for Nested Structures
When working with deeply nested arrays, recursive iteration techniques can help you process all levels efficiently.
Custom Recursive Function
// Process a nested array structure recursively
function process_array_recursively($array, $callback, $path = []) {
foreach ($array as $key => $value) {
// Build the current path
$current_path = array_merge($path, [$key]);
if (is_array($value)) {
// Recurse into nested array
process_array_recursively($value, $callback, $current_path);
} else {
// Process leaf value
$callback($value, $key, $current_path);
}
}
}
// Example nested data
$data = [
'personal' => [
'name' => 'John Doe',
'contact' => [
'email' => 'john@example.com',
'phone' => '555-1234'
]
],
'preferences' => [
'theme' => 'dark',
'notifications' => [
'email' => true,
'push' => false
]
]
];
// Define a callback function
$output = [];
$callback = function($value, $key, $path) use (&$output) {
$path_string = implode('.', $path);
$output[$path_string] = $value;
echo "$path_string = $value<br>";
};
// Process the nested array
process_array_recursively($data, $callback);
/* Outputs:
personal.name = John Doe
personal.contact.email = john@example.com
personal.contact.phone = 555-1234
preferences.theme = dark
preferences.notifications.email = 1
preferences.notifications.push =
*/
// Process nested arrays with RecursiveIteratorIterator
$data = [
'settings' => [
'general' => [
'site_title' => 'My WordPress Site',
'tagline' => 'Just another WordPress site'
],
'reading' => [
'posts_per_page' => 10,
'show_on_front' => 'page'
]
],
'theme' => [
'name' => 'Twenty Twenty-One',
'colors' => [
'primary' => '#0d6efd',
'secondary' => '#6c757d'
]
]
];
// Create recursive iterators
$recursiveArrayIterator = new RecursiveArrayIterator($data);
$recursiveIterator = new RecursiveIteratorIterator(
$recursiveArrayIterator,
RecursiveIteratorIterator::LEAVES_ONLY
);
// Iterate through all leaf nodes
foreach ($recursiveIterator as $key => $value) {
// Get path to current element
$path = [];
for ($i = 0; $i <= $recursiveIterator->getDepth(); $i++) {
$path[] = $recursiveIterator->getSubIterator($i)->key();
}
$path_string = implode('.', $path);
echo "$path_string = $value<br>";
}
/* Outputs:
settings.general.site_title = My WordPress Site
settings.general.tagline = Just another WordPress site
settings.reading.posts_per_page = 10
settings.reading.show_on_front = page
theme.name = Twenty Twenty-One
theme.colors.primary = #0d6efd
theme.colors.secondary = #6c757d
*/
Real-World Application: Processing WordPress Theme Options
/**
* Process nested theme options and sanitize all values
*/
function sanitize_theme_options($options) {
// Create ArrayIterator for the options array
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($options),
RecursiveIteratorIterator::SELF_FIRST
);
$result = [];
$current = &$result;
// Iterate through all elements
foreach ($iterator as $key => $value) {
// Get path to current element
$path = [];
for ($i = 0; $i < $iterator->getDepth(); $i++) {
$path[] = $iterator->getSubIterator($i)->key();
}
// Build the path in our result array
$current = &$result;
foreach ($path as $path_key) {
if (!isset($current[$path_key]) || !is_array($current[$path_key])) {
$current[$path_key] = [];
}
$current = &$current[$path_key];
}
// Apply appropriate sanitization based on key and value type
if (is_array($value)) {
$current[$key] = [];
} else {
// Sanitize based on key name pattern
if (strpos($key, 'html_') === 0) {
// Allow limited HTML
$current[$key] = wp_kses_post($value);
} elseif (strpos($key, 'url_') === 0 || $key === 'url') {
// Sanitize URLs
$current[$key] = esc_url_raw($value);
} elseif (strpos($key, 'email_') === 0 || $key === 'email') {
// Sanitize email addresses
$current[$key] = sanitize_email($value);
} elseif (strpos($key, 'num_') === 0 || is_numeric($value)) {
// Sanitize numbers
$current[$key] = intval($value);
} elseif ($value === 'true' || $value === 'false' || is_bool($value)) {
// Handle boolean values
$current[$key] = ($value === 'true' || $value === true) ? true : false;
} else {
// Default text sanitization
$current[$key] = sanitize_text_field($value);
}
}
}
return $result;
}
// Usage example
$theme_options = [
'general' => [
'site_title' => 'My <strong>WordPress</strong> Site',
'tagline' => 'Just another <script>alert("XSS")</script> WordPress site',
'logo_url' => 'http://example.com/logo.png'
],
'colors' => [
'primary' => '#0d6efd',
'custom' => [
'header' => '#333333',
'footer' => '#222222'
]
],
'contact' => [
'email' => 'admin@example.com',
'html_address' => '123 Main St.<br>Suite 101<br>New York, NY 10001'
]
];
$sanitized_options = sanitize_theme_options($theme_options);
// Save sanitized options
update_option('theme_options', $sanitized_options);
Performance Considerations
The way you iterate through arrays can significantly impact your application's performance, especially when dealing with large datasets. Here are some key considerations and best practices:
Choosing the Right Iteration Method
Different iteration methods have different performance characteristics:
- foreach is generally the most efficient way to iterate through arrays in PHP for most use cases
- for loops can be slightly faster for simple indexed arrays with numeric keys
- array_map(), array_filter(), and other built-in functions may have slightly more overhead but offer cleaner code
- while loops with array pointer functions are generally slower and should be avoided unless necessary
Performance Comparison
| Method | Speed | Memory Usage | Best For |
|---|---|---|---|
| foreach | Fast | Low | General purpose iteration, complex arrays |
| for | Very Fast | Very Low | Simple indexed arrays, when performance is critical |
| array_map() | Medium | Medium | Transforming array values, functional style |
| array_walk() | Medium | Low | Modifying array elements in place |
| array_filter() | Medium | Medium | Filtering elements by condition |
| array_reduce() | Medium | Low | Calculating aggregate values |
| while with pointers | Slow | Low | Legacy code, specific pointer manipulation |
| Generators | Medium | Very Low | Very large datasets, memory-constrained environments |
Optimization Strategies
- Avoid nested loops when possible, as they can lead to O(n²) complexity
- Pre-calculate array sizes for for loops using
count()outside the loop - Use references (
&) in foreach loops when modifying array elements to avoid copies - Consider generators for large datasets to minimize memory usage
- Cache results of expensive operations rather than recalculating them in each iteration
- Use array_* functions for their intended purposes rather than reinventing the wheel
- Break out of loops early when you've found what you're looking for
Optimizing Loop Performance
// Inefficient approach
$items = get_large_array_of_items(); // Thousands of items
$total = 0;
foreach ($items as $item) {
// Calling count() inside a loop is inefficient
for ($i = 0; $i < count($item['options']); $i++) {
$total += calculate_expensive_value($item['options'][$i]);
}
}
// Optimized approach
$items = get_large_array_of_items();
$total = 0;
foreach ($items as $item) {
// Pre-calculate the count
$option_count = count($item['options']);
// Cache expensive calculations
$option_values = [];
for ($i = 0; $i < $option_count; $i++) {
$option_values[$i] = calculate_expensive_value($item['options'][$i]);
}
// Sum the values
$total += array_sum($option_values);
}
Using Generators for Memory Efficiency
// Memory-intensive approach (loads all records at once)
function get_all_records() {
global $wpdb;
// This might consume a lot of memory with thousands of records
return $wpdb->get_results("SELECT * FROM large_data_table", ARRAY_A);
}
$records = get_all_records();
foreach ($records as $record) {
process_record($record);
}
// Memory-efficient approach using generators
function record_generator() {
global $wpdb;
// Get total count
$total = $wpdb->get_var("SELECT COUNT(*) FROM large_data_table");
// Process in batches of 100
$batch_size = 100;
for ($offset = 0; $offset < $total; $offset += $batch_size) {
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM large_data_table LIMIT %d, %d",
$offset,
$batch_size
),
ARRAY_A
);
foreach ($results as $result) {
yield $result;
}
}
}
// This processes records one at a time without loading all into memory
foreach (record_generator() as $record) {
process_record($record);
}
Practical Applications in WordPress Development
Let's explore some real-world scenarios where array iteration techniques are essential in WordPress development:
Processing Custom Post Types
Creating customized displays for WordPress custom post types often requires efficient array iteration:
/**
* Display featured portfolio items with categorized grouping
*/
function display_portfolio_grid() {
// Get portfolio items
$portfolio_args = [
'post_type' => 'portfolio',
'posts_per_page' => -1,
'orderby' => 'date',
'order' => 'DESC'
];
$portfolio_items = get_posts($portfolio_args);
if (empty($portfolio_items)) {
echo '<p>No portfolio items found.</p>';
return;
}
// Extract all categories
$all_categories = [];
foreach ($portfolio_items as $item) {
$categories = get_the_terms($item->ID, 'portfolio_category');
if (!empty($categories) && !is_wp_error($categories)) {
foreach ($categories as $category) {
$all_categories[$category->slug] = $category->name;
}
}
}
// Sort categories alphabetically
asort($all_categories);
// Group portfolio items by category
$categorized_items = [];
foreach ($portfolio_items as $item) {
$item_categories = get_the_terms($item->ID, 'portfolio_category');
if (!empty($item_categories) && !is_wp_error($item_categories)) {
foreach ($item_categories as $category) {
if (!isset($categorized_items[$category->slug])) {
$categorized_items[$category->slug] = [];
}
// Add the item to this category
$categorized_items[$category->slug][] = [
'id' => $item->ID,
'title' => get_the_title($item),
'permalink' => get_permalink($item),
'thumbnail' => get_the_post_thumbnail_url($item, 'medium'),
'excerpt' => get_the_excerpt($item)
];
}
}
}
// Display navigation tabs for categories
echo '<div class="portfolio-categories">';
echo '<ul class="category-tabs">';
echo '<li class="active"><a href="#all">All</a></li>';
foreach ($all_categories as $slug => $name) {
echo '<li><a href="#' . esc_attr($slug) . '">' . esc_html($name) . '</a></li>';
}
echo '</ul>';
echo '</div>';
// Display all portfolio items
echo '<div class="portfolio-grid" id="all">';
foreach ($portfolio_items as $item) {
echo '<div class="portfolio-item">';
echo '<a href="' . get_permalink($item) . '">';
if (has_post_thumbnail($item)) {
echo get_the_post_thumbnail($item, 'medium');
} else {
echo '<div class="no-thumbnail">No Image</div>';
}
echo '<h3>' . get_the_title($item) . '</h3>';
echo '</a>';
echo '</div>';
}
echo '</div>';
// Display categorized portfolio items
foreach ($all_categories as $slug => $name) {
echo '<div class="portfolio-grid" id="' . esc_attr($slug) . '" style="display: none;">';
if (isset($categorized_items[$slug])) {
foreach ($categorized_items[$slug] as $item) {
echo '<div class="portfolio-item">';
echo '<a href="' . esc_url($item['permalink']) . '">';
if (!empty($item['thumbnail'])) {
echo '<img src="' . esc_url($item['thumbnail']) . '" alt="' . esc_attr($item['title']) . '">';
} else {
echo '<div class="no-thumbnail">No Image</div>';
}
echo '<h3>' . esc_html($item['title']) . '</h3>';
echo '</a>';
echo '</div>';
}
} else {
echo '<p>No items in this category.</p>';
}
echo '</div>';
}
}
Building a Custom Settings Page
WordPress plugin and theme settings often require complex array operations:
/**
* Process and validate plugin settings
*/
function save_plugin_settings() {
// Check nonce for security
if (!isset($_POST['my_plugin_nonce']) || !wp_verify_nonce($_POST['my_plugin_nonce'], 'save_plugin_settings')) {
wp_die('Security check failed');
}
// Get current settings
$current_settings = get_option('my_plugin_settings', []);
// Define setting types for validation
$setting_types = [
'api_key' => 'string',
'enable_feature_a' => 'boolean',
'enable_feature_b' => 'boolean',
'max_items' => 'integer',
'cache_time' => 'integer',
'email_addresses' => 'array',
'custom_css' => 'html'
];
// Process submitted settings
$new_settings = [];
foreach ($setting_types as $setting_name => $type) {
// Initialize with current value if it exists
$new_settings[$setting_name] = isset($current_settings[$setting_name]) ?
$current_settings[$setting_name] : null;
// Process if this setting was submitted
if (isset($_POST[$setting_name])) {
$submitted_value = $_POST[$setting_name];
switch ($type) {
case 'string':
$new_settings[$setting_name] = sanitize_text_field($submitted_value);
break;
case 'boolean':
$new_settings[$setting_name] = (bool)$submitted_value;
break;
case 'integer':
$new_settings[$setting_name] = intval($submitted_value);
break;
case 'array':
if (is_array($submitted_value)) {
$new_settings[$setting_name] = array_map('sanitize_text_field', $submitted_value);
} elseif (is_string($submitted_value)) {
// Handle comma-separated values
$values = explode(',', $submitted_value);
$new_settings[$setting_name] = array_map('trim', $values);
}
break;
case 'html':
$new_settings[$setting_name] = wp_kses_post($submitted_value);
break;
}
}
}
// Save updated settings
update_option('my_plugin_settings', $new_settings);
// Redirect back to settings page with success message
wp_redirect(add_query_arg('settings-updated', '1', admin_url('admin.php?page=my-plugin-settings')));
exit;
}
/**
* Display plugin settings form
*/
function display_plugin_settings_page() {
// Get current settings
$settings = get_option('my_plugin_settings', []);
// Define setting fields
$setting_fields = [
'general' => [
'title' => 'General Settings',
'fields' => [
'api_key' => [
'label' => 'API Key',
'type' => 'text',
'description' => 'Enter your API key from your account dashboard.'
],
'max_items' => [
'label' => 'Maximum Items',
'type' => 'number',
'min' => 1,
'max' => 100,
'description' => 'Maximum number of items to display.'
],
'cache_time' => [
'label' => 'Cache Time (seconds)',
'type' => 'number',
'min' => 0,
'description' => 'How long to cache results. Set to 0 to disable caching.'
]
]
],
'features' => [
'title' => 'Feature Settings',
'fields' => [
'enable_feature_a' => [
'label' => 'Enable Feature A',
'type' => 'checkbox',
'description' => 'Activate the premium Feature A functionality.'
],
'enable_feature_b' => [
'label' => 'Enable Feature B',
'type' => 'checkbox',
'description' => 'Activate the premium Feature B functionality.'
]
]
],
'advanced' => [
'title' => 'Advanced Settings',
'fields' => [
'email_addresses' => [
'label' => 'Notification Emails',
'type' => 'textarea',
'description' => 'Enter email addresses, one per line.'
],
'custom_css' => [
'label' => 'Custom CSS',
'type' => 'textarea',
'class' => 'code',
'description' => 'Add custom CSS to override default styles.'
]
]
]
];
// Display settings form
echo '<div class="wrap">';
echo '<h1>My Plugin Settings</h1>';
// Show settings updated message
if (isset($_GET['settings-updated']) && $_GET['settings-updated'] == '1') {
echo '<div class="notice notice-success is-dismissible">';
echo '<p>Settings updated successfully.</p>';
echo '</div>';
}
echo '<form method="post" action="' . admin_url('admin-post.php') . '">';
echo '<input type="hidden" name="action" value="save_plugin_settings">';
wp_nonce_field('save_plugin_settings', 'my_plugin_nonce');
// Loop through setting sections
foreach ($setting_fields as $section_id => $section) {
echo '<h2>' . esc_html($section['title']) . '</h2>';
echo '<table class="form-table">';
// Loop through fields in this section
foreach ($section['fields'] as $field_id => $field) {
echo '<tr>';
echo '<th scope="row"><label for="' . esc_attr($field_id) . '">' . esc_html($field['label']) . '</label></th>';
echo '<td>';
// Get current value with default
$current_value = isset($settings[$field_id]) ? $settings[$field_id] : '';
// Render field based on type
switch ($field['type']) {
case 'text':
echo '<input type="text" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="' . esc_attr($current_value) . '" class="regular-text">';
break;
case 'number':
$min = isset($field['min']) ? ' min="' . esc_attr($field['min']) . '"' : '';
$max = isset($field['max']) ? ' max="' . esc_attr($field['max']) . '"' : '';
echo '<input type="number" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="' . esc_attr($current_value) . '"' . $min . $max . ' class="small-text">';
break;
case 'checkbox':
$checked = $current_value ? ' checked' : '';
echo '<input type="checkbox" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="1"' . $checked . '>';
break;
case 'textarea':
$class = isset($field['class']) ? esc_attr($field['class']) : '';
if (is_array($current_value)) {
$current_value = implode("
", $current_value);
}
echo '<textarea id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" rows="5" class="large-text ' . $class . '">' . esc_textarea($current_value) . '</textarea>';
break;
}
// Show field description if available
if (isset($field['description'])) {
echo '<p class="description">' . esc_html($field['description']) . '</p>';
}
echo '</td>';
echo '</tr>';
}
echo '</table>';
}
echo '<p class="submit">';
echo '<input type="submit" name="submit" id="submit" class="button button-primary" value="Save Changes">';
echo '</p>';
echo '</form>';
echo '</div>';
}
Batch Processing WooCommerce Products
Using generators for memory-efficient processing of large product catalogs:
/**
* Memory-efficient batch processing of WooCommerce products
* using generators and array iteration
*/
function process_products_in_batches() {
// Get total number of products
$total_products = wp_count_posts('product')->publish;
// Set batch size
$batch_size = 20;
// Create a product generator
$products = product_batch_generator($batch_size);
// Process settings
$update_prices = isset($_POST['update_prices']) && $_POST['update_prices'] === '1';
$price_adjustment = isset($_POST['price_adjustment']) ? floatval($_POST['price_adjustment']) : 0;
$update_stock = isset($_POST['update_stock']) && $_POST['update_stock'] === '1';
// Initialize counters
$processed = 0;
$updated = 0;
// Start timer
$start_time = microtime(true);
// Process products in batches
foreach ($products as $batch) {
foreach ($batch as $product) {
$processed++;
$product_updated = false;
// Update product price if requested
if ($update_prices && $price_adjustment != 0) {
$regular_price = get_post_meta($product->ID, '_regular_price', true);
if (!empty($regular_price)) {
$new_price = round(floatval($regular_price) * (1 + ($price_adjustment / 100)), 2);
update_post_meta($product->ID, '_regular_price', $new_price);
update_post_meta($product->ID, '_price', $new_price);
$product_updated = true;
}
}
// Update product stock if requested
if ($update_stock) {
$product_obj = wc_get_product($product->ID);
if ($product_obj && $product_obj->managing_stock()) {
// Apply stock update logic here
// This is just a placeholder example
$current_stock = $product_obj->get_stock_quantity();
// Sync with external inventory system or apply business rules
// For example, ensure minimum stock level
$min_stock = 5;
if ($current_stock < $min_stock) {
wc_update_product_stock($product->ID, $min_stock);
$product_updated = true;
}
}
}
if ($product_updated) {
$updated++;
wc_delete_product_transients($product->ID);
}
// Show progress every 50 products
if ($processed % 50 === 0 || $processed === $total_products) {
$percentage = round(($processed / $total_products) * 100);
echo "Processed $processed of $total_products products ($percentage%)...<br>";
// Flush output buffer to show progress
ob_flush();
flush();
}
}
}
$time_taken = microtime(true) - $start_time;
echo "<h3>Processing Complete</h3>";
echo "<p>Processed $processed products in " . round($time_taken, 2) . " seconds.</p>";
echo "<p>Updated $updated products.</p>";
}
/**
* Generator function to yield batches of products
*/
function product_batch_generator($batch_size = 20) {
$offset = 0;
do {
$args = [
'post_type' => 'product',
'posts_per_page' => $batch_size,
'offset' => $offset,
'post_status' => 'publish',
'fields' => 'all'
];
$products = get_posts($args);
if (!empty($products)) {
yield $products;
$offset += $batch_size;
}
} while (!empty($products));
}
Homework Assignment
Put your array iteration skills to the test with this WordPress-themed assignment:
WordPress Menu Transformer
Create a function that transforms a WordPress menu structure into a customized format for different display purposes. Your function should:
- Accept a WordPress menu location as input
- Retrieve the menu items for that location
- Process the hierarchical menu structure, handling parent-child relationships
- Generate multiple output formats based on a parameter (e.g., "dropdown", "mobile", "sitemap")
- Apply proper WordPress security and sanitization functions
Your solution should use at least 3 different PHP array iteration techniques we covered in this lesson.
/**
* WordPress Menu Transformer
*
* @param string $menu_location Menu location registered with register_nav_menus()
* @param string $format Output format: 'dropdown', 'mobile', or 'sitemap'
* @param array $args Optional. Additional arguments for customization
* @return string Formatted menu HTML
*/
function transform_wp_menu($menu_location, $format = 'dropdown', $args = []) {
// 1. Get the menu items for the specified location
$locations = get_nav_menu_locations();
if (!isset($locations[$menu_location])) {
return '<p>Menu not found for location: ' . esc_html($menu_location) . '</p>';
}
$menu_id = $locations[$menu_location];
$menu_items = wp_get_nav_menu_items($menu_id);
if (empty($menu_items)) {
return '<p>No menu items found.</p>';
}
// 2. Process the menu items into a hierarchical structure
// Your code here: Organize menu items into a parent-child hierarchy
// 3. Generate the appropriate output format
switch ($format) {
case 'dropdown':
// Your code here: Create a dropdown/select menu
break;
case 'mobile':
// Your code here: Create a mobile-friendly accordion menu
break;
case 'sitemap':
// Your code here: Create a sitemap-style nested list
break;
default:
return '<p>Invalid format specified.</p>';
}
// 4. Return the formatted menu
return $output;
}
// Example usage in a template:
// echo transform_wp_menu('primary-menu', 'dropdown');
// echo transform_wp_menu('footer-menu', 'sitemap');
Further Reading and Resources
- PHP Manual: Array Functions - Official documentation
- WordPress Developer Reference - Working with arrays in WordPress
- PHP: The Right Way - Modern PHP best practices
- Advanced WordPress Development Techniques - Advanced array handling in WordPress