{"action":"created","issue":{"url":"https://api.github.com/repos/silverstripe/silverstripe-framework/issues/3792","labels_url":"https://api.github.com/repos/silverstripe/silverstripe-framework/issues/3792/labels{/name}","comments_url":"https://api.github.com/repos/silverstripe/silverstripe-framework/issues/3792/comments","events_url":"https://api.github.com/repos/silverstripe/silverstripe-framework/issues/3792/events","html_url":"https://github.com/silverstripe/silverstripe-framework/issues/3792","id":54283171,"number":3792,"title":"RFC-1 Asset abstraction","user":{"login":"mateusz","id":118653,"avatar_url":"https://avatars.githubusercontent.com/u/118653?v=3","gravatar_id":"","url":"https://api.github.com/users/mateusz","html_url":"https://github.com/mateusz","followers_url":"https://api.github.com/users/mateusz/followers","following_url":"https://api.github.com/users/mateusz/following{/other_user}","gists_url":"https://api.github.com/users/mateusz/gists{/gist_id}","starred_url":"https://api.github.com/users/mateusz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mateusz/subscriptions","organizations_url":"https://api.github.com/users/mateusz/orgs","repos_url":"https://api.github.com/users/mateusz/repos","events_url":"https://api.github.com/users/mateusz/events{/privacy}","received_events_url":"https://api.github.com/users/mateusz/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":5,"created_at":"2015-01-14T04:32:35Z","updated_at":"2015-01-14T19:24:00Z","closed_at":null,"body":"# RFC-1 Asset abstraction\r\n\r\n- Authors: @mateusz, @hafriedlander\r\n- Status: proposed\r\n- Version: 1.0\r\n\r\n## Motivation\r\n\r\nThe driver for this change is solving the following problems we keep encountering in practice:\r\n\r\n- changing root path for asset storage\r\n- support Amazon S3 as a backend\r\n- support backends for sites residing on multiple servers (clustered sites)\r\n- have files versioned\r\n- have files with workflow\r\n- be able to model private files that do not appear in the Files & Images section\r\n\r\nWe currently cannot accomplish this due to the monolithic approach to the file handling in the Framework. This capability is currently expressed as a set of specialised `DataObject`s: `File`, `Folder` and `Image` which implicitly tie the database representation to the local filesystem, preventing any flexibility.\r\n\r\n## Proposal\r\n\r\nTo solve these problems we propose to:\r\n\r\n1. Create a backend-independent way of referring to files in the Framework.\r\n1. \"Degrade\" files from entities in their own right (`DataObjects`) to model properties (`DBFields`).\r\n1. Hide all file operations behind an interface.\r\n\r\nSpecifically, we would do so by introducing a new concept of a generalised **file reference** - a set of values (i.e. a tuple) uniquely pointing to a file regardless of the storage backend in use.\r\n\r\nThen we would change the model representation of the files: instead of using the coarse-grained data objects such as `File` we would move to using the `$db` fields. For this a new `DBField` would be introduced, called **FileReferenceField**.\r\n\r\nThe `File` and `Folder` would retain their APIs as much as possible, but be retrofitted to use the `FileReferenceField`. They would now conceptually become an inseparable part of the Files & Images subsystem of the CMS.\r\n\r\n`UploadField` would be updated to be able to work directly with the `FileReferenceField`s, additonally to the existing has_one support.\r\n\r\nFinally, we would remove all direct filesystem operations from the Framework and place them behind the new **Asset Persistence Layer (APL)** interface. This RFC does not define how an APL should work under the hood. Neither does it define the architecture nor interfaces of the storage backends. APL is tied to data stored by that APL, and changing the APL will cause data loss without a migration script.\r\n\r\n![](http://i.imgur.com/TPv7TU0.jpg)\r\n\r\nWe would expect to be able to deliver this into the \"3.x\" branch, so we would need to work to the following constraints:\r\n\r\n- retain the backward-compatibility as much as possible, both from the API side and the filesystem side - at minimum retain it as an option.\r\n- retain the UI in its current shape.\r\n\r\n### File reference tuple\r\n\r\nFile reference is a set of values (i.e. a tuple) describing a file uniquely, and independently of a storage backend. It must be generalised to allow different kinds of backends. We envisage the following values to be included in this tuple:\r\n\r\n||Hash|Variant|Filename|ParentObject|\r\n|---|---|---|---|---|\r\n|*Example*|\"d0be2dc...\"|\"Resized640x480\"|\"logo.jpg\"|A `File` reference|\r\n|*Description*|sha1 of the base content|Variant of the base content. \"null\" for the base file|The name of the file|Reference to the containing object|\r\n|*Rationale*|For managing content conflicts|For supporting derived files|For reconstructing the URL given a file reference and for managing naming conflicts|For reconstructing the URL and the directory structure|\r\n\r\nThe available values are not extensible. Backends may use any or all values of the tuple.\r\n\r\nThe tuple itself is not represented in the codebase directly as a class or an object, but manifests in the `FileReferenceField` as stored fields and in the discrete APL operation parameters and return values.\r\n\r\n### FileReferenceField\r\n\r\n`FileReferenceField` is a mechanism for expressing the file reference in the code and for storing it in the database. It would most likely be implemented as a packed or composite `DBField`, stored in table columns. The ParentObject does not need to be stored because it can be derived from the containing data object.\r\n\r\nAdditionally, the `FileReferenceField` acts as a convenient proxy object to the APL interface, since it has the ability to introspect all the necessary values. By necessity it would follow the APL interface as laid out in \"Appendix A: APL interface\".\r\n\r\n```php\r\n$file = File::get()->byID(1);\r\n\r\n// The following becomes deprecated, we no longer are allowed direct access:\r\n// file_get_contents($file->Filename)\r\n\r\n// Instead we need to operate through the FileReferenceField:\r\n$fileReference = $file->obj('FileReference');\r\n$fileReference->getAsString();\r\n```\r\n\r\nThis access method is preferred as a less verbose one, but APL could also be used directly if needed.\r\n\r\n### Asset Persistence Layer (APL)\r\n\r\nAll existing direct file operations in the Framework would be rewritten to use the APL, either indirectly through the `FileReferenceField`, or directly. A concrete APL would be configured once per site using a dependency injection.\r\n\r\nAPL requires all File Reference tuple values to be passed to all its operations.\r\n\r\nExpanding on the previous example:\r\n\r\n```php\r\n// Under the hood of the FileReferenceField\r\nfunction getAsString() {\r\n\t// Obtain the APL reference, most likely via DI.\r\n\t$apl = Injector::inst()->get('AssetPersistenceLayer');\r\n\t// Obtain the parent object reference.\r\n\t$parentObj = DataList::create($this->getParentClass())->byID($this->getParent());\r\n\t\r\n\t// Pass the tuple values as discrete parameters.\r\n\treturn $apl->getAsString($this->Hash, $this->Variant, $this->Filename, $parentObj);\r\n}\r\n```\r\n\r\nSee the \"Appendix A: APL interface\" for the proposed interface to the APL.\r\n\r\nUsing parameters is at the APL discretion - it would be perfectly legal for an implementation to ignore some of the values as long as it can ensure uniqueness.\r\n\r\nAdditionally, APL setters may modify the tuple values passed to it to ensure consistency. Callers have to inspect the return value and make sure to update their understanding of the file accordingly. This would for example be used in the situation of filename conflicts. Also see the \"Conflict resolution\" chapter below.\r\n\r\nA special case of a tuple rewrite is when a caller passes \"null\" as one of the tuple values. Caller can then expect the APL implementation to generate the value. This would be used when creating new files to obtain a hash.\r\n\r\nInternally, APL's storage format should be treated as proprietary to that APL. APL's are interchangeable in terms of the API, but not the data already stored - a data migration script would be necessary for that.\r\n\r\nSee \"Appendix B: Trivial APL method\" for some contextual pseudocode.\r\n\r\n### Storage backends\r\n\r\nThis RFC does not define how a concrete APL would work under the hood. The architecture and the interfaces of the storage backends is left to the implementor.\r\n\r\nThat said, we expect the Flysystem will be useful for this.\r\n\r\n### Simple APL\r\n\r\nA default, backwards-compatible implementation of the APL would be built as part of this RFC. With this APL, it would be possible for the apparent filesystem on disk would remain as it currently is to allow easier migration of the existing environments.\r\n\r\nThe Hash value in the tuple would be ignored by this backend.\r\n\r\nWe will most likely use the Flysystem as a backend for this APL - although we will need to resolve the Flysystem's PHP 5.3 incompatibility somehow (we want this change to be submitted against the 3.x branch).\r\n\r\nAlthough the Simple APL would support different backends through the Flysystem, because of the problems described in the \"Asynchronous APL API\" and the \"Performance\" chapters, we wouldn't recommend using this with S3 backend.\r\n\r\n### Other considerations\r\n\r\n#### Handling directories\r\n\r\nA directory (a \"folder\") is a concept that is not reflected in the APL interface. APL operations can only handle file references.\r\n\r\nUnder the hood however, a concrete APL may use the tuple values provided in the method calls to reconstruct the directories (specifically, the ParentObject might be used to recover the structure). It is entirely up to the implementation to handle this.\r\n\r\nOn the other end of the spectrum, the Files & Images' notion of \"folders\" will remain expressed as `Folder` data objects, but their `FileReferenceField` would be set to null (again, that's because the APL does not handle folders explicitly).\r\n\r\n#### Conflict resolution\r\n\r\nA conflict may occur if an attempt is made to write data to a tuple which already exists. The APL caller can pass their resolution preference though the `$conflictResolution` parameter.\r\n\r\nThe APL may support the following resolution approaches:\r\n\r\n* \"exception\" - throw an APLConflict exception.\r\n* \"overwrite\" - overwrite the data at the tuple\r\n* \"rename\" - allow the APL to change the tuple values to non-conflicting and write the data to a new location. APL will return the modified tuple.\r\n\r\n#### Derived files\r\n\r\nThe `Image` class provides file processing services. Files derived in this way are not represented in the database and are currently written to disk to an arbitrary location by the `GDBackend`.\r\n\r\nTo keep this RFC focused on the file abstraction, we propose to do the minimum adjustment needed to remove the direct filesystem operations while avoiding refactoring of the processing subsystem.\r\n\r\nThe `FileReferenceField` would allow us to obtain a derived file reference (that is not stored in the database).\r\n\r\n```php\r\n// Obtain the derived FileReferenceField from an existing one.\r\n$derivedReference = $fileReference->derive(\"Resized640x480\");\r\n// Work with the file reference as normal.\r\n$derivedReference->getAsURL();\r\n```\r\n\r\nNote the APL should not change tuple values for derived files - i.e. if the Variant value of the tuple is not null.\r\n\r\n#### Images in content\r\n\r\nThese will need to be rewritten to use shortcodes. Direct file references by path are no longer valid.\r\n\r\n## Rationale\r\n\r\n### How does this solve the mentioned problems\r\n\r\n#### Changing root path for asset storage\r\n\r\nWith all filesystem operations abstracted away, the default APL can include a configuration parameter for setting the filesystem root path.\r\n\r\n#### Amazon S3 and clustered sites\r\n\r\nThe access pattern used by an APL can be non-trivial. An example of a more sophisticated approach to persistency is a load-balanced site using S3 as a primary storage, and the filesystem as a secondary storage. Such a site would have the following problems:\r\n\r\n- S3 is *eventually* consistent and so the resource might not yet be there when we ask for it, especially if it has just been written.\r\n- the asset might have been uploaded to another machine in the cluster, so the file might not yet be synced back to the local filesystem.\r\n\r\nHere is a sequence diagram illustrating a cascading operation of such an APL. The backends are internal to the APL and invisible to the userspace code.\r\n\r\n![](http://i.imgur.com/RZwOgeZ.jpg)\r\n\r\n#### Versioned files\r\n\r\nThe APL does not deal with file versions, but it has the potential to store many distinct files with the same name thanks to the presence of the Hash in the file reference tuple.\r\n\r\nThe `FileReferenceField` serialises to the database as strings, so it may versioned just as any other database field. This paves the way to using `Versioned` extension directly on `File`.\r\n\r\n#### Private files\r\n\r\nOnce we switch to the File References, using `File` objects to manage files would no longer be obligatory. It would now be possible to bypass `File` and \"attach\" files to arbitrary `DataObject`s using the File Reference `DBField` directly.\r\n\r\n```php\r\nprivate static $db = array(\r\n\t'AttachedFile' => 'FileReference'\r\n);\r\n```\r\n\r\nThis would mean opting out of the usual functionality - the file would no longer appear in the \"Files & Images\" section. Custom methods to manipulate the assets would need to be built, or the `UploadField` could be used to directly interface with the `FileReferenceField`s.\r\n\r\n#### Files with workflow\r\n\r\nEssentially a mix of the file versioning and private files approach could be used to accomplish the workflow. Since files are now treated as model properties, we have a freedom to create new models and interfaces. We can also easily move files between the models by copying the tuple - e.g. from the \"Workflow\" model into the \"Files & Images\".\r\n\r\n### Alternative proposals\r\n\r\n* [micmania1's pull request](https://github.com/silverstripe/silverstripe-framework/issues/3562) refactors the Framework so that the filesystem operations are abstracted away, but the file references are not generalised (i.e. the backend still utilises the notion of path).\r\n* [Marcus's proposal](https://groups.google.com/d/msg/silverstripe-dev/MXd-908kxh0/a807iJVSapUJ) is similar to this RFC in that the filesystem operations are abstracted away, and a notion of a file reference is introduced (called a pointer - \"ContentId\"). What differs is this solution does not involve Framework modifications and is delivered as modules.\r\n\r\n### Common objections\r\n\r\n* **Versioning of files** was not universally seen as an important driver for this refactoring. It was thought that just decoupling the filesystem operations would be a good-enough solution (micmania1's solution would work in this case). This RFC is of opposite opinion: a potential for file versioning is a strategic feature of the Framework.\r\n* **File reference** was envisaged to be just a content hash, which would yield a simpler API. We found this to be too inflexible - it wouldn't allow us to build a backwards-compatible backend with unchanged on-disk structure. We decided to use a multi-value tuple instead.\r\n\r\n\r\n### Deferred & rejected features\r\n\r\n#### Deferred: Rewriting derived files subsystem into a separate API\r\n\r\nDeferred to keep this RFC focused. See \"Derived files\" chapter.\r\n\r\n#### Rejected: Versioned APL\r\n\r\nWe have decided the APL is not the right layer to implement file versioning and that it should be implemented on a higher level. This RFC will make it possible to accomplish both versioning folders and files.\r\n\r\nSee \"How does this solve the problems\" chapter.\r\n\r\n#### Rejected: Tuple as an object\r\n\r\nThis would be too complex and ultimately make the conceptual model too convoluted.\r\n\r\nThe drawback of not doing this is that in this proposal the `FileReferenceField` must act both as the tuple representation in code (with all tuple manipulation methods), and as a database field representation.\r\n\r\nThis violates the single-responsibility principle and leads to edge-case problems such as the one presented in the \"Derived files\" chapter, where a crippled `FileReferenceField` is produced that does not represent any database entity (which is counter-intuitive, because it's a `DBField` after all). \r\n\r\n#### Rejected: Extending the tuple with more values\r\n\r\nWe think the tuple is such a fundamental representation of the file resource that it should be fixed (just as a `Varchar` representation is fixed). Tuple was designed such that it should contain enough specificity to allow for all use cases listed in \"Motivation\" chapter.\r\n\r\nAllowing to extend it with custom values would cause module compatibility problems, and we want the APLs to be interchangeable.\r\n\r\n#### Rejected: Leaving derived files subsystem as it is (with direct local filesystem operations)\r\n\r\nThis wouldn't work for clustered sites. File derivation isn't always triggered on demand, which means some derived file will only be available on some local filesystem. Passing the derived files into the APL makes sure the files are always treated consistently.\r\n\r\nFor the same reason this won't work for deployments where there is no local persistent  storage available (e.g. AWS deployment utilising only S3).\r\n\r\n#### Rejected: Asynchronous APL API\r\n\r\nWrites to a backend might take long time, it's unclear what a \"rename\" or \"exception\" conflict resolution should do if we execute subsequent write to the same resource reference without waiting for completion. In this case we could end up losing data.\r\n\r\nTo solve this we could introduce an asynchronous API for the APL. A complete tuple corresponds to a single write operation, so we could possibly add a \"getWriteStatus(<tuple>)\" method to the APL. It could then return \"in progress\", \"success\", \"failure\". a UI could poll that until the result was success; if it returned failure then the rename/replace/retry logic could be put in place.\r\n\r\nHowever this can also be solved by using an APL that supports content hashes (there woudln't ever be any conflicts in such an APL) so we decided that it's not worth introducing the additional complexity related to the asynchronous API.\r\n\r\nIt's worth noting the Flysystem developers [discarded asynchronous operation](https://github.com/thephpleague/flysystem/issues/198) in their API.\r\n\r\n## Impact\r\n\r\n### Backwards-incompatible changes\r\n\r\n* API for `File`, `Image` and `Folder` - removal of the old path and name-related `DBFields` and adding the FileReferenceField.\r\n* `ASSETS_DIR` will be removed\r\n* `ASSETS_PATH` will be removed\r\n* All newly added images in the content areas will use shortcodes\r\n* ... and possibly more, but hopefully not too many - to be discovered while refactoring\r\n* Using Flysystem in the Simple APL will bump the required PHP version to 5.4\r\n\r\n### Security\r\n\r\nThe significant impact is that until the \"secureassets\" module is rewritten to support the APL and to work with the tuples there won't be any easy way to support secure files. \r\n\r\nFrom the user perspective, there won't be any changes in how `File` objects handle security. AssetAdmin, GridField and Subsites should not be impacted.\r\n\r\nThe real potential for improvement lies in custom APLs which will be able to secure files in ways different from the `.htaccess` approach. The `FileReferenceField` won't have any inherent notion of security and it will be up to the implementors of the APL to derive the security properties from the file reference tuple (especially the ParentObject). \r\n\r\nFor example, APL, while performing a write operation, could distinguish between secure (proxied) and insecure (directly accessible) assets by inspecting the ParentObject. It'd then store the resource in an appropriate backend. It would be up to the \"secure\" backend to ensure the authorisation is done upon a request, e.g. by passing the request through a Controller.\r\n\r\n### Performance\r\n\r\nThe Simple APL will write to the local filesystem, so we expect the performance not to be different than before the change. The APL method calls will be synchronous.\r\n\r\nHowever a special consideration needs to be given when writing APLs with long-running operations. The APL layer does not inherently support asynchronicity: writing data asynchronously leaves the backend in an unspecified state and the API does not make any provisions for introspecting this state. Instead we recommend using content hashes in your APL (see also the \"Asynchronous APL API\" chapter) - this allows the APL to avoid tuple clashes and unexpected overwrites of content.\r\n\r\nAnother thing to keep in mind for custom APLs is that the presence of long-running synchronous operations will impact the UI considerably. One solution here could be to execute some part of the write synchronously (i.e. to the local filesystem), but delegate the long-running writes to a queue.\r\n\r\n### Scalability\r\n\r\nThis change is a prerequisite for better scalability. Shifting the assets off the local machine is required for any kind of clustered or scalable environment. That said, the work included in this RFC will not affect the scalability directly - the Simple APL will predominantly be targeted towards the local filesystem.\r\n\r\n### Maintenance & operational\r\n\r\n#### Backups\r\n\r\nWith Simple APL, the backups will be done as before: a database dump and the filesystem snapshot are enough to restore all data.\r\n\r\nHowever backups may not be trivial when other APLs are used. People performing maintenance will need to know how to find out which APL is in use, and act accordingly.\r\n\r\n#### Migration\r\n\r\nWith Simple APL the filesystem representation will remain in place. However on the database level the migration from older sites will not be a straighforward copy. A migration script will be provided to convert the old File, Folder and Image data into the format used by the `FileReferenceFields`.\r\n\r\nThe same situation will occur when an APL is exchanged on a project with existing data - storage format is proprietary to the APL, so the data needs to be transformed for the new APL to understand it.\r\n\r\n### Internationalisation & localisation\r\n\r\nWe expect minimal or no modifications to the UI, so there should be no impact to i18n.\r\n\r\nFor l10n, situation will not change either: with Simple APL the filesystem representation will remain as is. The database representation will change, but it will still hold the filenames. Other parts of the tuple do not introduce any l10n considerations.\r\n\r\n## References\r\n\r\n1. [UserVoice: Files should be stored in DataObject fields](http://silverstripe.uservoice.com/forums/251266-new-features/suggestions/6425928-files-should-be-stored-in-dataobject-fields)\r\n2. [UserVoice: Filesystem Abstraction](http://silverstripe.uservoice.com/forums/251266-new-features/suggestions/6572660-filesystem-abstraction)\r\n3. [GitHub: micmania1's pull request](https://github.com/silverstripe/silverstripe-framework/issues/3562)\r\n4. [dev-list: Storing files in dataobject fields thread](https://groups.google.com/forum/#!topic/silverstripe-dev/MXd-908kxh0)\r\n5. [dev-list: Historical ASSET_BASE_PATH thread](https://groups.google.com/forum/?hl=en&fromgroups=#!topic/silverstripe-dev/Z7CmioND5Ow)\r\n6. [dev-list: Historical Silverstripe (Assets) vs. Scaling in the Cloud thread](https://groups.google.com/d/msg/silverstripe-dev/2so5i8a7ltY/FlvTUpM0oecJ)\r\n\r\n## Appendix A: APL interface\r\n\r\nHere is our proposition for the **minimal** APL interface. We may add more methods if we disover they are generic enough to be included.\r\n\r\nThe tuple needs to be included in all calls and is always `$hash, $variant, $filename, $parentObj`. Any values in the tuple may be set to null to indicate the caller is expecting the APL implementation to generate the values. The possibly modified tuple is also returned from the setters.\r\n\r\nSee the \"Proposal\" chapter for more information on the tuple.\r\n\r\n```php\r\ninterface AssetPersistenceLayer {\r\n\r\n\t/**\r\n\t * Write the data directly.\r\n\t *\r\n\t * The tuple may be modified by this method - it's the caller's responsibility\r\n\t * to update their data structures based on the return value. However\r\n\t * the APL should not change tuple values for derived files - i.e. if the Variant\r\n\t * value of the tuple is not null.\r\n\t *\r\n\t * @param $hash string|null Tuple part 1\r\n\t * @param $variant string|null Tuple part 2\t \r\n \t * @param $filename string|null Tuple part 3\r\n \t * @param $parentObj Object|null Tuple part 4\r\n\t * @param $data Binary data to set on the file reference.\r\n\t * @param $conflictResolution string Conflict resolution hint (exception|overwrite|rename)\r\n\t *\r\n\t * @return array($hash, $variant, $filename, $parentObj) The (possibly modified) file reference tuple.\r\n\t */\r\n\tfunction setFromString($hash, $variant, $filename, $parentObj, $data, $conflictResolution);\r\n\r\n\t/**\r\n\t * It might be faster for the implementation to operate directly on the file,\r\n\t * so the consuming code may choose to use this instead.\r\n\t *\r\n\t * The tuple may be modified by this method - it's the caller's responsibility\r\n\t * to update their data structures based on the return value. However\r\n\t * the APL should not change tuple values for derived files - i.e. if the Variant\r\n\t * value of the tuple is not null.\r\n\t *\r\n\t * @param $hash string|null Tuple part 1\r\n\t * @param $variant string|null Tuple part 2\t \r\n \t * @param $filename string|null Tuple part 3\r\n \t * @param $parentObj Object|null Tuple part 4\r\n\t * @param $path Absolute path to the file\r\n\t * @param $conflictResolution string Conflict resolution hint (exception|overwrite|rename)\r\n\t *\r\n \t * @return array($hash, $variant, $filename, $parentObj) The (possibly modified) file reference tuple.\r\n\t */\r\n\tfunction setFromLocalFile($hash, $variant, $filename, $parentObj, $path, $conflictResolution);\r\n\r\n\t/**\t\r\n\t * @param $hash string|null Tuple part 1\r\n\t * @param $variant string|null Tuple part 2\t \r\n \t * @param $filename string|null Tuple part 3\r\n \t * @param $parentObj Object|null Tuple part 4\r\n \t *\r\n\t * @return string Data from the file.\r\n\t */\r\n\tfunction getAsString($hash, $variant, $filename, $parentObj);\r\n\r\n\t/**\r\n\t * @param $hash string|null Tuple part 1\r\n\t * @param $variant string|null Tuple part 2\t \r\n \t * @param $filename string|null Tuple part 3\r\n \t * @param $parentObj Object|null Tuple part 4\r\n\t *\r\n\t * @return string URL for the data blob that can be fetched directly by the user.\r\n\t */\r\n\tfunction getAsURL($hash, $variant, $filename, $parentObj);\r\n}\r\n```\r\n\r\n## Appendix B: Trivial APL method\r\n\r\nExample pseudocode for a trivial APL method to illustrate several concepts from this RFC. Note this is not a code from any actual APL - Simple APL will use Flysystem and will likely be more complex.\r\n\r\n```php\r\nfunction setFromString($hash, $variant, $filename, $parentObj, $data, $conflictResolution) {\r\n\t// This backend does not support hashes, make sure it's null.\r\n\t$hash = null;\r\n\t\r\n\t// It is technically legal to pass an empty tuple value and expect the APL\r\n\t// to generate it. It would be more useful as a hash generation mechanism though.\r\n\tif (!$filename) $filename = 'unnamed_data_blob';\r\n\t\r\n\t// Find out the directory from the context.\r\n\tif ($parentObj instanceof File) {\r\n\t\t$path = $parentObj->getParent()->getRelativePath() . '/';\r\n\t} else {\r\n\t\t$path = 'homeless/';\r\n\t}\r\n\t\r\n\t// Configurable root directory for the backend.\r\n\t$path = $this->config()->root_directory . '/' . $path;\r\n\t\r\n\t// Do not permit tuple changes for derived files.\r\n\tif ($variant && $conflictResolution!='overwrite') throw new Exception(...);\r\n\t\r\n\t// Variable conflict handling.\r\n\tif (file_exists($path . $filename)) {\r\n\t\tswitch($conflictResolution) {\r\n\t\t\tcase 'exception': \r\n\t\t\t\tthrow new APLConflict(...);\r\n\t\t\t\tbreak;\r\n\t\t\tcase 'rename':\r\n\t\t\t\t$filename = $this->resolveFilenameConflict($filename);\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Finally - write the data.\r\n\tfile_put_contents($path . $filename, $data);\r\n\t\r\n\t// Return the new - possibly updated - tuple.\r\n\treturn array($hash, $variant, $filename, $parentObj);\r\n}\r\n```"},"comment":{"url":"https://api.github.com/repos/silverstripe/silverstripe-framework/issues/comments/69974813","html_url":"https://github.com/silverstripe/silverstripe-framework/issues/3792#issuecomment-69974813","issue_url":"https://api.github.com/repos/silverstripe/silverstripe-framework/issues/3792","id":69974813,"user":{"login":"stevie-mayhew","id":1953220,"avatar_url":"https://avatars.githubusercontent.com/u/1953220?v=3","gravatar_id":"","url":"https://api.github.com/users/stevie-mayhew","html_url":"https://github.com/stevie-mayhew","followers_url":"https://api.github.com/users/stevie-mayhew/followers","following_url":"https://api.github.com/users/stevie-mayhew/following{/other_user}","gists_url":"https://api.github.com/users/stevie-mayhew/gists{/gist_id}","starred_url":"https://api.github.com/users/stevie-mayhew/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stevie-mayhew/subscriptions","organizations_url":"https://api.github.com/users/stevie-mayhew/orgs","repos_url":"https://api.github.com/users/stevie-mayhew/repos","events_url":"https://api.github.com/users/stevie-mayhew/events{/privacy}","received_events_url":"https://api.github.com/users/stevie-mayhew/received_events","type":"User","site_admin":false},"created_at":"2015-01-14T19:24:00Z","updated_at":"2015-01-14T19:24:00Z","body":"I have to agree with @oddnoc - I don't see any way which this can be in a 3.x release - its a major change to an API. I thought that we were adopting semantic versioning once the PDO release has come out, which I assumed would be a 3.2 release under the current schema. That would make this a 4.x release at bst.\r\n\r\nI'm also worried about the continued persistence in maintaining PHP 5.3 support and this is a perfect example - having to resolve a third party's non-support of 5.3 isn't a trivial matter (who knows what they think about it!)"}}