Kotchasan Framework Documentation

Kotchasan Framework Documentation

Kotchasan\ArrayTool

EN 03 Feb 2026 11:46

Kotchasan\ArrayTool

Overview

ArrayTool is a utility class providing efficient tools for manipulating arrays and objects. This class offers functions that are NOT available in native PHP or provide significant convenience over native functions.

When to use:

  • Extract data from specific columns in array of arrays/objects
  • Search or filter data based on complex conditions
  • Insert data at specific positions in arrays
  • Handle arrays and objects uniformly

Key Features:

  • Works with both arrays and objects
  • Helper functions that add value beyond native PHP
  • Case-insensitive search and filter support
  • Handles nested arrays/objects

[!NOTE]
This documentation reflects the current ArrayTool API for PHP 7.4+. The class focuses on helpers that provide value beyond native PHP functions.

API Reference

columns()

Extract values from a specific column from array or object (similar to array_column() but works with both arrays and objects)

Signature:

public static function columns(iterable $source, string $column_key, ?string $index_key = null): array

Parameters:

  • $source (iterable) - Array, object, or array of objects to extract from
  • $column_key (string) - Name of the column to retrieve
  • $index_key (string|null) - Column to use as index (null = numeric index)

Returns: Array of extracted values

Usage Examples:

use Kotchasan\ArrayTool;

$users = [
    ['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com'],
    ['id' => 2, 'name' => 'Bob', 'email' => 'bob@example.com'],
    ['id' => 3, 'name' => 'Charlie', 'email' => 'charlie@example.com']
];

// Extract all names (numeric index)
$names = ArrayTool::columns($users, 'name');
// Result: ['Alice', 'Bob', 'Charlie']

// Extract emails with id as index
$emailsById = ArrayTool::columns($users, 'email', 'id');
// Result: [1 => 'alice@example.com', 2 => 'bob@example.com', 3 => 'charlie@example.com']

Works with objects:

$users = [
    (object)['id' => 1, 'name' => 'Alice'],
    (object)['id' => 2, 'name' => 'Bob']
];

$names = ArrayTool::columns($users, 'name', 'id');
// Result: [1 => 'Alice', 2 => 'Bob']

extract()

Extract keys and values from nested array recursively

Signature:

public static function extract(array $array, array &$keys, array &$values): void

Parameters:

  • $array (array) - Source array (can be nested)
  • $keys (array) - Reference array to store extracted keys
  • $values (array) - Reference array to store extracted values

Returns: void (modifies $keys and $values by reference)

Usage Examples:

$data = [
    'name' => 'John',
    'address' => [
        'city' => 'Bangkok',
        'country' => 'Thailand'
    ],
    'age' => 30
];

$keys = [];
$values = [];
ArrayTool::extract($data, $keys, $values);

// $keys = ['name', 'city', 'country', 'age']
// $values = ['John', 'Bangkok', 'Thailand', 30]

filter()

Filter array elements containing search string (case-insensitive)

Signature:

public static function filter(array $array, string $search): array

Parameters:

  • $array (array) - Array to filter
  • $search (string) - Search string

Returns: Filtered array with matching items (original keys preserved)

Usage Examples:

$items = [
    'apple',
    'banana',
    'orange',
    'grape',
    'pineapple'
];

$filtered = ArrayTool::filter($items, 'app');
// Result: [0 => 'apple', 4 => 'pineapple'] (contains 'app')

// Empty string returns original array
$all = ArrayTool::filter($items, '');
// Result: ['apple', 'banana', 'orange', 'grape', 'pineapple']

Works with nested arrays:

$products = [
    ['name' => 'Laptop', 'brand' => 'Dell'],
    ['name' => 'Mouse', 'brand' => 'Logitech'],
    ['name' => 'Keyboard', 'brand' => 'Dell']
];

// Search for Dell products
$dellProducts = ArrayTool::filter($products, 'Dell');
// Result: [0 => ['name' => 'Laptop', 'brand' => 'Dell'],
//          2 => ['name' => 'Keyboard', 'brand' => 'Dell']]

