Beginners (and not so) developers often have questions about how to work correctly with user-generated content, and specifically with pictures. This topic has many aspects and more than one solution. Here, only one of the possible options will have its own advantages and disadvantages. We also assume that the static and code are stored on a single server, and the files are loaded one by one.
Tasks solved by the system:
- convenient file downloads;
- the possibility of asynchronous processing of images;
- easy work with previews;
- separation of configuration from execution.
Go')
First, create a File table in the database with the following fields: id, name, size, width, height, is_deleted, is_ready, updated, created.
When loading a file, first of all, an entry will be created in the File table. Further, based on id, the path will be calculated by which it should be saved.
$number = sprintf('%08d', $id);
$uri = '/' .
substr($number, 0, 2) . '/' .
substr($number, 2, 2) . '/' .
substr($number, 4, 2) . '/' .
$number;
At the output we get something like this: / 00/47/31/00473161
upd1 Arkadiy_Kulev pointed out that it is better to limit the depth of directories to two levels.
upd2 Indeed, it is more correct to make a path in the reverse order for a better distribution of pictures into folders. Ie, we get the URL / 161/473/00473161. Thank you
atdWhat is it done for?
- file_id will be a field in the tables of entities that need images ($ user-> avatar_file_id). This approach allows you to determine the full path to the image on the basis of a single file_id without additional requests to the database.
- We get a unified way to work with all pictures, both user and downloadable operators through the admin area.
- A folder structure is created on the disk in such a way that there will be no more than 100 subfolders or files in one folder. Although this number can be increased to 1000 folders, but with a larger number of difficulties may arise in working with FS.
- The file table helps with asynchronous work with files. If we suddenly need to convert all the images into another format or perform any other manipulations, then we can use the is_ready field (or add our own field) to determine the images that we have already processed and which remains to be processed.
- Thanks to the is_deleted field, deleting images can be done asynchronously. The code will just have to set true (it is more convenient to implement on triggers), and there will be a special collector to delete (of course you need to write it). This can be implemented on triggers.
As can be seen from the example β00/47/31/00473161β, the number of possible downloads of pictures, with this approach, is limited to a billion. Naturally it is easy to change at the implementation stage.
To implement the functionality described above, you need to create the Image_Manager class with the receive method. This class will be engaged in creating a record in the database and moving the file to the appropriate directory in the file system, as well as creating the missing directories.
...
if ($form->isValid()) {
// , Image_Manager
// $_FILE .
...
$im = new Image_Manager($options); //
// 'avatar' ,
$file_id = $im->receive('avatar'); //
$user->avatar_file_id = $file_id;
$user->save()
}
We saved the original image and gave it to the user id. But it still needs to be processed, and it is possible to create several preview sizes for display on the site. Then you can go in several ways, create images at once or use lazy load. The second method also has several options for development and is beyond the scope of this article (At the end of the article a list of links is given, where an example of a possible implementation of this method is given). We will go the first way. And it is for this purpose that the $ options array is passed to the image_manager constructor, and the βavatarβ string is passed to the recive method.
In order to create thumbnails, you need a configuration file describing the types of images uploaded to the site and their parameters. For example (using the format Zend_Config_Ini):
...
[avatar] ; big, medium small
resize.big.OutputFileFormat = jpg
resize.big.keepFrame = true
resize.big.backgroundColor = 240.240.240
resize.big.width = 236
resize.big.height = 177
resize.medium.keepFrame = true
resize.medium.backgroundColor = 240.240.240
resize.medium.width = 144
resize.medium.height = 108
resize.small.keepFrame = true
resize.small.width = 72
resize.small.height = 54
resize.small.roundCorners = false
[user_album_photo]
...
It is this configuration file that is passed to the Image_Manager constructor. By calling the recive method, we specify a section of the configuration file and in fact we determine how this picture will be processed and how many previews will be created. To process images, it is desirable to have a separate library with which the Image_Manager interacts within itself.
To save the preview, use the same storage format of the source file β00/47/31/00473161β, but at the end a special hash is added, calculated based on the parameters of this preview, as a result the path will be something like this β/ 00/47/31 / 00473161X2280688952 .jpg "(again, this is just an example, you can do it differently). This hash helps determine the existence of a preview for images whose parameters have changed and, if there is no, generate an image upon request.
It remains to deal with the conclusion. The simplest option would be to write a special image helper, which will work like a url helper:
// url
$this->image($user->avatar_file_id, 'small', 'avatar')
The helper image refers to an object (possibly Image_Manager), which knows how to build a path to the current image, taking into account its parameters, section, and the name of the preview. If the pictures are on a separate host, then it substitutes it. In the absence of the necessary preview, the helper can generate it (controversially, with a large number of missing images, memory or timeout may end) or give way to a special stub for this type of file.
As a result, we got a convenient configuration file, file download in one line and a unified way to work with all site images, including user ones.
Related Links:
http://habrahabr.ru/blogs/nginx/77873/ - very valuable comments here
http://habrahabr.ru/blogs/nginx/94435/http://ru.wikipedia.org/wiki/WebDAV - when moving static to a separate server (s)