Привет, друзья. Хочу сегодня рассказать о том, как можно легко загружать/получать/удалять свои изображения в Laravel для любого типа контента. Специально для удобства работы с изображениями я написал свой пакет UploadImage. Но обо всем по порядку.
Итак, как вам известно, не один сайт не может обойтись без использования изображений. Это аксиома. Поэтому при разработке веб проекта, программист обязательно столкнется с необходимостью сохранять изображения на диск и получать изображения с диска.
По большому счету, Ларавел дает очень удобный инструментарий для этих целей. Но вот каким образом сделать автоматизированную систему сохранения изображений, в зависимости от разного типа контента, вот это уже большой вопрос. Конечно же в природе есть ряд решений для Ларавел, которые помогают в вопросе сохранения изображений, но по ряду причин они меня не устроили. Поэтому я решил написать свой "велосипед" - пакет UploadImage. Установить его можно через composer. Вот ссылка на пакет:
https://github.com/kirill-dan/uploadimageЯ обожаю решения из коробки, не люблю полуфабрикатов, которые нужно потом допиливать напильником. Поэтому я и стараюсь делать максимально коробочное решение.
Для работы с изображениями я выдвинул ряд обязательных требований.
Во первых, я решил в базе данных сохранять только название файла, без пути к этому файлу. Это дает возможность в любой момент перенести ваши, уже сохраненные изображения, куда угодно. Хоть в облако, хоть на другой сервер. Прописать путь к базовому хранилищу можно в едином файле настроек. Такой подход никогда не побьет ваши ссылки, а шаблоны всегда будут знать от куда подгружать изображения.
Следующий момент - это возможность располагать изображения автоматически в разные папки, в зависимости от типа контента. Для этого я применил простой прием. В своем контроллере вы можете вызывать метод сохранения/загрузки изображения, передавая в него, помимо самого файла изображения, еще и название модели. Например,
// Upload and save image.
try {
// Upload and save image.
$input['image'] = UploadImage::upload($file, 'post', $watermark)->getImageName();
} catch (UploadImageException $e) {
return back()->withInput()->withErrors(['image', $e->getMessage()]);
}
В этом случае из файла настроек будет получен путь, куда нужно сохранять изображение. Изображение будет проверено и сохранено в папку 'posts' В конец текстовой строки всегда дописывается символ s. Как это, например, происходит с миграциями и таблицами в базе данных. При чем, произойдет ряд проверок изображения, согласно настройкам. Например, чтобы файл был шириной не менее 500px. В папке 'posts' будет создана папка для оригинального изображения: 'original'. Именно в ней и будет сохранено изображение под случайным именем. И это имя будет возвращено методом одним из значений массива. Как это видно из кода.
Таким образом, изображения для разного контента будут хранится в разных директориях. Также можно в настройках включить функцию миниатюр. И указать какого размера нужно делать превьюшки. В этом случае будут созданы миниатюры и помещены рядом с папкой 'original' в свои каталоги, которые будут названы согласно размеру превьюшки с префиксом 'w', например 'w300'.
Еще можно в настройках включить использование водяных знаков и прописать к ним свои пути. Тогда водяной знак будет автоматически ставиться на оригинальные изображения. Для нанесения водфного знака есть третий параметр $watermark (по умолчанию false). В нем можно указать
true, для наклеивания водяного знака на ваше изображение. Если эту
переменную вообще не передавать, то на фото не будет наклеен водяной
знак.
Я специально изменил систему наклеивания ватермарка, чтобы сделать этот процесс более гибким. Может в одном типе контента нужны ватермарки, а в другом не нужны.

А если вы еще видеоролики храните на сайте, например из Ютуб, то наверняка вы захотите на картинку в тизере наложить изображение видеоплеера:

