Why array_filter removes zero, and how to keep valid stock counts
You filter an imported list before saving it, then discover that out-of-stock products disappeared. The default array_filter behavior removes values considered empty, including zero. That can be the opposite of the import's requirements.
Reproduce the missing zero
<?php
$counts = [0, 4, null, 9];
$default = array_filter($counts);
$present = array_filter($counts, fn ($value) => $value !== null);
assert(array_values($default) === [4, 9]);
assert(array_values($present) === [0, 4, 9]);
echo json_encode(array_values($present), JSON_THROW_ON_ERROR), PHP_EOL;
Expected output: [0,4,9]. The callback removes only null. It does not establish that every remaining value is a valid non-negative integer.
Preserve the intended shape
array_filter preserves keys. If this is a JSON list, use array_values after filtering so the exported keys remain sequential. If those keys are meaningful product identifiers, preserve them instead. These two outputs have different contracts even when the displayed values look similar.
A feed mapping "AX-204" to zero should remain a mapping. A list of stock-count samples should remain a list. Decide which structure the downstream reader expects before reindexing it.
Reject invalid values separately
After removing genuinely absent values, validate the rest. A callback that merely excludes null still accepts false, arrays, and strings. Avoid combining many silent corrections into one filter; a malformed product should usually be reported instead of disappearing without explanation.
Test zero, a positive integer, null, an empty string, and false. Then inspect the encoded JSON shape as well as the element count. A test that only checks "three values remain" can miss that the client now receives an object instead of a list.
The array_filter manual documents empty-value removal and key preservation. The import contract determines which of those defaults you actually want.