Lua predates PHP by 2 years, and I'm sure this combination of arrays and hash-maps predates both languages.
Either way, I'm not sold on it. Arrays and hash-maps are fundamentally different, not only for optimization's sake[1] but even in how people use them.
[1]: Recent versions of Lua now try to detect whether a table is an array, and apply optimizations when all its keys are ordered numbers without holes.
Lua doesn't try to detect whether a table is an array. The way it works is that a table is internally composed of two data structures, a hash part and an array part. Normally, integer keys go to the array part and everything else to the hash part. However, integer keys of a certain size will overflow into the hash part so that, e.g., storing a single integer key of 2^32 doesn't allocate an enormous empty array part.
In this way Lua already optimizes the array use case naturally. It never tries to infer whether a table is supposed to have array semantics or hash semantics. You can use a table as a hash, as an array, or (commonly) as both.
The cost of this simplicity and concision is born by the semantics of the length operator (#). The default __len metamethod on a table does a binary search looking for the first missing positive integer key, the boundary that marks the end of a logical array. The binary search will work even if your integer keys have spilled over into the hash part, though it works much faster if it doesn't have to inspect the hash part.
This why in Lua your arrays can't have holes (non-sequential positive integer keys), at least not if you want #t to behave as expected. Lua has no way of knowing the size of your array otherwise, at least not for plain tables lacking user-defined metamethods.
That said, there's a convention that uses the string key "n" to record the intended length. For example, table.pack() assigns the argument list to a table and sets "n" to the number of arguments. It does this because a nil argument value would create a hole. Also, since Lua 5.3 you can overload the __len metamethod, which could simply return t.n. Similarly, you can overload the __index and __newindex metamethods so that insertions update t.n or some other marker.
FWIW, I've tried hard not to express any value judgments in the above description. I've also deliberately abstained from discussing array-related language proposals.
I would argue that the length semantics are mostly unrelated to the hybrid array/hash nature. You could have the same problems on an array-only data structure, and you can invent semantics that avoid them without significantly changing the data structure.
It's amazing how many of the languages that feel recent today actually came out in the 90s: Python, JavaScript, Lua, Haskell, Java from the top of my head. Python is only five years younger (1990) than C++ (1985).
I guess it shows how much time you need to invest until a language is mature enough to enter common use, unless it's backed by a large entity like Microsoft (C#), Google (Go) or Mozilla (Rust).
its really an amazing object type, especially when you are given a great api to manipulate it. Which PHP does have.