承接 besnovatyj/yii2-cms-images 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

besnovatyj/yii2-cms-images

Composer 安装命令:

composer require besnovatyj/yii2-cms-images

包简介

Переиспользуемый пакет управления изображениями для Yii2 CMS модулей

README 文档

README

Пакет для управления изображениями в модулях Yii2 CMS. Предоставляет базовый AR-класс, standalone Yii2 actions и AJAX-виджет загрузки — чтобы любой модуль получил полноценное управление изображениями с минимальным кодом.

Возможности

  • BaseImage — абстрактный ActiveRecord с настроенным UploadBehavior (оригиналы + миниатюры по профилям)
  • 5 standalone actions — загрузка, удаление, список, сортировка, главное изображение
  • AJAX-виджет — drag-and-drop загрузка, сортировка, превью, параллельные загрузки
  • Pessimistic lock — защита от race condition при параллельной загрузке (через ImageOwnerInterface)
  • Автоматическое управление main_image_id — первое изображение становится главным автоматически

Установка

composer.json вашего модуля:

"require": {
  "besnovatyj/yii2-cms-images": "1.0.0"
}

Быстрый старт

1. Создать класс изображения

// src/entities/ArticleImage.php
namespace Besnovatyj\Article\entities;

use Besnovatyj\Images\base\BaseImage;

class ArticleImage extends BaseImage
{
    protected static function getParentAttribute(): string
    {
        return 'article_id'; // FK-атрибут в таблице изображений
    }

    protected static function getStorageName(): string
    {
        return 'Article'; // поддиректория в @static/origin/ и @static/cache/
    }

    protected static function getThumbProfiles(): array
    {
        return [
            'admin' => ['width' => 70,  'height' => 100],
            'thumb' => ['width' => 640, 'height' => 480],
        ];
    }

    public static function tableName(): string
    {
        return '{{%article_images}}';
    }
}

BaseImage автоматически настраивает пути к файлам:

@static/origin/Article/{article_id}/{image_id}.{ext}
@static/cache/Article/{article_id}/{profile}_{image_id}.{ext}

2. Создать ImageOwner-адаптер

// src/image/ArticleImageOwner.php
namespace Besnovatyj\Article\image;

use Besnovatyj\Article\entities\Article;
use Besnovatyj\Article\repositories\ArticleRepository;
use Besnovatyj\Images\contracts\ImageOwnerInterface;
use Besnovatyj\Images\contracts\NullImageOwnerTrait;

class ArticleImageOwner implements ImageOwnerInterface
{
    use NullImageOwnerTrait; // lockOwner/refreshOwner — no-op (если не нужен pessimistic lock)

    public function __construct(
        private readonly Article           $article,
        private readonly ArticleRepository $repository,
    ) {}

    public function getOwnerId(): int          { return $this->article->id; }
    public function getOwnedImages(): array    { return $this->article->images; }
    public function getMainImageId(): ?int     { return $this->article->main_image_id ?: null; }
    public function setMainImageId(?int $id): void { $this->article->setMainImage($id); }
    public function saveOwner(): void          { $this->repository->save($this->article); }
}

3. Подключить actions в контроллер

// src/controllers/backend/ArticleController.php
use Besnovatyj\Article\entities\ArticleImage;
use Besnovatyj\Article\image\ArticleImageOwner;
use Besnovatyj\Images\helpers\ImageActionsMap;

class ArticleController extends Controller
{
    public function actions(): array
    {
        return ImageActionsMap::get(
            ArticleImage::class,
            fn(int $id) => new ArticleImageOwner($this->repo->get($id), $this->repo),
        );
    }

    public function behaviors(): array
    {
        return [
            'verbs' => [
                'class'   => VerbFilter::class,
                'actions' => [
                    'add-image'      => ['POST'],
                    'delete-image'   => ['POST'],
                    'get-images'     => ['POST'],
                    'set-main-image' => ['POST'],
                    'set-new-sort'   => ['POST'],
                ],
            ],
        ];
    }
    // ...
}

4. Добавить виджет в view

// views/backend/article/view.php
use Besnovatyj\Images\widgets\upload\Widget;
use yii\helpers\Url;

<?= Widget::widget([
    'ownerId'   => $article->id,
    'endpoints' => [
        'getImages'    => Url::to(['/Article/backend/article/get-images'], true),
        'setNewSort'   => Url::to(['/Article/backend/article/set-new-sort'], true),
        'upload'       => Url::to(['/Article/backend/article/add-image'], true),
        'deleteImage'  => Url::to(['/Article/backend/article/delete-image'], true),
        'setMainImage' => Url::to(['/Article/backend/article/set-main-image'], true),
    ],
]) ?>

