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

4.0 fixes and improvements #219

Open
wants to merge 18 commits into
base: 4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 5 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ FileStorage Plugin for CakePHP

**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. The plugin features the [Gaufrette](https://github.com/KnpLabs/Gaufrette) **and** [FlySystem](https://github.com/thephpleague/flysystem) library in a CakePHP fashion and provides a simple way to use the storage adapters through the [StorageManager](src/Storage/StorageManager.php) class.
The **File Storage** plugin is giving you the possibility to upload and store files in virtually any kind of storage backend. The plugin features the [FlySystem](https://github.com/thephpleague/flysystem) library in a CakePHP fashion and provides a simple way to use the storage adapters.

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.

Expand All @@ -17,47 +17,22 @@ How it works

The whole plugin is build with clear [Separation of Concerns (SoC)](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. Storing the path to a file inside an arbitrary table along other data is considered as *bad practice* because it doesn't respect SoC from an architecture perspective but many people do it this way for some reason.

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.
You associate the `file_storage` table with your model using the FileStorage 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
--------------------------

* Apc
* Amazon S3
* ACL Aware Amazon S3
* Azure
* Doctrine DBAL
* Dropbox
* Ftp
* Grid FS
* In Memory
* Local File System
* MogileFS
* Open Cloud
* Rackspace Cloudfiles
* Sftp
* Zip File

Supported CakePHP Versions
--------------------------

* CakePHP 4.x -> 3.0 Branch
* CakePHP 4.x -> 4.0 Branch (Rewritten almost from scratch)
* CakePHP 4.x -> 3.0 Branch (Old codebase)
* CakePHP 3.x -> 2.0 Branch
* CakePHP 2.x -> 1.0 Branch

Requirements
------------

* PHP 7.2+
* PHP 7.4+
* CakePHP 4.x
* Gaufrette Storage Library 0.7.x

Optional but required if you want image processing out of the box:

* The [Imagine Image processing plugin](https://github.com/burzum/cakephp-imagine-plugin) if you want to process and store images.
* [FlySystem](https://github.com/thephpleague/flysystem) as alternative library over Gaufrette

You can still implement whatever file processing you want very easy. It's not tied to Imagine.

Documentation
-------------
Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@
],
"autoload": {
"psr-4": {
"Burzum\\FileStorage\\": "src",
"Burzum\\FileStorage\\Test\\Fixture\\": "tests\\Fixture"
"Burzum\\FileStorage\\": "src/",
"Burzum\\FileStorage\\Test\\Fixture\\": "tests/Fixture/"
}
},
"autoload-dev": {
"psr-4": {
"Cake\\Test\\": "/vendor/cakephp/cakephp/tests",
"Burzum\\FileStorage\\Test\\": "tests"
"TestApp\\": "tests/test_app/src/",
"Burzum\\FileStorage\\Test\\TestCase\\": "tests/TestCase/"
}
},
"suggest": {
Expand Down
21 changes: 21 additions & 0 deletions config/Migrations/20201110234846_AddCollectionColumn.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);

use Migrations\AbstractMigration;

class AddCollectionColumn extends AbstractMigration
{
/**
* Change Method.
*
* More information on this method is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
* @return void
*/
public function change()
{
$this->table('file_storage')
->addColumn('collection', 'string', ['length' => 128, 'null' => true, 'default' => null])
->update();
}
}
102 changes: 0 additions & 102 deletions docs/Documentation/Getting-a-File-Path-and-URL.md

This file was deleted.

134 changes: 1 addition & 133 deletions docs/Documentation/How-To-Use.md
Original file line number Diff line number Diff line change
@@ -1,136 +1,4 @@
How to Use It
=============

Before you continue to read this page it is recommended that you have read about [the Storage Manager](The-Storage-Manager.md) before.

The following text is going to describe two ways to store a file. Which of both you choose depends at the end on your use case but it is recommended to use the events because they automate the whole process much more.

The basic idea of this plugin is that files are always handled as separate entities and are associated to other models. The reason for that is simple. A file has multiple properties like size, mime type and other entities in the system can have more than one file for example. It is considered as *bad* practice to store lots of file paths as reference in a table together with other data.

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.

Preparing the File Upload
-------------------------

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

For example you have a `reports` table and want to save a PDF to it, you would then create an association like:

```php
public function initialize(array $config)
{
parent::initialize($config);
$this->table('reports');

$this->hasOne('PdfFiles', [
'className' => 'Burzum/FileStorage.PdfFiles',
'foreignKey' => 'foreign_key',
'conditions' => [
'PdfFiles.model' => 'Reports'
]
]);
}
```

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

```php
echo $this->Form->create($report, ['type' => 'file']);
echo $this->Form->input('title');
echo $this->Form->file('pdf_files.file'); // Pay attention here!
echo $this->Form->input('description');
echo $this->Form->submit(__('Submit'));
echo $this->Form->end();
```

[Make sure your form is using the right HTTP method](http://book.cakephp.org/3.0/en/views/helpers/form.html#changing-the-http-method-for-a-form)!

Store an uploaded file using Events
-----------------------------------

The **FileStorage** plugin comes with a class that acts just as a listener to some of the events in this plugin. Take a look at [ImageProcessingListener.php](../../src/Event/ImageProcessingListener.php).

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

* ImageVersion.createVersion
* ImageVersion.removeVersion
* ImageStorage.beforeSave
* ImageStorage.afterSave
* ImageStorage.beforeDelete
* ImageStorage.afterDelete

Events triggered in the `FileStorage` model:

* FileStorage.beforeSave
* FileStorage.afterSave
* FileStorage.afterDelete

Event Listeners
---------------

See [this page](Included-Event-Listeners.md) for the event listeners that are included in the plugin.


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

You'll have to customize it a little but its just a matter for a few lines.

Note the Listener expects a request data key `file` present in the form, so use `echo $this->Form->input('file');` to allow the Marshaller pick the right data from the uploaded file.

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

```php
public function add() {
$entity = $this->newEntity($postData);
$saved = $this->save($entity);
if ($saved) {
$key = 'your-file-name';
if (StorageManager::get('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));
}
}
return $entity;
}
```

Later, when you want to delete the file, for example in the beforeDelete() or afterDelete() callback of your Report model, you'll know the adapter you have used to store the attached PdfFile and can get an instance of this adapter configuration using the StorageManager. By having the path or key available you can then simply call:

```php
StorageManager::get($data['PdfFile']['adapter'])->delete($data['PdfFile']['path']);
```

Insted of doing all of this in the table object that has the files associated to it you can also simply extend the FileStorage table from the plugin and add your storage logic there and use that table for your association.

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 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.
TO BE DONE
2 changes: 1 addition & 1 deletion docs/Documentation/How-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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.
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 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](../../src/Storage/Listener) 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](Path-Builders.md) class.

Expand Down
24 changes: 0 additions & 24 deletions docs/Documentation/List-of-included-Adapters.md

This file was deleted.

10 changes: 0 additions & 10 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
# Home

The **File Storage** plugin is giving you the possibility to 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.

[See this list of included storage adapters.](Docs/Documentation/List-of-included-Adapters.md)

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 were. You can always write your own adapter or extend and overload existing ones.

## Documentation

* [Installation](Documentation/Installation.md)
* [How it works](Documentation/How-it-works.md)
* [How to Use it](Documentation/How-To-Use.md)
* [Migrating from File Storage v1 to v2](Migrating-from-File-Storage-v1-to-v2.md)
* [The Storage Manager](Documentation/The-Storage-Manager.md)
* [Included Event Listeners](Documentation/Included-Event-Listeners.md)
* [Path Builders](Documentation/Path-Builders.md)
* [Getting a file path and URL](Documentation/Getting-a-File-Path-and-URL.md)
* Image processing
* [Image Storage and Versioning](Documentation/Image-Storage-And-Versioning.md)
Expand Down
Loading