Memo on how to use fuelphp file upload
Hello.
I'm Mandai, in charge of Wild on the development team.
Simply uploading files with fuelphp is very easy, and if you copy and paste the code from the document, it will mostly work.
However, there were many things that I didn't know how to do, so I summarized them.
Preparing for file upload (HTML + CSS + JS)
Before implementing file upload, prepare front-side materials such as HTML.
Let's start with HTML.
It's an input tag inside the 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">not selected</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; }
Last but not least is 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'; } }); })();
When you display this in a browser, it looks like the following. Chrome is recommended.
The reason is obvious when you see it.
There are two file upload buttons.
Pressing either button will work.
The actual product is the one on the left, and the one on the right is a copy using a label tag.
I just wanted to do it.
Form parts display differently between browsers, and I also want to make the file upload button itself match the design, so this trick is useful.
The behavior and appearance are roughly matched using CSS + JS, and this time I made it resemble a chrome form component.
If you add "display: none" to the input tag in CSS, it will behave no differently than a normal form.
(Commented out)
The important thing is not to forget the enctype attribute in the form tag.
The front preparation is now complete.
Implementation of fuelphp (preparation)
Before implementation, we will make various settings and prepare the model.
Since fuelphp is configuration-first, it is possible to greatly change its behavior with the config file.
First, when uploading, prepare a config file for upload.
The source config file is located in "fuel/core/config/upload.php", so copy it under "fuel/app/config" and change the settings.
The required setting item is path.
As for the save destination, if you set it below public, the download mechanism will be easier, but if you know the URL of a PDF with personal information, you can download it as much as you want, so it is best to set it below public. It's dangerous.
If you need to control access, create and specify a directory such as files in the hierarchy above public.
In that case, there will be no way to access it externally and you will not be able to download it, so the overhead will be a concern, but we will deal with this by creating a separate controller to create the download URL.
This area is a little different from the file upload, so I'd like to write about it on another occasion.
This time, create a files directory at the same level as public (directly under the project root) and save the file.
Also, for the file name to be saved, I set randomize to true in the config file and used a 32-character MD5 hash.
The modified config/upload.php now looks like the following.
return array( 'path' => '/var/www/html/test/files', 'randomize' => true, );
Next, create the model.
Since a hash value is added to the file name to be saved, the original file name is no longer known, so its main role is to save it.
The SQL statement to create the uploads table for storage 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 oil command like the one below.
php /path/to/oil r fromdb:model uploads
Also, since we will be using an ORM package, we will need to modify the always_load item in config.php.
If you uncomment the packages part, it will be loaded automatically.
Implementation of fuelphp (controller)
We will implement the behavior of the controller when data is posted.
The source code written in the controller is now as follows.
function post_index() { $data = []; $this->template->title = 'test'; /* * As a result of randomize processing, 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 to change the file name to only the hash value before saving */ 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 "auto_process" is set to true in upload.php in config (default), Upload::process() does not need to be written explicitly.
the documentation , if you execute Upload::process() when 'auto_process' => true, Upload::process() will be executed twice.
You can overwrite the config with Upload::process(), so if the file upload process is described in multiple places, it seems preferable to set auto_process to false and execute Upload::process() each time. .
The Upload::register() method is used to interrupt processing before saving the file.
The first argument should be one of 'validate', 'before', and 'after'.
This time, I chose before because I want to change the file name and save path before saving.
If randomize is set to true in config/upload.php, the file name will be a 32-character hash value, but the original extension will be attached.
In order to completely unify file names with 32 characters, we have added a process to change the file name to just a hash value.
Also, regarding the save destination, a directory is created using the first and second characters of the hash value and reset as the path.
This is not particularly necessary if the number of files to be uploaded is small, but if the number of files to be uploaded is large, or tens of thousands of files are stored, the read/write response will drop significantly.
As a response to this, we have added processing to distribute the number of files in one directory.
$file, which is an argument in the closure in Upload::register(), is passed the class written in fuel/vendor/fuelphp/upload/src/File.php, and this class can be accessed from the outside. There seems to be no way to do that.
If you edit this class 'before' (before saving), you can manipulate the save path and files.
If you dump $file, it will contain 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 with Upload::get_files() is only the container part of Fuel\Upload\File, not the above class.
Also, even if you change the information in Upload::get_files(), the actual file information cannot be changed, so it seems that the only way to change the uploaded file information is in the before event.
This area is detailed in the register method section of the documentation
That's it.