FriendsOfCake / cakephp-upload

CakePHP: Handle file uploading sans ridiculous automagic
https://cakephp-upload.readthedocs.io/
MIT License
551 stars 255 forks source link

Access Entity in nameCallback option #417

Closed bobmulder closed 7 years ago

bobmulder commented 7 years ago

Hi there,

I want to change my filename based on some entity values. It's able to use entity related values when configuring the path, but I would like to set its filename.

Any hints about how to do this? An addition would be to add the entity as 3rd parameter in the callback...

Greetz,

Bob

josegonzalez commented 7 years ago

You could override the PathProcessor to not use the Default File Trait and do whatever you'd like instead. The nameCallback option is there as an easy way to get things done.

bobmulder commented 7 years ago

Thanks @josegonzalez, I'll check it out. It's probably worth supporting this by default?

bobmulder commented 7 years ago

For next users:

<?php
namespace App\Core\Upload;

use Cake\ORM\Entity;
use Cake\ORM\Table;
use Cake\Utility\Hash;
use Josegonzalez\Upload\File\Path\ProcessorInterface;
use Josegonzalez\Upload\File\Path\Basepath\DefaultTrait as BasepathTrait;

class PathProcessor implements ProcessorInterface
{
    /**
     * Table instance.
     *
     * @var \Cake\ORM\Table
     */
    protected $table;

    /**
     * Entity instance.
     *
     * @var \Cake\ORM\Entity
     */
    protected $entity;

    /**
     * Array of uploaded data for this field
     *
     * @var array
     */
    protected $data;

    /**
     * Name of field
     *
     * @var string
     */
    protected $field;

    /**
     * Settings for processing a path
     *
     * @var array
     */
    protected $settings;

    /**
     * Constructor
     *
     * @param \Cake\ORM\Table  $table the instance managing the entity
     * @param \Cake\ORM\Entity $entity the entity to construct a path for.
     * @param array            $data the data being submitted for a save
     * @param string           $field the field for which data will be saved
     * @param array            $settings the settings for the current field
     */
    public function __construct(Table $table, Entity $entity, $data, $field, $settings)
    {
        $this->table = $table;
        $this->entity = $entity;
        $this->data = $data;
        $this->field = $field;
        $this->settings = $settings;
    }

    /**
     * Returns the filename for the current field/data combination.
     * If a `nameCallback` is specified in settings, then that callable
     * will be invoked with the current upload data.
     *
     * @return string
     */
    public function filename()
    {
        $processor = Hash::get($this->settings, 'nameCallback', null);
        if (is_callable($processor)) {
            return $processor($this->data, $this->settings, $this->entity);
        }

        return $this->data['name'];
    }

    use BasepathTrait;
}

And in your config:

$this->addBehavior('Josegonzalez/Upload.Upload', [
            'file' => [
                'pathProcessor' => PathProcessor::class,
                'nameCallback' => function ($data, $settings, $entity) {
                    // use $entity here
               }
            ]
        ]);
josegonzalez commented 7 years ago

Want to make a PR to expand it?

bobmulder commented 7 years ago

As soon I got time for that I will! To make it backwards compatible we should detect if the $entity is requested by the developer... Or do you prefer another PathProcessor (which doesn't feel that 'DRY')

josegonzalez commented 7 years ago

We can just cut a minor/major release if need be.

JayWalker512 commented 7 years ago

Bumping this as I too would find this very useful. Naming files currently seems rather arbitrary, I can use a hash of the file or whatever the user supplied (which seems like a bad idea), or maybe something else. Renaming file like $entity->full_name would take care of many use-cases, but it looks like this never made it in to the repo?

josegonzalez commented 7 years ago

You can use {field-value:SOME_FIELD} to grab some value from the entity.

JayWalker512 commented 7 years ago

Thank you for the tip, though that does not seem to be working for me in the path or in the nameCallback. Here is my configuration:

$this->addBehavior('Josegonzalez/Upload.Upload', [
            'photo_path' => [
                'path' => '{DS}webroot{DS}img{DS}sps{DS}{field-name:first_name}{DS}',
                'pathProcessor' => 'Josegonzalez\Upload\File\Path\DefaultProcessor',
                'nameCallback' => function($data, $settings) { 
                    return "{field-name:first_name}.jpg";
                },
            ]
        ]);

When I upload using these settings, I get a file which is literally webroot/img/sps/{field-name:first_name}/{field-name:first_name}.jpg. I would like to be able to use the nameCallback as above as I am only interested in using the field on file name, not the directory name.

If you can confirm that this is a sensible configuration I will try to figure out what's going wrong, or I can put together a minimal project for you to demonstrate the behavior.

josegonzalez commented 7 years ago

I didn't say {field-name:SOME_FIELD}, I said {field-value:SOME_FIELD}. That belongs in your path option, as specified here: https://cakephp-upload.readthedocs.io/en/latest/configuration.html. That assumes you have that field in the data being posted of course.

JayWalker512 commented 7 years ago

Ah yes! What a silly mistake. So then I will use @bobmulder solution to pull a field from the entity in the nameCallback, since {field-value:SOME_FIELD} has no effect there. Thank you for your time!