Skip to content

Commit

Permalink
Add support for files upload
Browse files Browse the repository at this point in the history
  • Loading branch information
Romain Monteil committed Dec 17, 2018
1 parent cc35898 commit f821357
Show file tree
Hide file tree
Showing 28 changed files with 623 additions and 17 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/public/build/fonts/glyphicons-*
/public/build/images/glyphicons-*
/public/uploads/

###> symfony/framework-bundle ###
/.env.local
Expand Down
6 changes: 6 additions & 0 deletions assets/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,9 @@ body#blog_search .post-metadata {
font-size: 16px;
margin-bottom: 8px;
}

/* Page: 'User edit'
------------------------------------------------------------------------- */
body#user_edit #main form .form-group .thumbnail {
max-width: 150px;
}
2 changes: 1 addition & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ parameters:
# This parameter defines the codes of the locales (languages) enabled in the application
app_locales: en|fr|de|es|cs|nl|ru|uk|ro|pt_BR|pl|it|ja|id|ca|sl|hr|zh_CN|bg|tr
app.notifications.email_sender: anonymous@example.com
app.uploader_directory: '%kernel.project_dir%/public/uploads'

services:
# default configuration for services in *this* file
Expand Down Expand Up @@ -32,3 +33,10 @@ services:
# 'arguments' key and define the arguments just below the service class
App\EventSubscriber\CommentNotificationSubscriber:
$sender: '%app.notifications.email_sender%'

App\EventSubscriber\UploadSubscriber:
tags:
- { name: doctrine.event_subscriber, connection: default }