Для этого вам нужно передать в метод четвертый параметр для наложения видео, который равен true.
// Upload and save image.
try {
// Upload and save image.
$input['image'] = UploadImage::upload($file, 'post', false, $video)->getImageName();
} catch (UploadImageException $e) {
return back()->withInput()->withErrors(['image', $e->getMessage()]);
}
Получить же изображение с диска очень просто. Нужно вызвать метод загрузки и передать в него строку своей модели:
$path = UploadImage::load('post');
Таким образом вы получите путь к папке, где хранится изображение, а само имя файла у вас хранится в базе. Если нужна миниатюра, то передайте в метод вторым параметром ширину миниатюры, просто цифрой.
Если же вам необходимо удалить из базы вашу запись, для которой создано изображение, то это тоже очень легко сделать:
UploadImage::delete($post->image, 'post');
Нужно передать в метод имя файла и название модели. Тогда алгоритм удалит и оригинальное изображение и все миниатюры, которые были для него сделаны.
По мимо всего прочего, очень хочется, чтобы при начальном заполнении формы была возможность увидеть загруженное изображение. Например, на вашем сайте есть возможность пользователям добавлять свои новости. Пользователь начинает заполнять форму добавления новости, выбирает фотографию, она загружается на диск. Пользователю сразу же показывается превьюшка, еще до того, как он закончил заполнение формы.
Для того, чтобы пользователю отобразить превьюшку, достаточно в шаблоне обернуть ваше поле загрузки изображения следующим образом:
<div class="image-preview-block">
<div class="image-preview-image">div>
{!! Form::file('image', ['class' => 'image-preview-input']) !!}
div>
Больше ни о чем думать не нужно все произойдет автоматически.
Но есть один момент, достаточно любопытный. Представим, что по каким-то причинам, пользователь решил не создавать новость и бросит эту затею. Просто закрыл страницу и все. При этом изображение уже было загружено на диск. Что же делать? Теперь это изображение будет похоронено в виде мусора.
Я решил эту проблему достаточно просто. Когда пользователь загружает изображение, то мой функционал его получает от пользователя, сохраняет на диск, потом получает из диска и перекодирует в текстовый формат Base64 и отдает его в виде превьюшки назад пользователю. А оригинальное изображение удаляет с диска. Вот и все. Если пользователь не засабмитит форму, то ничего страшного не произойдет. А если засабмитит, то изображение опять загрузится на диск.В новом релизе заменен алгоритм. Теперь превью изображения загружается средствами браузера. Единственное что будет получено от сервера, то это только настройка для ширины превьюшки.
Но и это еще не все! Самое вкусное я оставил на закуску. Как нельзя представить современный сайт без изображений, так и нельзя, на сегодняшний день, представить сайт без WYSIWYG редактора. Никто, в здравом уме, не пишет контент в простом текстовом поле. И просто огромное количество пользователей знает лично, какая головная боль вставлять изображения в текст в самом редакторе! Для разных редакторов придуманы десятки всевозможных загрузчиков изображений, как платных, так и бесплатных. Я видел их все, или почти все. И мое личное мнение - это просто КОШМАР!!!
Я всегда раньше мечтал о том, чтобы просто нажать на кнопку вставить изображение прямо в редакторе, выбрал файл на компьютере, и изображение просто вставилось в мой текст, без вообще каких-либо дополнительных телодвижений. И еще мечтал о том, чтобы при удалении статьи, куда вставилось много изображений из редактора, ненужно ничего было бы делать. Все фотографии автоматически бы удалились с диска вместе со статьей. Ах мечты-мечты. Но хватит мечтать. Я это взял, да и сделал!
В технической документации к пакету вы найдете пример, как это можно сделать. Но суть в чем. В настройках в вашем редакторе вы указываете, какая JS функция должна загружать изображения, а какая удалять. После этого, нажав на кнопку загрузки изображения, оно будет загружено в специальную папку, которую можно настроить в файле конфигурации. Если включены водяные знаки, то на изображение он будет нанесен, а изображение будет масштабировано и сжато, согласно настроек в конфиге.
И когда вы передаете изображение из текстового редактора, то вы можете
передать сразу массив изображений. Все они автоматически будут загружены и
возвращены в редактор.
Когда вы находитесь в редакторе текста и нажмете на кнопку удаления изображения, то обработчик в финале вызовет метод, который удалит изображение с диска.
Все изображения в вашей статье будут содержать только относительные ссылки, поэтому переезд с сервера на сервер не побьет их. Если же вы захотите удалить статью и все изображения в ней, то вам нужно всего-навсего вызвать следующий метод:
UploadImage::deleteBody($post->body);
Вы просто передаете в метод текст, где содержаться ссылки на ваши фотографии или изображения. Метод распарсит весь текст, найдет только относительные изображения и удалит их с вашего диска. Легко и просто :)
Да, еще момент. Вылетело из головы. В метод сохранения изображений, вы можете передать не только само изображение, но и ссылку на изображение, которое хранится на каком-то сайте. Метод автоматически распознает что вы передаете. Тогда изображение будет закачено и превращено в обычное изображение на вашем диске.
Вот, в принципе, и все. Кому поможет мое решение, я буду очень рад. Лично мне оно теперь очень сильно сохраняет и время и нервы. Если есть предложения по улучшению и исправлению, то пишите в комментариях. До скорой встречи.
Комментарии (46):
{
if($size)
{
$this->thumbnails=$size;
}
Если же вам необходимо удалить из базы вашу запись, для которой создано изображение, то это тоже очень легко сделать:
UploadImage::delete($post->image, 'post');
Нужно передать в метод имя файла и название модели. Тогда алгоритм удалит и оригинальное изображение и все миниатюры, которые были для него сделаны.
Удаляется из original, а миниатюра остается, пробовал UploadImage::delete($user->image, 'user', '250'); , так же не принесло результата. Что я делаю не так ?
$images[] = UploadImage::upload($img, 'post', $watermark, $video, $thumbnail)->getImageName();
}
1 verbose cli [ '/usr/bin/node', '/usr/bin/npm', 'run', 'dev' ]
2 info using npm@3.10.10
3 info using node@v6.11.4
4 verbose run-script [ 'predev', 'dev', 'postdev' ]
5 info lifecycle @~predev: @
6 silly lifecycle @~predev: no script for predev, continuing
7 info lifecycle @~dev: @
8 verbose lifecycle @~dev: unsafe-perm in lifecycle true
9 verbose lifecycle @~dev: PATH: /usr/lib/node_modules/npm/bin/node-gyp-bin:/home/vagrant/code/blog/node_modules/.bin:/home/vagrant/.composer/vendor/bin:/home/
10 verbose lifecycle @~dev: CWD: /home/vagrant/code/blog
11 silly lifecycle @~dev: Args: [ '-c', 'npm run development' ]
12 silly lifecycle @~dev: Returned: code: 1 signal: null
13 info lifecycle @~dev: Failed to exec dev script
14 verbose stack Error: @ dev: `npm run development`
14 verbose stack Exit status 1
14 verbose stack at EventEmitter. (/usr/lib/node_modules/npm/lib/utils/lifecycle.js:255:16)
14 verbose stack at emitTwo (events.js:106:13)
14 verbose stack at EventEmitter.emit (events.js:191:7)
14 verbose stack at ChildProcess. (/usr/lib/node_modules/npm/lib/utils/spawn.js:40:14)
14 verbose stack at emitTwo (events.js:106:13)
14 verbose stack at ChildProcess.emit (events.js:191:7)
14 verbose stack at maybeClose (internal/child_process.js:920:16)
14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:230:5)
15 verbose pkgid @
16 verbose cwd /home/vagrant/code/blog
17 error Linux 4.4.0-96-generic
18 error argv "/usr/bin/node" "/usr/bin/npm" "run" "dev"
19 error node v6.11.4
20 error npm v3.10.10
21 error code ELIFECYCLE
22 error @ dev: `npm run development`
22 error Exit status 1
23 error Failed at the @ dev script 'npm run development'.
23 error Make sure you have the latest version of node.js and npm installed.
23 error If you do, this is most likely a problem with the package,
23 error not with npm itself.
23 error Tell the author that this fails on your system:
23 error npm run development
23 error You can get information on how to open an issue for this project with:
23 error npm bugs
23 error Or if that isn't available, you can get their info via:
23 error npm owner ls
23 error There is likely additional logging output above.
24 verbose exit [ 1, true ]
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix.sass('resources/assets/sass/app.scss', 'public/css/app.css').version();
mix.js(['resources/assets/js/app.js', 'resources/assets/js/upload_image_preview.js'],
'public/js/all.js').version();
<meta name="csrf-token" content="{{ csrf_token() }}">
95% emitting
ERROR Failed to compile with 1 errors 12:31:21 PM
error in ./resources/assets/js/upload_image_preview.js
Syntax Error: Unexpected token (1:0)
> 1 | <!DOCTYPE html>
| ^ <html lang="en">
2 | <head>
Старайтесь использовать не пути, а имена роутов. Это лучше тем, что если вам нужно будет поменять путь, вы поменяете только у роута, а везде он подхватится уже автоматом.
Мультизагрузку через
я не делал. Я руководствовался другой логикой и принципом. Одна фотка для тизера, как ознакомительная, и все. А мультизагрузка через редактор изображений WYSIWYG. Например, на вход контроллера
можно подать массив с изображениями из JS. Все изображения будут загружены и будет возвращен массив с уже загруженными изображениями. Здесь есть пример: https://cleverman.org/post/38
Своего генератора в настройках нет, но могу попробовать сделать.
Почему я фотки переименовываю. Во первых, чтобы вам не загрузили какой-то вредоносный код, который маскируется под изображение (есть горький опыт). Потом, чтобы точно не было совпадений по имени. Иначе будет перезаписан файл изображения.
Можно ли как-то использовать собственный generateNewName() ?
Может как-то через настройки, мол new_name_generator => 'default' или new_name_generator => 'superpuperSvoyNameGenerator'.
namespace App\Http\Controllers;
use Dan\UploadImage\Exceptions\UploadImageException;
use Illuminate\Http\Request;
use UploadImage;
class UploadController extends Controller
{
public function create() { return view('uploads.create'); }
public function store(Request $request)
{
$file = $request->file('image');
$video = false;
$watermark = false;
// Upload and save image.
try {
// Upload and save image.
$input['image'] = UploadImage::upload($file, 'post', $watermark, $video)->getImageName();
} catch (UploadImageException $e) {
return back()->withInput()->withErrors(['image', $e->getMessage()]);
}
}
}
@section('content')
<h2 class="text-center">Загрузить</h2>
{!! Form::open([ 'url' => 'uploads', 'class' => 'ui form'] ) !!}
<div class="image-preview-block">
<div class="image-preview-image"></div>
{!! Form::file('image', ['class' => 'image-preview-input']) !!}
</div>
{!! Form::submit('Загрузить', ['class' => 'fluid ui blue button']) !!}
{!! Form::close() !!}
@include ('errors.list')
@stop
П.С. Чтобы сделать цитату, нужно выделить кусок теста в комментарии и нажать на кнопку. При наведении мышки выводится подсказка.