Skip to content
This repository was archived by the owner on Mar 5, 2022. It is now read-only.

Commit 6dafebb

Browse files
author
Florian Krämer
committed
Merge branch '3.0-event-overhaul' into 1.1.0
Conflicts: src/Model/Table/FileStorageTable.php tests/TestCase/Model/Table/FileStorageTest.php
2 parents 0427346 + 3a1e6e8 commit 6dafebb

File tree

71 files changed

+4137
-572
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+4137
-572
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
/vendor
22
/plugins
3-
/tmp
3+
/tmp
4+
/composer.lock
5+
/nbproject

.scrutinizer.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@ checks:
55
remove_php_closing_tag: true
66
remove_trailing_whitespace: true
77
tools:
8-
external_code_coverage:
9-
timeout: 1800
10-
runs: 1
118
php_code_coverage: false
129
php_loc:
1310
enabled: true
1411
excluded_dirs: [vendor, tests, config, docs]
1512
php_cpd:
1613
enabled: true
17-
excluded_dirs: [vendor, tests, config, docs]
14+
excluded_dirs: [vendor, tests, config, docs]
15+
filter:
16+
excluded_paths: [src/Event/, src/Lib/]
17+
build:
18+
tests:
19+
override:
20+
-
21+
command: 'phpunit --coverage-clover=coverage.xml'
22+
coverage:
23+
file: 'coverage.xml'
24+
format: 'php-clover'

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
language: php
2+
sudo: false
23

34
php:
45
- 5.4

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
1-
FileStorage Plugin for CakePHP 2.x and 3.x
2-
==========================================
1+
FileStorage Plugin for CakePHP
2+
==============================
33

