You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Mar 5, 2022. It is now read-only.
The **File Storage** plugin is giving you the possibility to upload and store files in virtually any kind of storage backend. This plugin is wrapping the [Gaufrette](https://github.com/KnpLabs/Gaufrette) library in a CakePHP fashion and provides a simple way to use the storage adapters through the [StorageManager](Lib/StorageManager.php) class.
9
11
10
12
Storage adapters are an unified interface that allow you to store file data to your local file system, in memory, in a database or into a zip file and remote systems. There is a database table keeping track of what you stored where. You can always write your own adapter or extend and overload existing ones.
[Please donate if you like it!](https://pledgie.com/campaigns/29682)
15
+
------------------------------
16
+
17
+
Already thought of how many hours development time this plugin saved you already? It would be *awesome* if you don't mind sharing some of your success by donating a small amount! Thank you.
18
+
19
+
How it works
20
+
------------
21
+
22
+
The whole plugin is build with clear [Separation of Concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) in mind: A file is *always* an entry in the `file_storage` table from the app perspective. The table is the *reference* to the real place of where the file is stored and keeps some meta information like mime type, filename, file hash (optional) and size as well. You associate the `file_storage` table with your model using the FileStorage or ImageStorage model from the plugin via hasOne, hasMany or HABTM. When you upload a file you save it to the FileStorage model through the associations, `Documents.file` for example. The FileStorage model dispatches then file storage specific events, the listeners listening to these events process the file and put it in the configured storage backend using adapters for different backends and build the storage path using a path builder class.
Copy file name to clipboardExpand all lines: docs/Documentation/How-To-Use.md
+27-24Lines changed: 27 additions & 24 deletions
Original file line number
Diff line number
Diff line change
@@ -9,8 +9,8 @@ The basic idea of this plugin is that files are always handled as separate entit
9
9
10
10
This plugin resolves that issue by handling each file as a completely separate entity in the application. There is just one table `file_storage` that will keep the reference to all your files, no matter where they're stored.
11
11
12
-
How to Store an Uploaded File
13
-
-----------------------------
12
+
Preparing the File Upload
13
+
-------------------------
14
14
15
15
This section is going to show how to store a file using the Storage Manager directly.
16
16
@@ -20,36 +20,43 @@ For example you have a Report model and want to save a pdf to it, you would then
20
20
public function initialize(array $config)
21
21
{
22
22
$this->hasOne('PdfFiles', [
23
-
'className' => 'FileStorage.PdfFiles',
24
-
'foreignKey' => 'foreign_key'
23
+
'className' => 'Burzum/FileStorage.PdfFiles',
24
+
'foreignKey' => 'foreign_key',
25
+
'conditions' => [
26
+
'PdfFiles.model' => 'Reports'
27
+
]
25
28
]);
26
29
}
27
30
```
28
31
29
-
In your add.ctp or edit.ctp views you would add something like:
32
+
In your `add.ctp` or `edit.ctp` views you would add something like:
30
33
31
34
```php
32
-
echo $this->Form->input('Report.title');
33
-
echo $this->Form->input('PdfFile.file');
34
-
echo $this->Form->input('Report.description');
35
+
echo $this->Form->input('title');
36
+
echo $this->Form->input('pdf_files.file');
37
+
echo $this->Form->input('description');
35
38
```
36
39
40
+
Handling the File Upload
41
+
------------------------
42
+
37
43
**Now comes the crucial point of the whole implementation**
38
44
39
45
Because of to many different requirements and personal preferences out there the plugin is *not* automatically storing the file. You'll have to customize it a little but its just a matter for a few lines.
40
46
41
47
Lets go by this scenario inside the report model, assuming there is an add() method:
42
48
43
49
```php
44
-
$entity = $this->newEntity($data);
50
+
$entity = $this->newEntity($postData);
45
51
$saved = $this->save($entity);
46
52
if ($saved) {
47
53
$key = 'your-file-name';
48
-
if (StorageManager::adapter('Local')->write($key, file_get_contents($this->data['PdfFile']['file']['tmp_name']))) {
@@ -69,16 +76,16 @@ The **FileStorage** plugin comes with a class that acts just as a listener to so
69
76
70
77
This class will listen to all the ImageStorage model events and save the uploaded image and then create the versions for that image and storage adapter.
71
78
72
-
It is important to understand that each storage adapter requires a different handling. You can not treat a local file the same as a file you store in a cloud service. The interface that this plugin and Gaufrette provide is the same but not the internals.
79
+
It is important to understand that nearly each storage adapter requires a little different handling: Most of the time you can't treat a local file the same as a file you store in a cloud service. The interface that this plugin and Gaufrette provide is the same but not the internals. So a path that works for your local file system might not work for your remote storage system because it has other requirements or limitations.
73
80
74
-
So if you want to store a file using Amazon S3 you would have to store it, create all the versions of that image locally and then upload each of them and then delete the local temp files.
81
+
So if you want to store a file using Amazon S3 you would have to store it, create all the versions of that image locally and then upload each of them and then delete the local temp files. The good news is the plugin can already take care of that.
75
82
76
-
When you create a new listener it is important that you check the model field and the event subject object if it matches what you expect. Using the event system you could create any kind of storage and upload behavior without inheriting or touching the model code. Just write a listener class and attach it to the global CakeEventManager.
83
+
When you create a new listener it is important that you check the `model` field and the event subject object (usually a table object inheriting \Cake\ORM\Table) if it matches what you expect. Using the event system you could create any kind of storage and upload behavior without inheriting or touching the model code. Just write a listener class and attach it to the global EventManager.
77
84
78
85
List of events
79
86
--------------
80
87
81
-
Events triggered in the ImageStorage model:
88
+
Events triggered in the `ImageStorage` model:
82
89
83
90
* ImageVersion.createVersion
84
91
* ImageVersion.removeVersion
@@ -87,7 +94,7 @@ Events triggered in the ImageStorage model:
87
94
* ImageStorage.beforeDelete
88
95
* ImageStorage.afterDelete
89
96
90
-
Events triggered in the FileStorage model:
97
+
Events triggered in the `FileStorage` model:
91
98
92
99
* FileStorage.beforeSave
93
100
* FileStorage.afterSave
@@ -101,14 +108,10 @@ See [this page](Included-Event-Listeners.md) for the event listeners that are in
101
108
Why is it done like this?
102
109
-------------------------
103
110
104
-
Every developer might want to store the file at a different point or apply other operations on the file before or after it is store. Based on different circumstances you might want to save an associated file even before you created the record its going to get attached to, in other scenarios like in this documentation you might want to do it after.
111
+
Every developer might want to store the file at a different point or apply other operations on the file before or after it is stored. Based on different circumstances you might want to save an associated file even before you created the record its going to get attached to, in other scenarios like in this documentation you might want to do it after.
105
112
106
113
The ``$key`` is also a key aspect of it: Different adapters might expect a different key. A key for the Local adapter of Gaufrette is usually a path and a file name under which the data gets stored. That's also the reason why you use `file_get_contents()` instead of simply passing the tmp path as it is.
107
114
108
115
It is up to you how you want to generate the key and build your path. You can customize the way paths and file names are build by writing a custom event listener for that.
109
116
110
117
It is highly recommended to read the Gaufrette documentation for the read() and write() methods of the adapters.
The whole plugin is build with clear [Separation of Concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) in mind: A file is *always* an entry in the `file_storage` table from the app perspective.
5
+
6
+
The table is the *reference* to the real place of where the file is stored and keeps some meta information like mime type, filename, file hash (optional) and size as well. You associate the `file_storage` table with your model using the FileStorage or ImageStorage model from the plugin via hasOne, hasMany or HABTM.
7
+
8
+
When you upload a file you save it to the FileStorage model through the associations, `Documents.file` for example. The FileStorage model dispatches then file storage specific events. The listeners listening to these events process the file and put it in the configured storage backend using adapters for different backends and build the storage path using a path builder class.
**[For the deprecated event listeners please click here](Legacy-Event-Listeners.md)**
6
5
7
-
The file and folder structure it will generate looks like that:
6
+
---
8
7
9
-
```
10
-
basePath/files/xx/xx/xx/<uuid>/<uuid>.<extension>
11
-
```
8
+
Introduction
9
+
------------
12
10
13
-
ImageProcessingListener
14
-
-----------------------
11
+
The included event listeners will throw a StorageException when something went wrong. It's your duty to handle them. Also you can configure a logger to the `storage` log scope to filter logs by this scope.
15
12
16
-
This listener will create versions of images if Configure::read('Media.imageSizes.' . $model); is not empty. If no processing operations for that model were specified it will just save the image.
13
+
Each listener has a configured *Path Builder*, check the [path builder documentation] to see what they do and what their purpose is.
17
14
18
-
This adapter replaces LocalImageProcessingListener and currently supports the Local and AmazonS3 adapter.
15
+
To change the path builder config for a listener check what path builder the listener is using and pass the path builder config to the constructor of the listener:
16
+
17
+
```php
18
+
$listener = new LocalListener([
19
+
'pathBuilderOptions' => [
20
+
// options go here
21
+
]
22
+
]);
23
+
```
19
24
20
-
The file and folder structure it will generate looks like that:
25
+
If you want to implement your own listeners you'll have to extend them from the [AbstractListener](../../src/Storage/Listener/AbstractListener.php) and implement the event callbacks.
26
+
27
+
Local Listener
28
+
--------------
29
+
30
+
The local listener will store files by default in this kind of path:
* For the Local adapter basePath is the value configured for this adapter, by default the `TMP` constant.
33
-
* For AmazonS3 the basePath will be the bucket and Amazon S3 URL prefix.
34
-
35
-
xx stands for a semi random alphanumerical value calculated based on the given file name if the Local adapter was used.
42
+
The listener is using by default the `LocalPathBuilder` to generate the path.
36
43
37
-
**Some important notes about the path the processor generates:**
44
+
The reason for the UUID folder name is simply to ensure it is unique per file and it makes it easy to store versions of the same file in the same folder.
38
45
39
-
The path stored to the database is **not** going to be the complete path, it won't add the filename for a reason.
46
+
AWS S3 Listener
47
+
---------------
40
48
41
-
The filename is generated by the processor on the fly when adding/deleting/modifying images because the versions are build on the fly and not stored to the database. See `ImageProcessingListener::_buildPath()`.
49
+
There is no new AWS S3 listener yet, you can either use the old legacy listener or write your own based on the new listeners. A contribution of a new listener is highly welcome!
42
50
43
-
LocalImageProcessingListener (deprecated)
44
-
-----------------------------------------
51
+
Legacy Local File Storage Listener
52
+
----------------------------------
45
53
46
-
The LocalImageProcessingListener is **deprecated**, use ImageProcessingListener.
54
+
This listener mimics the behavior of the deprecated `LocalFileStorageEventListener`.
0 commit comments