getFirstKey()

Get the first key of an array or object

Signature:

public static function getFirstKey($source)

Parameters:

  • $source (array|object) - Array or object

Returns: string|int|null - First key, or null if no keys found

Usage Examples:

$array = ['name' => 'John', 'age' => 30, 'city' => 'Bangkok'];
$firstKey = ArrayTool::getFirstKey($array);
// Result: 'name'

$object = (object)['id' => 1, 'title' => 'Test'];
$firstKey = ArrayTool::getFirstKey($object);
// Result: 'id'

$empty = [];
$firstKey = ArrayTool::getFirstKey($empty);
// Result: null

[!NOTE]
For arrays in PHP 7.3+, you can use array_key_first() instead. This method is kept for object support.

insertAfter()

Insert data into an array after the specified key

Signature:

public static function insertAfter(array $source, $find, $key, $value): array

Parameters:

  • $source (array) - Array to insert into
  • $find (string|int) - Key to search for
  • $key (string|int) - Key for the new data
  • $value (mixed) - Data to insert

Returns: Modified array (if key not found, inserts at the end)

Usage Examples:

$menu = [
    'home' => 'Home',
    'about' => 'About',
    'contact' => 'Contact'
];

// Insert after 'home'
$menu = ArrayTool::insertAfter($menu, 'home', 'services', 'Services');
// Result: [
//     'home' => 'Home',
//     'services' => 'Services',
//     'about' => 'About',
//     'contact' => 'Contact'
// ]

// If key not found, inserts at end
$menu = ArrayTool::insertAfter($menu, 'notfound', 'blog', 'Blog');
// 'blog' will be added at the end

insertBefore()

Insert data into an array before the specified key

Signature:

public static function insertBefore(array $source, $find, $key, $value): array

Parameters:

  • $source (array) - Array to insert into
  • $find (string|int) - Key to search for
  • $key (string|int) - Key for the new data
  • $value (mixed) - Data to insert

Returns: Modified array (if key not found, inserts at the end)

Usage Examples:

$menu = [
    'home' => 'Home',
    'contact' => 'Contact'
];

// Insert before 'contact'
$menu = ArrayTool::insertBefore($menu, 'contact', 'about', 'About');
// Result: [
//     'home' => 'Home',
//     'about' => 'About',
//     'contact' => 'Contact'
// ]

replace()

Replace/merge values from $replace into $source (works with arrays and objects)

Signature:

public static function replace($source, $replace)

Parameters:

  • $source (array|object) - Base array or object
  • $replace (array|object) - Values to merge/replace

Returns: array|object - Modified source

Usage Examples:

// With arrays
$data = ['a' => 1, 'b' => 2, 'c' => 3];
$replacements = ['b' => 5, 'd' => 4];

$result = ArrayTool::replace($data, $replacements);
// Result: ['a' => 1, 'b' => 5, 'c' => 3, 'd' => 4]

// With objects
$config = (object)['host' => 'localhost', 'port' => 3306];
$updates = ['port' => 3307, 'database' => 'test'];

$config = ArrayTool::replace($config, $updates);
// Result: object with host='localhost', port=3307, database='test'

Search array of arrays/objects for items with matching key-value pair

Signature:

public static function search(array $array, string $key, $search): array

Parameters:

  • $array (array) - Array of arrays or objects to search
  • $key (string) - Key/property to match
  • $search (mixed) - Value to match (strict comparison)

Returns: Array of matching items with original keys preserved

Usage Examples:

$users = [
    ['id' => 1, 'name' => 'Alice', 'status' => 'active'],
    ['id' => 2, 'name' => 'Bob', 'status' => 'inactive'],
    ['id' => 3, 'name' => 'Charlie', 'status' => 'active'],
    ['id' => 4, 'name' => 'Diana', 'status' => 'pending']
];

// Find active users
$activeUsers = ArrayTool::search($users, 'status', 'active');
// Result: [
//     0 => ['id' => 1, 'name' => 'Alice', 'status' => 'active'],
//     2 => ['id' => 3, 'name' => 'Charlie', 'status' => 'active']
// ]

