houdunwang/arr
A lightweight PHP array helper library from houdunwang, offering convenient utilities for manipulating, converting, filtering, and querying arrays. Useful for Laravel and standalone PHP projects that need cleaner array handling without extra dependencies.
Install
composer require houdunwang/arr
There is no Laravel service provider or auto-discovered facade — this is a plain static utility class. Import it directly:
use Houdunwang\Arr\Arr;
Where to look first
src/Arr.php (or src/ArrFacade.php in some releases) — the entire surface area is a handful of public static methods. Skim the method list once; that is the documentation.composer.json — confirm the PHP constraint before installing on an older runtime. Older tags target PHP 5.6/7.x._child, children, or similar) differ between versions. Read the actual tree()/cat() signature in the file you installed before writing code against it.First use case: flat category table → nested tree
This is what the package exists for. The typical starting point is an adjacency-list table (id, pid/parent_id, name) that you've already loaded:
$rows = Category::orderBy('sort')->get()->toArray();
$tree = Arr::tree($rows, 0, 'id', 'pid', 'name');
// -> nested array, each node carrying a children key
Once you can produce that, children(), parent(), and the level-flattening helpers all fall out of the same data shape, so building the tree correctly is 80% of the work.
tree() is pure array work and does an O(n) pass, but you still don't want to run it on every request against a full table.
$tree = Cache::rememberForever('cat.tree', function () {
return Arr::tree(Category::orderBy('sort')->get()->toArray(), 0, 'id', 'pid', 'name');
});
Invalidate on any write to the category table — a model observer with Cache::forget('cat.tree') on saved/deleted is enough.
<select> dropdownsTrees are for rendering; dropdowns need an indented flat list. Combine tree() with your recursive flatten, or use the package's level helper if your version has one (it usually stamps a level / _level field onto each node):
$flat = [];
$walk = function ($nodes, $depth) use (&$walk, &$flat) {
foreach ($nodes as $n) {
$flat[] = ['id' => $n['id'], 'label' => str_repeat(' ', $depth) . $n['name']];
if (!empty($n['_child'])) {
$walk($n['_child'], $depth + 1);
}
}
};
$walk($tree, 0);
Key detail: the children key name is version-specific. Dump one node with dd($tree[0]) on day one and hardcode the right key in a single helper rather than scattering the literal across views.
Use the parent-lookup helper to walk up from a node id, or build it yourself from a keyed map — the map approach is often clearer and lets you skip a DB hit per level:
$byId = Category::pluck('name', 'id'); // id => name
$chain = [];
for ($id = $current; $id && isset($byId[$id]); $id = $parents[$id] ?? 0) {
array_unshift($chain, $byId[$id]);
}
If you use Arr::parent(), it typically returns an array of ancestor rows, ordered root-first or leaf-first depending on version — assert the order in a test before wiring it into a view.
The other headline feature. Given per-attribute option lists, produce every combination:
$specs = [
['红色', '蓝色'],
['S', 'M', 'L'],
['棉', '涤纶'],
];
$combos = Arr::cartesian($specs);
// 12 combinations; each is an array of one value per attribute
Pattern that works well: keep the Cartesian call as the only thing this package does in your SKU service, and map its output to your own DTO immediately. Then the storage layer (one product_skus row per combination, keyed by a hash of the option ids) never depends on the package's return shape.
foreach ($combos as $combo) {
Sku::firstOrCreate(
['product_id' => $product->id, 'signature' => implode('|', $combo)],
['stock' => 0]
);
}
firstOrCreate keyed on a signature means editing a product's spec list is idempotent — existing SKUs survive, new ones appear, and you soft-delete orphans rather than truncating the table and losing stock counts.
The package works on plain arrays. Always ->toArray() an Eloquent collection or ->get()->toArray() a builder result first; passing a Collection or a Model will either fatal or silently produce garbage. Likewise, results come back as arrays — if you need models again, re-query by the ids you got, don't try to reconstruct models.
TreeBuilder / SkuGenerator class of your own and call the package from inside — then mock your class.tree($data, $parentId, $idField, $pidField, $nameField) is the common shape, but forks and tags shuffle the middle parameters. Read the installed source, not the README, not a blog post.'parent_id' vs 'pid' vs 'parentId' — no normalization, no type coercion. A string '0' parent id from a MySQL driver vs an int 0 from a seeder will silently drop rows out of the tree. Cast ids to int before treeing, or at least verify the root level populates.0. If your schema uses NULL for roots, either COALESCE it in SQL or normalize in PHP first.cartesian([]) and cartesian([[]]) are not the same. Test both if specs are optional.tree() on 50k categories is fine occasionally, terrible per-request. Cache aggressively; the cache key is the whole table version.$rows afterward.preg/type errors without English context. var_dump at the call site beats searching issues.kalnoy/nestedset (materialized-path trees, writes stay fast) and staudenmeir/laravel-adjacency-list (recursive CTE queries, no full-table load). Reach for houdunwang/arr when you specifically want the Cartesian-product helper or you're already in the Houdunwang ecosystem.composer update can remove or rewrite the repo. Fork it into your own namespace, or pin an exact version, for anything load-bearing.How can I help you explore Laravel packages today?