Based on discussions at #1308152: Add stream wrappers to access .json files in extensions with comment #1308152-167: Add stream wrappers to access .json files in extensions by @fietserwin, reproduced below. There is need to rework the streamwrapper API for D8 to achieve more consistency and optimizations of implementation.
Regarding the state of the API for our stream wrappers
Having reviewed this code now for the 3rd time in a short period, I mentioned I kept jumping from method to method, following call chains and going round in circles. To me, this is a sign that something structural is wrong with what we are doing here. Think of:
- Is the inheritance tree logical?
- What methods are there and what methods would I expect?
- where are the methods implemented and overridden and is this logical?
- Is method naming logical and consistent?So back to the tasks and responsibilities of a stream wrapper:
implement PHPStreamWrapperInterface so it is accessible via and reacts like standard PHP file functions. This is done by LocalStream and LocalReadonlyStream. All subclasses of these are only there to define different parts of the local file system.However, besides this main task, there are some Drupal specific functionality to be provided. Or, what public methods would I expect (besides the stream wrapper functions):
Stream wrapper as a file wrapper:
realpath(): I doubt that this will be used a lot (directly), but it doesn't hurt to have it public.
dirname(): This is even more doubtful, but it could be used to create another file in the same directory, though we could simply use dirname($wrapper->realpath()) for that.Stream wrapper as an externally accessible resource:
getExternalUrl(): this is of course where these stream wrappers shine and provide additional value above just being a stream wrapper.
As all these stream wrappers are expected to operate within the hierarchical local file system, the only difference is the starting point they define within that local file system and the url that corresponds to that starting point. Given this, what (protected) methods would I expect?Stream wrapper as a file wrapper:
getDirectoryPath(): the starting point within the local file system that this stream wrapper covers. I don't think this name is logical and would like to suggest to rename it to something like getStreamDirectory() or getBaseDirectory(), or getBasePath(). (The latter following a method in Symfony\Component\HttpFoundation\Request.) Note: there is a todo about the naming of this method, but the issue it refers to is long closed.
getTarget(): the relative path within the sub file system that this stream wrapper covers. I think that this name can also be better, but looking at file_uri_target(), I can understand the name. Perhaps something like getRelativePath() would be better given that for system streams the first part of the target is also part of what defines the base directory(, or getPathInfo() to follow Symfony\Component\HttpFoundation\Request again).Stream wrapper as an externally accessible resource:
and at the url level, getStreamUrl(): the (base) url that this stream wrapper covers.
getTargetUrl(): given that we are working within a hierarchical file system, this will be equal to getTarget(), except perhaps replacing \ with /. The same suggestion here as to the name of this method: getRelativeUrl() would be better.
and for the system stream wrappers, getOwner(): returns the first part after the scheme (module or theme name) used for:
getOwnerDirectory(): returns the directory where the owner (module, theme or profile) is located.
Comments
Comment #1
almaudoh commentedComment #3
jibranComment #13
guypaddock commented+1 The Stream Wrapper API is a mess.
Stream wrappers simultaneously behave as both open file resources (i.e. an open "stream") and the high-level interface about the protocol for that type of file resource (i.e. what should be a factory for opening a new stream). Because the same type of classes are being used for 2-3 different concerns, the naming of these objects is inconsistent. Core names most of these objects "streams" (
LocalStream,TemporaryStream,PrivateStream,PublicStream) while calling them "stream wrappers" while other areas of core and contrib both call and name them "stream wrappers" (HttpStreamWrapper,DummyStreamWrapper, etc).Callers have to keep track of what methods they can and cannot call on the stream wrapper depending upon the state of the wrapper, and the same wrapper can't be used for more than one file even though some methods on the wrapper expose information about the protocol and wrapper itself and can be called without the wrapper even having a stream open.
Comment #14
guypaddock commented