// Search with boolean
$items = [
    ['name' => 'Item 1', 'featured' => true],
    ['name' => 'Item 2', 'featured' => false],
    ['name' => 'Item 3', 'featured' => true]
];

$featured = ArrayTool::search($items, 'featured', true);
// Result: [0 => ..., 2 => ...]

Works with objects:

$users = [
    (object)['id' => 1, 'role' => 'admin'],
    (object)['id' => 2, 'role' => 'user']
];

$admins = ArrayTool::search($users, 'role', 'admin');
// Result: [0 => object(['id' => 1, 'role' => 'admin'])]

shift()

Remove elements from an array before the specified key (keeps elements from the specified key onwards)

Signature:

public static function shift(array $source, $key): array

Parameters:

  • $source (array) - The source array
  • $key (string|int) - The key to start keeping elements from

Returns: Array of elements from the specified key onwards

Usage Examples:

$data = [
    'step1' => 'Start',
    'step2' => 'Process',
    'step3' => 'Complete',
    'step4' => 'Close'
];

$remaining = ArrayTool::shift($data, 'step2');
// Result: [
//     'step2' => 'Process',
//     'step3' => 'Complete',
//     'step4' => 'Close'
// ]

// With numeric array
$numbers = [10, 20, 30, 40, 50];
$fromIndex2 = ArrayTool::shift($numbers, 2);
// Result: [2 => 30, 3 => 40, 4 => 50]

sort()

Sort array of associative arrays/objects by a specified key

Signature:

public static function sort(array $array, string $sort_key = 'id', bool $descending = false): array

Parameters:

  • $array (array) - Array to sort
  • $sort_key (string) - Key to sort by (default: 'id')
  • $descending (bool) - Sort descending (default: false = ascending)

Returns: Sorted array (re-indexed numerically)

Usage Examples:

$products = [
    ['id' => 3, 'name' => 'Product C', 'price' => 100],
    ['id' => 1, 'name' => 'Product A', 'price' => 300],
    ['id' => 2, 'name' => 'Product B', 'price' => 200]
];

// Sort by id (ascending)
$sortedById = ArrayTool::sort($products, 'id');
// Result: [
//     0 => ['id' => 1, 'name' => 'Product A', 'price' => 300],
//     1 => ['id' => 2, 'name' => 'Product B', 'price' => 200],
//     2 => ['id' => 3, 'name' => 'Product C', 'price' => 100]
// ]

// Sort by price (descending)
$sortedByPrice = ArrayTool::sort($products, 'price', true);
// Result: [
//     0 => ['id' => 1, 'name' => 'Product A', 'price' => 300],
//     1 => ['id' => 2, 'name' => 'Product B', 'price' => 200],
//     2 => ['id' => 3, 'name' => 'Product C', 'price' => 100]
// ]

// Sort by name (alphabetically)
$sortedByName = ArrayTool::sort($products, 'name');
// Result: sorted alphabetically (case-insensitive)

[!NOTE]
Sorting uses case-insensitive comparison and the returned array will be re-indexed numerically (0, 1, 2...)

toString()

Convert nested array/object to string by joining values recursively

Signature:

public static function toString(string $glue, $source): string

Parameters:

  • $glue (string) - Separator between values
  • $source (mixed) - Array, object, or scalar value

Returns: string - Concatenated string

Usage Examples:

// Simple array
$data = ['apple', 'banana', 'orange'];
$result = ArrayTool::toString(', ', $data);
// Result: "apple, banana, orange"

// Nested array
$nested = [
    'user' => [
        'name' => 'John',
        'details' => ['age' => 30, 'city' => 'Bangkok']
    ]
];

$result = ArrayTool::toString(' | ', $nested);
// Result: "John | 30 | Bangkok"

// With scalar value
$value = ArrayTool::toString(',', 'test');
// Result: "test"

unserialize()

Unserialize a string and update the source array with the unserialized data (supports both JSON and PHP serialize)

Signature:

public static function unserialize($str, $source = [], $replace = true)