Pessimistic lock (для модулей с параллельной загрузкой)

Если несколько файлов могут загружаться одновременно и у родительской сущности есть main_image_id, нужен pessimistic lock. Иначе несколько запросов одновременно увидят main_image_id = null и попытаются его установить → FK constraint violation.

Пример (см. GalleryImageOwner в yii2-cms-gallery):

class GalleryImageOwner implements ImageOwnerInterface
{
    // НЕ используем NullImageOwnerTrait — реализуем lock самостоятельно

    public function lockOwner(): void
    {
        $this->gallery->lock(); // PessimisticLockBehavior — SELECT FOR UPDATE
    }

    public function refreshOwner(): void
    {
        $this->gallery->refresh(); // получаем актуальные данные после lock
    }
    // ...
}

UploadImageAction всегда вызывает lockOwner()refreshOwner() внутри транзакции. Для модулей без lock эти методы — no-op через NullImageOwnerTrait.

Структура пакета

src/
  base/
    BaseImage.php                   # Абстрактный AR с UploadBehavior
  contracts/
    ImageOwnerInterface.php         # Контракт адаптера владельца
    NullImageOwnerTrait.php         # No-op реализации lock/refresh
  forms/
    UploadImageForm.php             # Форма загрузки (formName = 'AddImageForm')
  actions/
    ActionTrait.php                 # isAjax(), errorResponse()
    UploadImageAction.php           # POST add-image
    DeleteImageAction.php           # POST delete-image
    GetImagesAction.php             # POST get-images
    SetMainImageAction.php          # POST set-main-image
    SetNewSortAction.php            # POST set-new-sort
  helpers/
    ImageActionsMap.php             # ::get() — все 5 actions одним вызовом
  widgets/
    upload/
      Widget.php                    # Yii2 виджет (yii\base\Widget)
      assets/
        Assets.php                  # AssetBundle
        media/js/                   # TypeScript источники + собранный dist/index.js

API Actions

Все actions принимают запросы с заголовком X-Requested-With' === 'XMLHttpRequest.

Action POST-параметры Описание
add-image AddImageForm[id], AddImageForm[file] Загрузить изображение
delete-image DeleteImageForm[id], DeleteImageForm[imageId] Удалить изображение
get-images GetImagesForm[id] Получить список изображений
set-main-image SetMainImageForm[id], SetMainImageForm[imageId] Установить главное
set-new-sort SetNewSortForm[id], SetNewSortForm[sortOrder] (JSON) Обновить порядок

Формат ответа:

{
    "status": "success"
}
{
    "status": "error",
    "message": "...",
    "data": {
        "message": "..."
    }
}

get-images при успехе возвращает:

{
    "status": "success",
    "data": {
        "1": {
            "kind": "server",
            "id": 1,
            "sort": 0,
            "fileName": "photo.jpg",
            "previewUrl": "...",
            "srcUrl": "...",
            "isMain": true
        }
    }
}

BaseImage: параметры конфигурации

Метод Возвращает Описание
getParentAttribute() string Имя FK-атрибута (gallery_id, person_id)
getStorageName() string Поддиректория хранилища (Gallery, Person)
getThumbProfiles() array Профили миниатюр для UploadBehavior
tableName() string Имя таблицы БД

Публичные методы:

BaseImage::make(int $parentId, UploadedFile $file): static  // фабричный метод
BaseImage::getParentAttributeName(): string                 // для Actions
$image->setSort(int $sort): void
$image->isIdEqualTo(int $id): bool
$image->getParentId(): int

ImageActionsMap: параметры

ImageActionsMap::get(
    string $imageClass,        // FQCN потомка BaseImage
    callable $ownerResolver,   // fn(int $id): ImageOwnerInterface
    string $previewProfile,    // профиль миниатюры для get-images (по умолчанию 'thumb')
): array

Пример использования в gallery (с pessimistic lock)

yii2-cms-gallery — эталонная реализация с pessimistic lock:

  • src/entities/gallery/Image.php — extends BaseImage
  • src/image/GalleryImageOwner.php — реализует lock через PessimisticLockBehavior
  • src/controllers/backend/GalleryController.php — подключает через ImageActionsMap

Требования

  • PHP >= 8.4
  • yiisoft/yii2 ~2.0.0
  • yiisoft/yii2-bootstrap5 ~2.0.0
  • besnovatyj/yii2-cms-uploadBaseImage конфигурирует Besnovatyj\Upload\heap\UploadBehavior (оригиналы + миниатюры через ThumbnailProfile)

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: TypeScript

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-06

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固