A wide use of the basic language structures is often the sign of a missing concept in the domain model. Passing around arrays and Iterators may be harmful if the implicit objects they represent do not fit with the operations available on them. We may have collections where elements can be removed, or repeated, in spite of business rules; queues and stacks that allow random access to all their elements, and so on. This contradicts the principle of defining and referring to the smallest possible interface.
Sooner or later someone will start calling a method that should not be here, and that by then could never be removed by substituting the object. Actually in php you can call methods even if they are not defined on the interface you are referring to (it's a dynamic language), but if you write unit tests passing in mocks and stubs, they will fail and tell you that the code is smelling.
Before looking for a remedy to these design issues, let's just analyze a little Spl classes and interfaces to put this discussion in context.
- the Traversable interface and its Iterator extension would be perfect if php had generics capabilities built into the language: Iterator is a segregated interface that works well because it can be passed directly to foreach() constructs. The only issue is that Iterators that returns object of different classes can be mixed without type safety, so you may find code that expects a string Iterator and is passed a object Iterator.
- Countable is a very good interface, with only one method. It's really hard to implement this interface with more than one semantic meaning.
- SplQueue, SplStack, SplObjectStorage are only fast data structures, that have nothing to do with object-oriented design. So they're fine and perform well, but do not refer to them more than two or three times in your codebase (essentially wrapping them in explicit domain objects.) Really, a Queue or Stack that implements Iterator is a recipe for a disaster.
- SplObserver and SplSubject are really... pointless. Patterns are solutions that are implemented with many variations every time they are taken into consideration. So it makes no sense to specify standard interfaces for patterns, and I see many problems in these ones: detach() may remain unused since php objects are short-lived. Also attach() may be implemented differently if there is only one observer, or a specific set of them. And notify() may be a private or protected method.
For a full analysis of the underlying design problems of Spl, see the related SOLID principles post.
As you may know, composition (and its specialization, wrapping) is usually considered the greatest friend of an object-oriented developer.
The basic Spl classes have too much methods and behavior. This is fine for them because they try to cover all the feasible use cases, but it's dangerous for Client classes because they may rely on operations that should not be available to them, becoming coupled to a large interface. A fundamental principle of type safety is that object should be interchangeable only if they are really interchangeable, not because they happen to have a similar structure.
This means that I would never pass an SplQueue in my class, because it has so many methods (22 public methods) that, for example, I cannot mock it consistently in testing. I would wrap SplQueue in a class that defines an (at least implicit) small interface, and that I can pass around without fear because only my explicitly defined methods can be called.
Long story short, try to wrap Spl data structures in your classes, that define the interfaces that you want, and never pass them around as-is. As all languages structures, they are catch-all objects: your code will suffer from a spreaded, large set of calls to their public methods, becoming pinned to them. They are a great tool to get something done fast, but hiding their existence from the rest of the codebase is fundamental.