Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

File uploads

A file upload is a form field through which the visitor sends a picture or a document. Laminas receives the file into a temporary folder, checks it against your rules and moves it wherever you say. Let us add a picture to a post.

Preparation

Create the folder public/uploads — the files will land there. Add a column for the file name to the table:

ALTER TABLE posts ADD image VARCHAR(255) NULL;

In the Post class add the property public ?string $image = null; and the line $this->image = $data['image'] ?? null; in exchangeArray(), and in PostTable::save() add the key 'image' => $post->image to the $data array.

The form field

Add an element in the PostForm constructor:

// module/Blog/src/Form/PostForm.php
$this->add([
    'name' => 'image',
    'type' => Element\File::class,
    'options' => ['label' => 'Picture'],
]);

In the add.phtml view script render it with <?= $this->formRow($form->get('image')) ?>. The enctype="multipart/form-data" attribute, without which the browser does not send the file, is added by the form itself when prepare() is called.

Rules for the file

In getInputFilterSpecification() a file has a special input type — FileInput:

// module/Blog/src/Form/PostForm.php
use Laminas\Filter\File\RenameUpload;
use Laminas\InputFilter\FileInput;
use Laminas\Validator\File\Extension;
use Laminas\Validator\File\Size;
 
'image' => [
    'type' => FileInput::class,
    'required' => false,
    'validators' => [
        ['name' => Size::class, 'options' => ['max' => '2MB']],
        ['name' => Extension::class, 'options' => ['extension' => ['jpg', 'jpeg', 'png']]],
    ],
    'filters' => [
        [
            'name' => RenameUpload::class,
            'options' => [
                'target' => './public/uploads/post',
                'randomize' => true,
                'use_upload_extension' => true,
            ],
        ],
    ],
],

For a FileInput the order is reversed: validators first, then filters. The RenameUpload filter moves the file from the temporary folder to public/uploads. With randomize the name gets a random addition — for example post_64a1f3c2.jpg — so two files with the same name do not overwrite each other.

Handling in the controller

Files arrive separately from the other fields, so they are merged before setData():

// module/Blog/src/Controller/PostController.php
if ($request->isPost()) {
    $form->setData(array_merge_recursive(
        $request->getPost()->toArray(),
        $request->getFiles()->toArray(),
    ));
 
    if ($form->isValid()) {
        $data = $form->getData();
 
        $post = new Post();
        $post->exchangeArray($data);
        $post->image = empty($data['image']['tmp_name']) ? null : basename($data['image']['tmp_name']);
 
        $this->posts->save($post);
 
        return $this->redirect()->toRoute('blog/view', ['id' => $post->id]);
    }
}

After validation $data['image'] is an array describing the file, and its tmp_name is already the new path in public/uploads. Only the file name is saved to the database.

Showing the picture

<!-- module/Blog/view/blog/post/view.phtml -->
<h1><?= $this->escapeHtml($this->post->title) ?></h1>
 
<?php if ($this->post->image !== null): ?>
    <img src="<?= $this->basePath('uploads/' . $this->post->image) ?>" alt="">
<?php endif; ?>

Add a post with a picture through /blog/add — it appears under the heading on the post page.

Common mistake

Never save a file under the name the visitor sent, and never without checking the extension: that is how a shell.php file gets onto the server, to be opened by a link and executed. Always restrict extensions with the Extension validator and give files your own names through RenameUpload with randomize.

Thanks for reading!

Donate to the site — we work hard for you.