Memo on how to use fuelphp file upload

Hello.
I'm Mandai, in charge of Wild on the development team.

Uploading a file using fuelphp is very easy, and if you simply copy and paste the code from the documentation, it will generally work.
However, there are many things I don't understand about modifying it, so I've put together a guide.


 

Preparing for file upload (HTML + CSS + JS)

Before implementing file upload, prepare the front-end materials such as HTML

Let's start with the HTML.
It's an input tag inside a form tag.

<form id="form" name="form" method="post" enctype="multipart/form-data"><input type="file" name="f" class="hidden" id="f" multiple="multiple"> <label for="f"><span class="btn">File selection</span> <span class="selected" id="f_selected">No selection</span></label></form>

 
Next, CSS

input.hidden { /* display: none;*/ } label > span.btn { display: inline-block; border: 1px solid #999; font: 13.333px arial; height: 21px; line-height: 21px; padding-left: 0.25em; padding-right: 0.25em; border-radius: 2px; background: -webkit-linear-gradient(#fcfcfc, #dcdcdc); background: linear-gradient(#fcfcfc, #dcdcdc); } label > span.btn:active { background: -webkit-linear-gradient(#dcdcdc, #fcfcfc); background: linear-gradient(#dcdcdc, #fcfcfc); } label > span.btn:hover { border: 1px solid #666; } label > span.selected { display: inline-block; font: 13.333px arial; height: 21px; line-height: 21px; } label { -moz-user-select: none; -khtml-user-select: none; -webkit-user-select: none; user-select: none; }

 
Finally, JavaScript

(function(){ var f = document.getElementById('f'); var label_selected = document.getElementById(f.id + '_selected'); f.addEventListener('change', function(){ if (f.files.length == 0){ label_selected.textContent = 'Not selected'; } else if (f.files.length == 1){ label_selected.textContent = f.files[0].name; } else { label_selected.textContent = f.files.length + 'Files'; } }); })();


 If you view this in a browser, it will look like this. I recommend Chrome.
The reason is obvious when you view it.

duplicate_file_upload

So, there are two file upload buttons.
Either button will work.

The actual product is on the left, and the one on the right is a copy that uses the label tag. I
just wanted to do it.

The display of form components differs between browsers, and there is also a desire to make the file upload button itself conform to the design, so this kind of trick is useful.
The behavior and appearance are roughly the same using CSS + JS, and in this case it is similar to the Chrome form component.

If you add "display: none" to the input tag in CSS, it will behave exactly like a normal form.
(It is commented out.)

The important thing is not to forget the enctype attribute in the form tag

The front is now ready

 

Implementing fuelphp (preparation)

Before implementation, we will prepare various settings and models

FuelPHP is configuration-first, so you can change its behavior significantly with the config file.
First, when uploading, prepare a config file for uploading.

The original config file is located in "fuel/core/config/upload.php", so copy it to "fuel/app/config" and change the settings

The required setting is the path.
If you set the save destination to a location under public, the download mechanism will be simple, but it is dangerous to store PDFs containing personal information under public, as they can be downloaded as much as you want if the URL is known.

If you need to control access, create a directory such as files above public and specify that.
In that case, there will be no way to access it from outside and you won't be able to download it, so overhead may be a concern, but you can deal with this by creating a separate controller that creates a download URL.
This is a bit off topic from file uploading, so I hope to write an article about it on another occasion.

This time, we will create a files directory at the same level as public (directly under the project root) and save the files there

In addition, the file name to be saved is set to a 32-character MD5 hash by setting randomize to true in the config file

The modified config/upload.php now looks like this:

return array( 'path' => '/var/www/html/test/files', 'randomize' => true, );


 Next, we create a model.
Its main role is to save the file name, which will be saved with a hash value, making the original file name unknown.

The SQL statement to create the uploads table for saving is as follows:

CREATE TABLE `uploads` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `origin_name` varchar(256) NOT NULL, `file_name` varchar(256) NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`), KEY `idx_file_name` (`file_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 
After executing this SQL, the quickest way is to create a model using the following oil command:

php /path/to/oil r fromdb:model uploads


 Also, since we will be using an ORM package, we will modify the always_load item in config.php.
Uncommenting the packages section will make it load automatically.

 

fuelphp implementation (controller)

We will now implement the behavior of the controller when data is posted

The source code written in the controller is as follows:

function post_index() { $data = []; $this->template->title = 'test'; /* * After randomizing, the file name will be as follows: * [0-9a-f]{32}.[ext] * Since the original file name will be saved in a separate field, we will introduce a process before saving that will make the file name just a hash value */ Upload::register('before', function(&$file) { $file['filename'] = substr($file['filename'], 0, 32); $file['path'] .= $file['filename'][0]. '/'. $file['filename'][1]. '/'; }); if (Upload::is_valid()) { Upload::save(); foreach (Upload::get_files() as $file) { Model_Upload::add($file); } } foreach (Upload::get_errors() as $file) { // Error handling } $this->template->content = View::forge('upload/index', $data); }


 If you set "auto_process" to true in upload.php in the config (default), you don't need to explicitly write Upload::process().
the documentation , when "'auto_process' => true", if you run Upload::process(), it will be run twice.

Since config can be overwritten with Upload::process(), if file upload processing is described in multiple places, it is preferable to set auto_process to false and run Upload::process() each time

The Upload::register() method is used to interrupt the process before saving the file.
The first argument is chosen from 'validate', 'before', or 'after'.
In this case, we selected 'before' because we want to change the file name and save path before saving.

If you set randomize to true in config/upload.php, the file name will be a 32-character hash value, but the original extension will be added.
In order to unify the file name to 32 characters, we have included a process that converts the file name to just the hash value.

Additionally, for the save destination, a directory is created using the first and second characters of the hash value and this is set as the path. This
is not particularly necessary if the number of files being uploaded is small, but if the number of files being uploaded is large, such as tens of thousands of files being stored, the read/write response will drop significantly.
To address this, we have added processing to distribute the number of files within a single directory.

The $file argument in the closure of Upload::register() is the class written in fuel/vendor/fuelphp/upload/src/File.php, and there seems to be no way to access this class from outside.
If you edit this class 'before' (before saving), you will be able to manipulate the save path and file.

If you dump $file, you'll see something like this:

Fuel\Upload\File::__set_state(array( 'container' => array ( 'element' => 'f', 'filename' => 'e9477391cbeefc40e69f7e1bc24868d2', 'name' => 'test.html', 'type' => 'text/html', 'tmp_name' => '/tmp/phpDsDmiL', 'error' => 0, 'size' => 335, 'extension' => 'html', 'basename' => 'test', 'mimetype' => 'text/html', 'path' => '/home/vagrant/workspace/test/files/e/9/', ), 'index' => 0, 'errors' => array ( ), 'config' => array ( 'langCallback' => '\\Upload::lang_callback', 'moveCallback' => NULL, 'max_size' => 0, 'max_length' => 0, 'ext_whitelist' => array ( ), 'ext_blacklist' => array ( ), 'type_whitelist' => array ( ), 'type_blacklist' => array ( ), 'mime_whitelist' => array ( ), 'mime_blacklist' => array ( ), 'prefix' => '', 'suffix' => '', 'extension' => '', 'randomize' => true, 'normalize' => false, 'normalize_separator' => '_', 'change_case' => false, 'path' => '/var/www/html/test/files', 'create_path' => true, 'path_chmod' => 511, 'file_chmod' => 438, 'auto_rename' => true, 'new_name' => false, 'overwrite' => false, ), 'isValidated' => true, 'isValid' => true, 'callbacks' => array ( 'before_validation' => array ( ), 'after_validation' => array ( ), 'before_save' => array ( 0 => Closure::__set_state(array( )), ), 'after_save' => array ( ), ), ))


 It's a bit confusing, but the file information obtained by Upload::get_files() is not the class mentioned above, but only the container part of Fuel\Upload\File.
Also, even if you change the information in Upload::get_files(), you cannot change the actual file information, so it seems that the only way to change the information of the uploaded file is in the before event.

This is explained in more detail in the register method section of the documentation

 
That's it.

If you found this article helpful , please give it a like!
0
Loading...
0 votes, average: 0.00 / 10
5,509
X facebook Hatena Bookmark pocket

The person who wrote this article

About the author

Yoichi Bandai

My main job is developing web APIs for social games, but I'm also fortunate to be able to do a lot of other work, including marketing.
Furthermore, my portrait rights in Beyond are treated as CC0 by him.