We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Upload Image. Model Behavior

Upload Image via Model Behavior.

This is not a issue. I'm just sharing my solution. Maybe it will help beginners

<?php

namespace YourVendorHere\Models\Behavior;

use Phalcon\Mvc\Model\Behavior;
use Phalcon\Mvc\Model\BehaviorInterface;
use Phalcon\Mvc\ModelInterface;
use Phalcon\Mvc\Model\Exception;
use Phalcon\Logger;
use Symfony\Component\Filesystem\Filesystem; // Not necessarily, just convenient

class Imageable extends Behavior implements BehaviorInterface
{
    /**
     * Upload image path
     * @var string
     */
    protected $uploadPath = null;

    /**
     * Model field
     * @var null
     */
    protected $imageField = null;

    /**
     * Old model image
     * @var string
     */
    protected $oldFile = null;

    /**
     * Application logger
     * @var \Phalcon\Logger\Adapter\File
     */
    protected $logger = null;

    /**
     * Filesystem Utils
     * @var \Symfony\Component\Filesystem\Filesystem
     */
    protected $filesystem = null;

    /**
     * Allowed types
     * @var array
     */
    protected $allowedFormats = ['image/jpeg', 'image/png', 'image/gif'];

    public function notify($eventType, ModelInterface $model)
    {
        if (!is_string($eventType)) {
            throw new Exception('Invalid parameter type.');
        }

        // Check if the developer decided to take action here
        if (!$this->mustTakeAction($eventType)) {
            return;
        }

        $options = $this->getOptions($eventType);

        if (is_array($options)) {
            $this->logger = $model->getDI()->get('logger');
            $this->filesystem = new Filesystem;

            $this->setImageField($options, $model)
                 ->setAllowedFormats($options)
                 ->setUploadPath($options)
                 ->processUpload($model);
        }
    }

    protected function setImageField(array $options,  ModelInterface $model)
    {
        if (!isset($options['field']) || !is_string($options['field'])) {
            throw new Exception("The option 'field' is required and it must be string.");
        }

        $this->imageField = $options['field'];
        $this->oldFile = $model->{$this->imageField};

        return $this;
    }

    protected function setAllowedFormats(array $options)
    {
        if (isset($options['allowedFormats']) && is_array($options['allowedFormats'])) {
            $this->allowedFormats = $options['allowedFormats'];
        }

        return $this;
    }

    // Symfony\Component\Filesystem\Filesystem uses here, you can do it otherwise
    protected function setUploadPath(array $options)
    {
        if (!isset($options['uploadPath']) || !is_string($options['uploadPath'])) {
            throw new Exception("The option 'uploadPath' is required and it must be string.");
        }

        $path = $options['uploadPath'];

        if (!$this->filesystem->exists($path)) {
            $this->filesystem->mkdir($path);
        }

        $this->uploadPath = $path;

        return $this;
    }

    protected function processUpload(ModelInterface $model)
    {
        /** @var \Phalcon\Http\Request $request */
        $request = $model->getDI()->getRequest();

        if (true == $request->hasFiles(true)) {
            foreach ($request->getUploadedFiles() as $file) {
                // NOTE!!!
                // Nothing was validated here! Any validations must be are made in a appropriate validator
                if ($file->getKey() != $this->imageField || !in_array($file->getType(), $this->allowedFormats)) {
                    continue;
                }

                $uniqueFileName = time() . '-' . uniqid() . '.' . strtolower($file->getExtension());

                if ($file->moveTo(rtrim($this->uploadPath, '/\\') . DIRECTORY_SEPARATOR . $uniqueFileName)) {
                    $model->writeAttribute($this->imageField, $uniqueFileName);
                    $this->logger->log(Logger::INFO, sprintf(
                        'Success upload file %s into %s', $uniqueFileName, $this->uploadPath
                    ));

                    // Delete old file
                    $this->processDelete();
                }
            }
        }

        return $this;
    }

    // Symfony\Component\Filesystem\Filesystem uses here, you can do it otherwise
    protected function processDelete()
    {
        if ($this->oldFile) {
            $fullPath = rtrim($this->uploadPath, '/\\') . DIRECTORY_SEPARATOR . $this->oldFile;

            try {
                $this->filesystem->remove($fullPath);
                $this->logger->log(Logger::INFO, sprintf('File %s deleted successful.', $fullPath));
            } catch(\Exception $e) {
                $this->logger->log(Logger::ALERT, sprintf(
                    'An error occurred deleting file %s: %s', $fullPath, $e->getMessage()
                ));
            }
        }
    }
}

And just use in model:

namespace YourVendorHere\Models;

use Phalcon\Mvc\Model;
use YourVendorHere\Models\Behavior\Imageable;

class Product extends Model
{
    protected $image;

    public function initialize()
    {
        /** @var \Phalcon\Config $config */
        $config = $this->getDI()->get('config');

        if (!isset($config->media) || !isset($config->media->upload_path) || !is_string($config->media->upload_path)) {
            throw new Exception("The config param '\$config->media->upload_path' is required and it must be string.");
        }

        $this->uploadPath = rtrim($config->media->upload_path, '/\\') . DIRECTORY_SEPARATOR .'product';

        // NOTE: You need validate image (file size, file format, etc.)

        $this->addBehavior(new Imageable([
            'beforeCreate' => [
                'field'      => 'image',
                'uploadPath' => $this->uploadPath,
            ],
            'beforeUpdate' => [
                'field'      => 'image',
                'uploadPath' => $this->uploadPath,
            ],
        ]));
    }

    public function getImage()
    {
        return $this->image;
    }

    public function setImage($image)
    {
        $this->image = $image;

        return $this;
    }
}


58.4k
Accepted
answer

Hey @sergeyklay

This is tip, if so share here https://phalcontip.com