44
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.txt)
55
[![Build Status](https://img.shields.io/travis/burzum/cakephp-file-storage/3.0.svg?style=flat-square)](https://travis-ci.org/burzum/cakephp-file-storage)
66
[![Coverage Status](https://img.shields.io/coveralls/burzum/cakephp-file-storage/3.0.svg?style=flat-square)](https://coveralls.io/r/burzum/cakephp-file-storage)
77

8+
**If you're upgrading from CakePHP 2.x please read [the migration guide](docs/Documentation/Migrating-from-CakePHP-2.md).**
9+
810
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.
911

1012
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.
1113

12-
**If you're upgrading from CakePHP 2.x please read [the migration guide](docs/Documentation/Migrating-from-CakePHP-2.md).**
14+
[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.
1323

14-
**List of supported Adapters**
24+
List of supported Adapters
25+
--------------------------
1526

1627
* Apc
1728
* Amazon S3

composer.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
],
1414
"minimum-stability": "dev",
1515
"require": {
16-
"php": ">=5.4.19",
1716
"cakephp/cakephp": "~3.0",
1817
"cakephp/plugin-installer": "*",
1918
"cakephp/migrations": "~1.0",

config/bootstrap.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<?php
2-
use \Cake\Event\EventManager;
3-
use \Burzum\FileStorage\Event\ImageProcessingListener;
4-
use \Burzum\FileStorage\Event\LocalFileStorageListener;
2+
use Burzum\FileStorage\Storage\Listener\LocalListener;
3+
use Cake\Event\EventManager;
54

6-
$listener = new ImageProcessingListener();
7-
EventManager::instance()->on($listener);
8-
9-
$listener = new LocalFileStorageListener();
5+
$listener = new LocalListener([
6+
'imageProcessing' => true
7+
]);
108
EventManager::instance()->on($listener);

docs/Documentation/Database-Setup.md

Lines changed: 0 additions & 21 deletions
This file was deleted.

docs/Documentation/How-To-Use.md

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ The basic idea of this plugin is that files are always handled as separate entit
99

1010
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.
1111

12-
How to Store an Uploaded File
13-
-----------------------------
12+
Preparing the File Upload
13+
-------------------------
1414

1515
This section is going to show how to store a file using the Storage Manager directly.
1616

@@ -20,36 +20,43 @@ For example you have a Report model and want to save a pdf to it, you would then
2020
public function initialize(array $config)
2121
{
2222
$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+
]
2528
]);
2629
}
2730
```
2831

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:
3033

3134
```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');
3538
```
3639

40+
Handling the File Upload
41+
------------------------
42+
3743
**Now comes the crucial point of the whole implementation**
3844

3945
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.
4046

4147
Lets go by this scenario inside the report model, assuming there is an add() method:
4248

4349
```php
44-
$entity = $this->newEntity($data);
50+
$entity = $this->newEntity($postData);
4551
$saved = $this->save($entity);
4652
if ($saved) {
4753
$key = 'your-file-name';
48-
if (StorageManager::adapter('Local')->write($key, file_get_contents($this->data['PdfFile']['file']['tmp_name']))) {
49-
$this->data['PdfFile']['foreign_key'] = $saved->id;
50-
$this->data['PdfFile']['model'] = 'Report';
51-
$this->data['PdfFile']['path'] = $key;
52-
$this->data['PdfFile']['adapter'] = 'Local';
54+
if (StorageManager::adapter('Local')->write($key, file_get_contents($this->data['pdf_files']['file']['tmp_name']))) {
55+
$postData['pdf_files']['foreign_key'] = $saved->id;
56+
$postData['pdf_files']['model'] = 'Reports';
57+
$postData['pdf_files']['path'] = $key;
58+
$postData['pdf_files']['adapter'] = 'Local';
59+
$this->PdfDocuments->save($this->PdfDocuments->newEntity($postData));
5360
}
5461
}
5562
```
@@ -69,16 +76,16 @@ The **FileStorage** plugin comes with a class that acts just as a listener to so
6976

7077
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.
7178

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.
7380

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.
7582

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.
7784

7885
List of events
7986
--------------
8087

81-
Events triggered in the ImageStorage model:
88+
Events triggered in the `ImageStorage` model:
8289

8390
* ImageVersion.createVersion
8491
* ImageVersion.removeVersion
@@ -87,7 +94,7 @@ Events triggered in the ImageStorage model:
8794
* ImageStorage.beforeDelete
8895
* ImageStorage.afterDelete
8996

90-
Events triggered in the FileStorage model:
97+
Events triggered in the `FileStorage` model:
9198

9299
* FileStorage.beforeSave
93100
* FileStorage.afterSave
@@ -101,14 +108,10 @@ See [this page](Included-Event-Listeners.md) for the event listeners that are in
101108
Why is it done like this?
102109
-------------------------
103110

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.
105112

106113
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.
107114

108115
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.
109116

110117
It is highly recommended to read the Gaufrette documentation for the read() and write() methods of the adapters.
111-
112-
113-
114-

docs/Documentation/How-it-works.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
How it works
2+
------------
3+
4+
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.
Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,55 @@
11
Included Event Listeners
22
========================
33

4-
LocalFileStorageListener
5-
------------------------
4+
**[For the deprecated event listeners please click here](Legacy-Event-Listeners.md)**
65

7-
The file and folder structure it will generate looks like that:
6+
---
87

9-
```
10-
basePath/files/xx/xx/xx/<uuid>/<uuid>.<extension>
11-
```
8+
Introduction
9+
------------
1210

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.
1512

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.
1714

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+
```
1924

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:
2131

2232
```
23-
basePath/images/xx/xx/xx/<uuid>/<uuid>.<extension>
33+
<basePath>/<model>/<randomPath>/<uuid>/<uuid>.<extension>
2434
```
2535

26-
Versioned images will look like that
36+
Example:
2737

2838
```
29-
basePath/images/xx/xx/xx/<uuid>/<uuid>.<hash>.<extension>
39+
/var/www/my_app/files/Documents/05/51/68/38f684612c6f11e5a2cb0800200c9a66/38f684612c6f11e5a2cb0800200c9a66.jpg
3040
```
3141

32-
* 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.
3643

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.
3845

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+
---------------
4048

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!
4250

43-
LocalImageProcessingListener (deprecated)
44-
-----------------------------------------
51+
Legacy Local File Storage Listener
52+
----------------------------------
4553

46-
The LocalImageProcessingListener is **deprecated**, use ImageProcessingListener.
54+
This listener mimics the behavior of the deprecated `LocalFileStorageEventListener`.
55+

0 commit comments

Comments
 (0)