App\Utils\FileUploader:
$uploadPath: '%app.uploader_directory%'
Binary file modified data/database.sqlite
Binary file not shown.
Binary file modified data/database_test.sqlite
Binary file not shown.
File renamed without changes.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions public/build/entrypoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"js": [
"/build/runtime.6cf710cd.js",
"/build/0.830d5dde.js",
"/build/1.bdd72f15.js",
"/build/1.1c76ba1c.js",
"/build/js/admin.4ca6ad4a.js"
]
},
Expand All @@ -35,7 +35,7 @@
"/build/runtime.6cf710cd.js"
],
"css": [
"/build/css/app.4aa95248.css"
"/build/css/app.38ae95b3.css"
]
},
"css/admin": {
Expand Down
4 changes: 2 additions & 2 deletions public/build/manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"build/0.830d5dde.js": "/build/0.830d5dde.js",
"build/1.bdd72f15.js": "/build/1.bdd72f15.js",
"build/1.1c76ba1c.js": "/build/1.1c76ba1c.js",
"build/2.dbd2ede0.js": "/build/2.dbd2ede0.js",
"build/css/admin.css": "/build/css/admin.1b3ff8e3.css",
"build/css/app.css": "/build/css/app.4aa95248.css",
"build/css/app.css": "/build/css/app.38ae95b3.css",
"build/js/admin.js": "/build/js/admin.4ca6ad4a.js",
"build/js/app.js": "/build/js/app.b0ae906f.js",
"build/js/login.js": "/build/js/login.71306f38.js",
Expand Down
26 changes: 26 additions & 0 deletions src/Annotation/Uploadable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace App\Annotation;

use Doctrine\Common\Annotations\Annotation;

/**
* This file defined an annotation to target a class.
*
* @Annotation
* @Target("CLASS")
*
* @author Romain Monteil <monteil.romain@gmail.com>
*/
class Uploadable
{
}
54 changes: 54 additions & 0 deletions src/Annotation/UploadableField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace App\Annotation;

use Doctrine\Common\Annotations\Annotation\Target;
use Doctrine\Common\Annotations\AnnotationException;

/**
* This file defined an annotation to target a property on a class.
*
* @Annotation
* @Target("PROPERTY")
*
* @author Romain Monteil <monteil.romain@gmail.com>
*/
class UploadableField
{
private $filename;

private $path;

public function __construct(array $options)
{
if (!isset($options['filename'])) {
throw new AnnotationException('Attribute "filename" is required for UploadableField annotation.');
}

if (!isset($options['path'])) {
throw new AnnotationException('Attribute "path" is required for UploadableField annotation.');
}

$this->filename = $options['filename'];
$this->path = $options['path'];
}

public function getFilename(): string
{
return $this->filename;
}

public function getPath(): string
{
return $this->path;
}
}
51 changes: 51 additions & 0 deletions src/Annotation/UploadableReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace App\Annotation;

use Doctrine\Common\Annotations\Reader;

/**
* @author Romain Monteil <monteil.romain@gmail.com>
*/
class UploadableReader
{
private $reader;

public function __construct(Reader $reader)
{
$this->reader = $reader;
}

public function isUploadable($entity): bool
{
$reflection = new \ReflectionClass(\get_class($entity));

return null !== $this->reader->getClassAnnotation($reflection, Uploadable::class);
}

public function getUploadableFields($entity): array
{
$reflection = new \ReflectionClass(\get_class($entity));

$properties = [];
if ($this->isUploadable($entity)) {
foreach ($reflection->getProperties() as $property) {
$propertyAnnotation = $this->reader->getPropertyAnnotation($property, UploadableField::class);
if (null !== $propertyAnnotation) {
$properties[$property->getName()] = $propertyAnnotation;
}
}
}

return $properties;
}
}
28 changes: 28 additions & 0 deletions src/Entity/Interfaces/TimestampableEntityInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace App\Entity\Interfaces;

/**
* This interface allow us to mark an Entity as Timestampable.
*
* @author Romain Monteil <monteil.romain@gmail.com>
*/
interface TimestampableEntityInterface
{
public function getCreatedAt(): ?\DateTimeInterface;

public function setCreatedAt();

public function getUpdatedAt(): ?\DateTimeInterface;

public function setUpdatedAt();
}
58 changes: 57 additions & 1 deletion src/Entity/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@

namespace App\Entity;

use App\Annotation\Uploadable;
use App\Annotation\UploadableField;
use App\Entity\Interfaces\TimestampableEntityInterface;
use App\Entity\Traits\TimestampableEntityTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Assert;

/**
* @ORM\Entity(repositoryClass="App\Repository\PostRepository")
* @ORM\Table(name="symfony_demo_post")
* @ORM\HasLifecycleCallbacks()
* @Uploadable()
*
* Defines the properties of the Post entity to represent the blog posts.
*
Expand All @@ -31,8 +38,10 @@
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class Post
class Post implements TimestampableEntityInterface
{
use TimestampableEntityTrait;

/**
* Use constants to define configuration options that rarely change instead
* of specifying them under parameters section in config/services.yaml file.
Expand Down Expand Up @@ -90,6 +99,27 @@ class Post
*/
private $publishedAt;

/**
* @var string
*
* @ORM\Column(type="text")
*/
private $thumbnail;

/**
* @Assert\Image(
* mimeTypes={
* "image/jpeg",
* "image/png"
* },
* maxSize="1M",
* maxWidth="1000",
* maxHeight="500"
* )
* @UploadableField(filename="thumbnail", path="posts")
*/
private $thumbnailFile;

/**
* @var User
*
Expand Down Expand Up @@ -173,6 +203,32 @@ public function setPublishedAt(?\DateTime $publishedAt): void
$this->publishedAt = $publishedAt;
}

public function getThumbnail(): ?string
{
return $this->thumbnail;
}

public function setThumbnail(string $thumbnail): void
{
$this->thumbnail = $thumbnail;
}

public function getThumbnailFile(): ?File
{
return $this->thumbnailFile;
}

public function setThumbnailFile(File $thumbnailFile): void
{
$this->thumbnailFile = $thumbnailFile;

// the thumbnailFile field not being mapped with ORM, we force the change of a property that is mapped
// so that the change of this field is taken into account when updating the entity.
if (null !== $thumbnailFile) {
$this->updatedAt = new \DateTime();
}
}

public function getAuthor(): User
{
return $this->author;
Expand Down
Loading

0 comments on commit f821357

Please sign in to comment.