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

Commit

Permalink
Merge branch '3.0-event-overhaul' into 1.1.0
Browse files Browse the repository at this point in the history
Conflicts:
	src/Model/Table/FileStorageTable.php
	tests/TestCase/Model/Table/FileStorageTest.php
  • Loading branch information
Florian Krämer committed Aug 25, 2015
2 parents 0427346 + 3a1e6e8 commit 6dafebb
Show file tree
Hide file tree
Showing 71 changed files with 4,137 additions and 572 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/vendor
/plugins
/tmp
/tmp
/composer.lock
/nbproject
15 changes: 11 additions & 4 deletions .scrutinizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@ checks:
remove_php_closing_tag: true
remove_trailing_whitespace: true
tools:
external_code_coverage:
timeout: 1800
runs: 1
php_code_coverage: false
php_loc:
enabled: true
excluded_dirs: [vendor, tests, config, docs]
php_cpd:
enabled: true
excluded_dirs: [vendor, tests, config, docs]
excluded_dirs: [vendor, tests, config, docs]
filter:
excluded_paths: [src/Event/, src/Lib/]
build:
tests:
override:
-
command: 'phpunit --coverage-clover=coverage.xml'
coverage:
file: 'coverage.xml'
format: 'php-clover'
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
language: php
sudo: false

php:
- 5.4
Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
FileStorage Plugin for CakePHP 2.x and 3.x
==========================================
FileStorage Plugin for CakePHP
==============================