Parameters:

  • $str (string) - The serialized string
  • $source (array) - Array to update with unserialized data (optional, default: [])
  • $replace (bool) - Whether to replace existing values in the source array (optional, default: true)

Returns: array - The updated source array

Usage Examples:

// JSON string
$json = '{"name":"John","age":30,"city":"Bangkok"}';
$data = ArrayTool::unserialize($json);
// Result: ['name' => 'John', 'age' => 30, 'city' => 'Bangkok']

// PHP serialized string
$serialized = serialize(['id' => 1, 'status' => 'active']);
$data = ArrayTool::unserialize($serialized);
// Result: ['id' => 1, 'status' => 'active']

// Merge with existing array
$existing = ['host' => 'localhost', 'port' => 3306];
$updates = '{"port":3307,"database":"mydb"}';

$config = ArrayTool::unserialize($updates, $existing);
// Result: ['host' => 'localhost', 'port' => 3307, 'database' => 'mydb']

// Don't replace existing values (replace = false)
$existing = ['name' => 'Alice', 'age' => 25];
$updates = '{"name":"Bob","city":"Bangkok"}';

$data = ArrayTool::unserialize($updates, $existing, false);
// Result: ['name' => 'Alice', 'age' => 25, 'city' => 'Bangkok']
// (name not replaced because it already exists)

[!NOTE]
This function tries to decode as JSON first, if that fails it uses unserialize() instead.

inArrayAny()

Check if any value in the needle array exists in the haystack array

Signature:

public static function inArrayAny(array $needles, array $haystack, bool $strict = false): bool

Parameters:

  • $needles (array) - Array of values to search for
  • $haystack (array) - Array to search in
  • $strict (bool) - Perform strict type comparison (default: false)

Returns: bool - True if any value is found

Usage Examples:

$permissions = ['read', 'write', 'delete'];
$userPermissions = ['read', 'execute'];

// Check if user has any required permission
$hasAny = ArrayTool::inArrayAny($permissions, $userPermissions);
// Result: true (has 'read')

$adminPermissions = ['create', 'delete'];
$hasAny = ArrayTool::inArrayAny($adminPermissions, $userPermissions);
// Result: false (has none)

// Use strict comparison
$numbers = ['1', '2', '3'];
$check = [1, 4];

$found = ArrayTool::inArrayAny($check, $numbers, false);
// Result: true ('1' == 1 is true)

$found = ArrayTool::inArrayAny($check, $numbers, true);
// Result: false ('1' === 1 is false)

[!NOTE]
Unlike native in_array() which accepts a single value, this accepts an array of values and returns true if ANY of them is found.

getNextKey()

Retrieve the key of the element following a specified key in the array

Signature:

public static function getNextKey(array $array, $key)

Parameters:

  • $array (array) - The input array
  • $key (string|int) - The key to find the next key after

Returns: string|int|null - The next key, or null if not found or at end

Usage Examples:

$menu = [
    'home' => 'Home',
    'about' => 'About',
    'services' => 'Services',
    'contact' => 'Contact'
];

$nextKey = ArrayTool::getNextKey($menu, 'about');
// Result: 'services'

$nextKey = ArrayTool::getNextKey($menu, 'contact');
// Result: null (at the end of array)

$nextKey = ArrayTool::getNextKey($menu, 'notfound');
// Result: null (key doesn't exist)

// With numeric array
$numbers = [10, 20, 30, 40];
$nextKey = ArrayTool::getNextKey($numbers, 1);
// Result: 2

Real-World Examples

1. User Data Management

use Kotchasan\ArrayTool;

class UserManager
{
    private $users = [
        ['id' => 1, 'name' => 'Alice', 'role' => 'admin', 'active' => true],
        ['id' => 2, 'name' => 'Bob', 'role' => 'user', 'active' => true],
        ['id' => 3, 'name' => 'Charlie', 'role' => 'user', 'active' => false],
        ['id' => 4, 'name' => 'Diana', 'role' => 'moderator', 'active' => true]
    ];

    /**
     * Get active users
     */
    public function getActiveUsers()
    {
        return ArrayTool::search($this->users, 'active', true);
    }