[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.txt)
[![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)
[![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)

**If you're upgrading from CakePHP 2.x please read [the migration guide](docs/Documentation/Migrating-from-CakePHP-2.md).**

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.

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.

**If you're upgrading from CakePHP 2.x please read [the migration guide](docs/Documentation/Migrating-from-CakePHP-2.md).**
[Please donate if you like it!](https://pledgie.com/campaigns/29682)
------------------------------

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.

How it works
------------

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.

**List of supported Adapters**
List of supported Adapters
--------------------------

* Apc
* Amazon S3
Expand Down
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
],
"minimum-stability": "dev",
"require": {
"php": ">=5.4.19",
"cakephp/cakephp": "~3.0",
"cakephp/plugin-installer": "*",
"cakephp/migrations": "~1.0",
Expand Down
12 changes: 5 additions & 7 deletions config/bootstrap.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
<?php
use \Cake\Event\EventManager;
use \Burzum\FileStorage\Event\ImageProcessingListener;
use \Burzum\FileStorage\Event\LocalFileStorageListener;
use Burzum\FileStorage\Storage\Listener\LocalListener;
use Cake\Event\EventManager;

$listener = new ImageProcessingListener();
EventManager::instance()->on($listener);

$listener = new LocalFileStorageListener();
$listener = new LocalListener([
'imageProcessing' => true
]);
EventManager::instance()->on($listener);
21 changes: 0 additions & 21 deletions docs/Documentation/Database-Setup.md

This file was deleted.

51 changes: 27 additions & 24 deletions docs/Documentation/How-To-Use.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ The basic idea of this plugin is that files are always handled as separate entit

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.

How to Store an Uploaded File
-----------------------------
Preparing the File Upload
-------------------------

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

Expand All @@ -20,36 +20,43 @@ For example you have a Report model and want to save a pdf to it, you would then
public function initialize(array $config)
{
$this->hasOne('PdfFiles', [
'className' => 'FileStorage.PdfFiles',
'foreignKey' => 'foreign_key'
'className' => 'Burzum/FileStorage.PdfFiles',
'foreignKey' => 'foreign_key',
'conditions' => [
'PdfFiles.model' => 'Reports'
]
]);
}
```

In your add.ctp or edit.ctp views you would add something like:
In your `add.ctp` or `edit.ctp` views you would add something like:

```php
echo $this->Form->input('Report.title');
echo $this->Form->input('PdfFile.file');
echo $this->Form->input('Report.description');
echo $this->Form->input('title');
echo $this->Form->input('pdf_files.file');
echo $this->Form->input('description');
```

Handling the File Upload
------------------------

**Now comes the crucial point of the whole implementation**

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.

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

```php
$entity = $this->newEntity($data);
$entity = $this->newEntity($postData);
$saved = $this->save($entity);
if ($saved) {
$key = 'your-file-name';
if (StorageManager::adapter('Local')->write($key, file_get_contents($this->data['PdfFile']['file']['tmp_name']))) {
$this->data['PdfFile']['foreign_key'] = $saved->id;
$this->data['PdfFile']['model'] = 'Report';
$this->data['PdfFile']['path'] = $key;
$this->data['PdfFile']['adapter'] = 'Local';
if (StorageManager::adapter('Local')->write($key, file_get_contents($this->data['pdf_files']['file']['tmp_name']))) {
$postData['pdf_files']['foreign_key'] = $saved->id;
$postData['pdf_files']['model'] = 'Reports';
$postData['pdf_files']['path'] = $key;
$postData['pdf_files']['adapter'] = 'Local';
$this->PdfDocuments->save($this->PdfDocuments->newEntity($postData));
}
}
```
Expand All @@ -69,16 +76,16 @@ The **FileStorage** plugin comes with a class that acts just as a listener to so

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.

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

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

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

List of events
--------------

Events triggered in the ImageStorage model:
Events triggered in the `ImageStorage` model:

* ImageVersion.createVersion
* ImageVersion.removeVersion
Expand All @@ -87,7 +94,7 @@ Events triggered in the ImageStorage model:
* ImageStorage.beforeDelete
* ImageStorage.afterDelete

Events triggered in the FileStorage model:
Events triggered in the `FileStorage` model:

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

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

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.

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.

It is highly recommended to read the Gaufrette documentation for the read() and write() methods of the adapters.




8 changes: 8 additions & 0 deletions docs/Documentation/How-it-works.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
How it works
------------

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.
57 changes: 33 additions & 24 deletions docs/Documentation/Included-Event-Listeners.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,55 @@
Included Event Listeners
========================

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

The file and folder structure it will generate looks like that:
---

```
basePath/files/xx/xx/xx/<uuid>/<uuid>.<extension>
```
Introduction
------------

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

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.
Each listener has a configured *Path Builder*, check the [path builder documentation] to see what they do and what their purpose is.

This adapter replaces LocalImageProcessingListener and currently supports the Local and AmazonS3 adapter.
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:

```php
$listener = new LocalListener([
'pathBuilderOptions' => [
// options go here
]
]);
```

The file and folder structure it will generate looks like that:
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.

Local Listener
--------------

The local listener will store files by default in this kind of path:

```
basePath/images/xx/xx/xx/<uuid>/<uuid>.<extension>
<basePath>/<model>/<randomPath>/<uuid>/<uuid>.<extension>
```

Versioned images will look like that
Example:

```
basePath/images/xx/xx/xx/<uuid>/<uuid>.<hash>.<extension>
/var/www/my_app/files/Documents/05/51/68/38f684612c6f11e5a2cb0800200c9a66/38f684612c6f11e5a2cb0800200c9a66.jpg
```

* For the Local adapter basePath is the value configured for this adapter, by default the `TMP` constant.
* For AmazonS3 the basePath will be the bucket and Amazon S3 URL prefix.

xx stands for a semi random alphanumerical value calculated based on the given file name if the Local adapter was used.
The listener is using by default the `LocalPathBuilder` to generate the path.

**Some important notes about the path the processor generates:**
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.

The path stored to the database is **not** going to be the complete path, it won't add the filename for a reason.
AWS S3 Listener
---------------

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()`.
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!

LocalImageProcessingListener (deprecated)
-----------------------------------------
Legacy Local File Storage Listener
----------------------------------

The LocalImageProcessingListener is **deprecated**, use ImageProcessingListener.
This listener mimics the behavior of the deprecated `LocalFileStorageEventListener`.

15 changes: 13 additions & 2 deletions docs/Documentation/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ Installing the plugin via [Composer](https://getcomposer.org/) is very simple, j
composer require burzum/file-storage:3.0.*@dev
```

Database Setup
--------------

You need to setup the plugin database using [the official migrations plugin for CakePHP](https://github.com/cakephp/migrations).

```
cake migrations migrate -p Burzum/FileStorage
```

If you're coming from the CakePHP 2.0 version of the plugin, the support for the CakeDC Migrations plugin has been dropped in favor of [the official migrations plugin](https://github.com/cakephp/migrations).

CakePHP Bootstrap
-----------------

Expand Down Expand Up @@ -43,12 +54,12 @@ Depending on the storage backend of your choice, for example Amazon S3 or Dropbo
Please see the [Specific Adapter Configuration](Specific-Adapter-Configurations.md) page of the documentation for more information about then. It is also worth checking the Gaufrette documentation for additonal adapters.

Running Tests
=============
-------------

The plugin tests are set up in a way that you can run them without putting the plugin into a CakePHP3 application. All you need to do is to go into the FileStorage folder and run these commands:

```
cd <file-storage-plugin-folder>
composer install
composer update
phpunit
```
Loading

0 comments on commit 6dafebb

Please sign in to comment.