    /**
     * Get all administrators
     */
    public function getAdmins()
    {
        return ArrayTool::search($this->users, 'role', 'admin');
    }

    /**
     * Get user names (indexed by ID)
     */
    public function getUserNamesById()
    {
        return ArrayTool::columns($this->users, 'name', 'id');
        // Result: [1 => 'Alice', 2 => 'Bob', 3 => 'Charlie', 4 => 'Diana']
    }

    /**
     * Search users by term
     */
    public function searchUsers($term)
    {
        return ArrayTool::filter($this->users, $term);
    }

    /**
     * Sort users by name
     */
    public function sortByName($descending = false)
    {
        return ArrayTool::sort($this->users, 'name', $descending);
    }
}

// Usage
$manager = new UserManager();

$active = $manager->getActiveUsers();
$admins = $manager->getAdmins();
$nameMap = $manager->getUserNamesById();
$searchResults = $manager->searchUsers('ali'); // case-insensitive search

2. Menu Building and Management

class MenuBuilder
{
    private $menu = [];

    public function __construct()
    {
        $this->menu = [
            'home' => ['title' => 'Home', 'url' => '/', 'icon' => 'home'],
            'contact' => ['title' => 'Contact', 'url' => '/contact', 'icon' => 'mail']
        ];
    }

    /**
     * Add menu after home
     */
    public function addMenuAfterHome($key, $title, $url, $icon)
    {
        $item = ['title' => $title, 'url' => $url, 'icon' => $icon];
        $this->menu = ArrayTool::insertAfter($this->menu, 'home', $key, $item);
        return $this;
    }

    /**
     * Add menu before contact
     */
    public function addMenuBeforeContact($key, $title, $url, $icon)
    {
        $item = ['title' => $title, 'url' => $url, 'icon' => $icon];
        $this->menu = ArrayTool::insertBefore($this->menu, 'contact', $key, $item);
        return $this;
    }

    /**
     * Update menus
     */
    public function updateMenu($updates)
    {
        foreach ($updates as $key => $data) {
            if (isset($this->menu[$key])) {
                $this->menu[$key] = ArrayTool::replace($this->menu[$key], $data);
            }
        }
        return $this;
    }

    /**
     * Get all menu titles
     */
    public function getMenuTitles()
    {
        return ArrayTool::columns($this->menu, 'title');
    }

    public function getMenu()
    {
        return $this->menu;
    }
}

// Usage
$builder = new MenuBuilder();

$builder
    ->addMenuAfterHome('about', 'About', '/about', 'info')
    ->addMenuBeforeContact('services', 'Services', '/services', 'briefcase')
    ->updateMenu([
        'home' => ['icon' => 'house'] // change icon
    ]);

$menu = $builder->getMenu();
// Result: [
//     'home' => ['title' => 'Home', 'url' => '/', 'icon' => 'house'],
//     'about' => ['title' => 'About', 'url' => '/about', 'icon' => 'info'],
//     'services' => ['title' => 'Services', 'url' => '/services', 'icon' => 'briefcase'],
//     'contact' => ['title' => 'Contact', 'url' => '/contact', 'icon' => 'mail']
// ]

3. API Data Processing

class ApiDataProcessor
{
    /**
     * Process API response
     */
    public function processUserResponse($apiResponse)
    {
        $users = $apiResponse['data']['users'] ?? [];

        // Extract needed data
        $emails = ArrayTool::columns($users, 'email');
        $userMap = ArrayTool::columns($users, 'name', 'id');

        // Filter verified users
        $verified = ArrayTool::search($users, 'verified', true);

        // Sort by creation date
        $sorted = ArrayTool::sort($verified, 'created_at', true);

        return [
            'emails' => $emails,
            'user_map' => $userMap,
            'verified_users' => $verified,
            'latest_users' => $sorted
        ];
    }

    /**
     * Merge data from multiple sources
     */
    public function mergeUserData($users, $profiles)
    {
        foreach ($users as &$user) {
            $userId = $user['id'];
            $profileData = ArrayTool::search($profiles, 'user_id', $userId);

            if (!empty($profileData)) {
                $user = ArrayTool::replace($user, $profileData[0]);
            }
        }

        return $users;
    }

    /**
     * Sanitize data (remove sensitive fields)
     */
    public function sanitize($data)
    {
        $sensitiveFields = ['password', 'token', 'secret_key'];

        foreach ($data as &$item) {
            $item = array_diff_key($item, array_flip($sensitiveFields));
        }

        return $data;
    }
}

4. Permission Management

class PermissionChecker
{
    /**
     * Check if user has any required permission
     */
    public function hasAnyPermission($userPermissions, $requiredPermissions)
    {
        return ArrayTool::inArrayAny($requiredPermissions, $userPermissions);
    }

    /**
     * Check if user has allowed role
     */
    public function hasRole($userRoles, $allowedRoles)
    {
        return ArrayTool::inArrayAny($allowedRoles, $userRoles);
    }
}

// Usage
$checker = new PermissionChecker();

$userPerms = ['post:read', 'post:create', 'comment:read'];
$requiredPerms = ['post:delete', 'post:update'];

$canEdit = $checker->hasAnyPermission($userPerms, $requiredPerms);
// Result: false (no required permissions)

$userRoles = ['editor', 'author'];
$adminRoles = ['admin', 'super-admin'];

$isAdmin = $checker->hasRole($userRoles, $adminRoles);
// Result: false

Best Practices

1. Validate Data Before Usage

// ✅ Good
public function safeColumnExtraction($data, $column)
{
    if (!is_array($data) || empty($data)) {
        return [];
    }

    // Check if column exists
    $firstItem = reset($data);
    if (!isset($firstItem[$column])) {
        throw new InvalidArgumentException("Column '{$column}' not found");
    }

    return ArrayTool::columns($data, $column);
}

// ❌ Bad - no validation
public function unsafeExtraction($data, $column)
{
    return ArrayTool::columns($data, $column); // might error
}

2. Use Native PHP Functions When Appropriate

// For safe array access
// ✅ Use null coalescing operator
$value = $config['key'] ?? 'default';

// For removing keys
// ✅ Use native functions
$result = array_diff_key($array, array_flip(['key1', 'key2']));

// For getting first key (PHP 7.3+)
// ✅ Use native function for arrays
$firstKey = array_key_first($array);

// But if you need to support objects, use ArrayTool::getFirstKey()
$firstKey = ArrayTool::getFirstKey($object);

3. Performance for Large Datasets

// For large datasets (> 1000 items)
// Consider using array_filter or foreach instead
public function efficientSearch($data, $key, $value)
{
    if (count($data) > 1000) {
        return array_filter($data, function($item) use ($key, $value) {
            return isset($item[$key]) && $item[$key] === $value;
        });
    }

    return ArrayTool::search($data, $key, $value);
}

4. Type Safety

// ✅ Specify types clearly
public function typeSafeReplace($source, array $replacements)
{
    if (!is_array($source) && !is_object($source)) {
        throw new InvalidArgumentException('Source must be array or object');
    }

    return ArrayTool::replace($source, $replacements);
}

Important Considerations

[!WARNING]
sort() re-indexes the array
The sort() function returns a numerically re-indexed array (0, 1, 2...) instead of preserving original keys

// Original keys will be lost
$data = [
    'user1' => ['name' => 'Bob'],
    'user2' => ['name' => 'Alice']
];

$sorted = ArrayTool::sort($data, 'name');
// Result: [
//     0 => ['name' => 'Alice'],
//     1 => ['name' => 'Bob']
// ]
// NOT ['user2' => ..., 'user1' => ...]

[!WARNING]
search() uses strict comparison
The search() function uses === for comparison, be aware of types

$users = [['id' => 1], ['id' => '1']];

ArrayTool::search($users, 'id', 1);   // only matches ['id' => 1]
ArrayTool::search($users, 'id', '1'); // only matches ['id' => '1']

[!CAUTION]
Memory usage with nested arrays
Using toString() with large nested arrays may consume significant memory