I'm about to embark on trying to add a profile picture field to my User entity in a symfony2 project I'm working on which uses FOSUserBundle. I have a feeling that this should be really simple, however, I've not found any useful documentation explaining how to do it.
At the moment, I'm planning on adding an unmapped field to my form object (which I've extended from the generic ProfileEdit form) which takes an uploaded file. I'll then create an event listener for either FOSUserEvents::PROFILE_EDIT_SUCCESS or FOSUserEvents::PROFILE_EDIT_COMPLETED, which will take the form, handle the file upload and persist the path of the uploaded file (with methods to get the public URL and absolute path) to my User object, before adding a message to the Response flashbag to say it was either successful or unsuccessful. Is this really the correct/best practice way to do this? Or am I missing something? Is this functionality really not included yet in the FOSUserBundle? If so I've not found the docs for it, but would love it if it was....
Any help / tips / voodoo advice would be so greatly appreciated!
So, it turns out I was getting lost answering this question: the file upload needn't be handled by FOSUserBundle per se, rather by Doctrine. I added a property to my User entity which is not persisted, but which is simply used by the form to edit a user's profile. In the code below this property is $profilePictureFile. Lifecycle callbacks then ensure that this file is copied to the relevant location before the entity is persisted (and similarly when edited and when deleted).
Still, I thought I'd post an answer alongside my code here to help others who find themselves wanting to add profile pictures to a user in FOSUserBundle in the future.
The relevant documentation is:
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/overriding_forms.rst
<?php
// DAWeldonExampleBundle/Entity/User.php
namespace DAWeldon\Example\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Security\Core\Util\SecureRandom;
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="datetime")
*/
protected $lastEdited;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank(message="Please enter your surname.", groups={"Registration", "Profile"})
*/
protected $surname;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank(message="Please enter your forename.", groups={"Registration", "Profile"})
*/
protected $forename;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $nickname;
/**
* #Assert\File(maxSize="2048k")
* #Assert\Image(mimeTypesMessage="Please upload a valid image.")
*/
protected $profilePictureFile;
// for temporary storage
private $tempProfilePicturePath;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $profilePicturePath;
public function __construct()
{
parent::__construct();
// your own logic
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set surname
*
* #param string $surname
* #return User
*/
public function setSurname($surname)
{
$this->surname = $surname;
return $this;
}
/**
* Get surname
*
* #return string
*/
public function getSurname()
{
return $this->surname;
}
/**
* Set forename
*
* #param string $forename
* #return User
*/
public function setForename($forename)
{
$this->forename = $forename;
return $this;
}
/**
* Get forename
*
* #return string
*/
public function getForename()
{
return $this->forename;
}
/**
* Asks whether the user is granted a particular role
*
* #return boolean
*/
public function isGranted($role)
{
return in_array($role, $this->getRoles());
}
/**
* Set nickname
*
* #param string $nickname
* #return User
*/
public function setNickname($nickname)
{
$this->nickname = $nickname;
return $this;
}
/**
* Get nickname
*
* #return string
*/
public function getNickname()
{
return $this->nickname;
}
/**
* Get the best way to address this person
*
* #return string
*/
public function getBestAddress() {
if (empty($this->getNickname()) and empty($this->getForename()) && empty($this->getSurname())) {
return $this->getUsername();
}
elseif (empty($this->getNickname())) {
return $this->getForename().' '.$this->getSurname();
}
else {
return $this->getNickname();
}
}
/**
* Sets the file used for profile picture uploads
*
* #param UploadedFile $file
* #return object
*/
public function setProfilePictureFile(UploadedFile $file = null) {
// set the value of the holder
$this->profilePictureFile = $file;
// check if we have an old image path
if (isset($this->profilePicturePath)) {
// store the old name to delete after the update
$this->tempProfilePicturePath = $this->profilePicturePath;
$this->profilePicturePath = null;
} else {
$this->profilePicturePath = 'initial';
}
return $this;
}
/**
* Get the file used for profile picture uploads
*
* #return UploadedFile
*/
public function getProfilePictureFile() {
return $this->profilePictureFile;
}
/**
* Set profilePicturePath
*
* #param string $profilePicturePath
* #return User
*/
public function setProfilePicturePath($profilePicturePath)
{
$this->profilePicturePath = $profilePicturePath;
return $this;
}
/**
* Get profilePicturePath
*
* #return string
*/
public function getProfilePicturePath()
{
return $this->profilePicturePath;
}
/**
* Get the absolute path of the profilePicturePath
*/
public function getProfilePictureAbsolutePath() {
return null === $this->profilePicturePath
? null
: $this->getUploadRootDir().'/'.$this->profilePicturePath;
}
/**
* Get root directory for file uploads
*
* #return string
*/
protected function getUploadRootDir($type='profilePicture') {
// the absolute directory path where uploaded
// documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir($type);
}
/**
* Specifies where in the /web directory profile pic uploads are stored
*
* #return string
*/
protected function getUploadDir($type='profilePicture') {
// the type param is to change these methods at a later date for more file uploads
// get rid of the __DIR__ so it doesn't screw up
// when displaying uploaded doc/image in the view.
return 'uploads/user/profilepics';
}
/**
* Get the web path for the user
*
* #return string
*/
public function getWebProfilePicturePath() {
return '/'.$this->getUploadDir().'/'.$this->getProfilePicturePath();
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUploadProfilePicture() {
if (null !== $this->getProfilePictureFile()) {
// a file was uploaded
// generate a unique filename
$filename = $this->generateRandomProfilePictureFilename();
$this->setProfilePicturePath($filename.'.'.$this->getProfilePictureFile()->guessExtension());
}
}
/**
* Generates a 32 char long random filename
*
* #return string
*/
public function generateRandomProfilePictureFilename() {
$count = 0;
do {
$generator = new SecureRandom();
$random = $generator->nextBytes(16);
$randomString = bin2hex($random);
$count++;
}
while(file_exists($this->getUploadRootDir().'/'.$randomString.'.'.$this->getProfilePictureFile()->guessExtension()) && $count < 50);
return $randomString;
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*
* Upload the profile picture
*
* #return mixed
*/
public function uploadProfilePicture() {
// check there is a profile pic to upload
if ($this->getProfilePictureFile() === null) {
return;
}
// if there is an error when moving the file, an exception will
// be automatically thrown by move(). This will properly prevent
// the entity from being persisted to the database on error
$this->getProfilePictureFile()->move($this->getUploadRootDir(), $this->getProfilePicturePath());
// check if we have an old image
if (isset($this->tempProfilePicturePath) && file_exists($this->getUploadRootDir().'/'.$this->tempProfilePicturePath)) {
// delete the old image
unlink($this->getUploadRootDir().'/'.$this->tempProfilePicturePath);
// clear the temp image path
$this->tempProfilePicturePath = null;
}
$this->profilePictureFile = null;
}
/**
* #ORM\PostRemove()
*/
public function removeProfilePictureFile()
{
if ($file = $this->getProfilePictureAbsolutePath() && file_exists($this->getProfilePictureAbsolutePath())) {
unlink($file);
}
}
/**
* Set lastEdited
*
* #param \DateTime $lastEdited
* #return User
*/
public function setLastEdited($lastEdited)
{
$this->lastEdited = $lastEdited;
return $this;
}
/**
* Get lastEdited
*
* #return \DateTime
*/
public function getLastEdited()
{
return $this->lastEdited;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function setLastEditedValueAsNow() {
$this->setLastEdited(new \DateTime());
}
}
And of course, the profile form looks like this:
<?php
// DAWeldonExampleBundle/Form/Type/ProfileFormType.php
namespace DAWeldon\Example\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ProfileFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// add your custom field
$builder->add('username')
->add('surname')
->add('forename')
->add('nickname')
->add('profilePictureFile');
}
public function getParent()
{
return 'fos_user_profile';
}
public function getName()
{
return 'readypeeps_user_profile';
}
}
Related
Hello fellows I on my asympfony 3.0 project I want to make a Library that when is constructed to read from config.yml these parameters:
max_width
max_height
And the library will read them and will perfom a image resize to these dimentions provided by config.yml or any other way to read config.
In order to be more clear I have this Entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Filesystem\Filesystem;
/**
* #ORM\Entity
* #ORM\Table(name="images")
* #ORM\Entity(repositoryClass="AppBundle\Entity\ImagesRepository")
* #ORM\HasLifecycleCallbacks
*/
class Images
{
/**
* #ORM\Column(type="string", length=60)
* #ORM\Id
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="AppBundle\Doctrine\AutoIdGenerate")
*/
private $id;
/**
* Filename of the Image
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* Filename of the Thumbnail
* #ORM\Column(type="string", length=100)
*/
private $name_small;
/**
* ImageGroup og the Image
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\ImageGroups", inversedBy="images")
*/
private $group;
/**
* #Assert\File(maxSize="20000000")
*/
private $file;
private $upload_dir='images';
private $temp;
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file,$upload_dir)
{
$this->file = $file;
// check if we have an old image path
if (isset($this->name))
{
// store the old name to delete after the update
$this->temp = $this->name;
$this->name = null;
}
else
{
$this->name = sha1(uniqid(mt_rand(), true)).'.'.$file->guessExtension();
}
$this->name_small="small_".$this->name;
$this->upload_dir=$upload_dir;
return $this;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->getFile())
{
// do whatever you want to generate a unique name
$filename = sha1(uniqid(mt_rand(), true));
$this->name = $filename.'.'.$this->getFile()->guessExtension();
$this->name_small='small_'.$this->name;
}
}
/**
*Getting the directory that will upload the file
*/
public function getUploadRootDir()
{
$dir= __DIR__.'/../../../web/'.$this->upload_dir;
return $dir;
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->getFile())
{
return;
}
// if there is an error when moving the file, an exception will
// be automatically thrown by move(). This will properly prevent
// the entity from being persisted to the database on error
//-- Make the Thumbnail Here --
$dir=$this->getUploadRootDir();
$fs = new Filesystem();
if(!$fs->exists($dir))
{
$fs->mkdir($dir,0777,true);
}
$file=$this->getFile();
$file->move($dir, $this->name);
// check if we have an old image
if (isset($this->temp))
{
// delete the old image
unlink($this->getUploadRootDir().'/'.$this->temp);
// clear the temp image path
$this->temp = null;
}
$this->file = null;
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
$file = $this->getUploadRootDir();
error_reporting(E_ERROR | E_PARSE); //If the file does not exist just ingore the warning
if (file_exists($file.'/'.$this->name)===true)
{
unlink($file.'/'.$this->name);
}
if(file_exists($file.'/'.$this->name_small)===true);
{
unlink($file.'/'.$this->name_small);
}
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);//But we want warnings back
}
/**
* Get id
*
* #return string
*/
public function getId()
{
return $this->id;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Get nameSmall
*
* #return string
*/
public function getNameSmall()
{
return $this->name_small;
}
/**
* Set group
*
* #param \AppBundle\Entity\ImageGroups $group
*
* #return Images
*/
public function setGroup(\AppBundle\Entity\ImageGroups $group = null)
{
$this->group = $group;
return $this;
}
/**
* Get group
*
* #return \AppBundle\Entity\ImageGroups
*/
public function getGroup()
{
return $this->group;
}
/**
* Set name
*
* #param string $name
*
* #return Images
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Set nameSmall
*
* #param string $nameSmall
*
* #return Images
*/
public function setNameSmall($nameSmall)
{
$this->name_small = $nameSmall;
return $this;
}
}
As you can see the entity above has Doctrine triggers. Inside function upload I want to call:
new ImageProcessor($image_path,$extention)->to_thumb($filename)
And this will read from config.yml the max_width and max_height and will perform the image resize. The ImageProcessor will be the library that will do any kind of image processings like crop, resize, etc etc.
The problem is NOT how to resize the image but on how can I make this Library resubable and easily configurable by config.yml. I cannot find a good tutorial or manual to do that.
Hope this will help: http://symfony.com/doc/current/cookbook/bundles/configuration.html
Shortly you'll need to implement
Configuration.php that will describe tree of bundle config in config.yml
Catching values in bundle extension and store them in way you choose (in a global parameter or pass it to library using DI container).
I've got fos_user entity that is in relation OneToOne with reservedArea entity.
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="ReservedAreaBundle\Entity\ReservedArea", cascade={"persist"})
* #ORM\JoinColumn(name="reserved", referencedColumnName="id", onDelete="SET NULL")
*/
protected $reserved;
public function __construct()
{
parent::__construct();
}
public function getReserved()
{
return $this->reserved;
}
public function setReserved($reserved)
{
$this->reserved = $reserved;
}
}
In my reservedArea entity i've got only a OneToMany relation with File entity.
class ReservedArea
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="ReservedAreaBundle\Entity\File", mappedBy="reserved_area", cascade={"persist"})
*/
protected $file;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Constructor
*/
public function __construct()
{
$this->file = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add file
*
* #param \ReservedAreaBundle\Entity\File $file
*
* #return ReservedArea
*/
public function addFile(\ReservedAreaBundle\Entity\File $file)
{
$this->file[] = $file;
return $this;
}
/**
* Remove file
*
* #param \ReservedAreaBundle\Entity\File $file
*/
public function removeFile(\ReservedAreaBundle\Entity\File $file)
{
$this->file->removeElement($file);
}
/**
* Get file
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getFile()
{
return $this->file;
}
}
And in my File entity i've got just a simple string.
class File
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $file;
/**
* #ORM\ManyToOne(targetEntity="ReservedAreaBundle\Entity\ReservedArea", inversedBy="file")
* #ORM\JoinColumn(name="reserved_area", referencedColumnName="id")
*/
protected $reservedArea;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set reservedArea
*
* #param \ReservedAreaBundle\Entity\ReservedArea $reservedArea
*
* #return File
*/
public function setReservedArea(\ReservedAreaBundle\Entity\ReservedArea $reservedArea = null)
{
$this->reservedArea = $reservedArea;
return $this;
}
/**
* Get reservedArea
*
* #return \ReservedAreaBundle\Entity\ReservedArea
*/
public function getReservedArea()
{
return $this->reservedArea;
}
/**
* Set file
*
* #param string $file
*
* #return File
*/
public function setFile($file)
{
$this->file = $file;
return $this;
}
/**
* Get file
*
* #return string
*/
public function getFile()
{
return $this->file;
}
Now if i try to access to user file doing something like that :
$files = $this->getUser()->getReserved()->getFile();
foreach($files as $file) {
var_dump($file); //or $file->getFile();
}
I get an error :
Notice: Undefined index: reserved_area
In my database I add an user with a reserved area and two file and if i direct make :
SELECT u.username, f.file FROM fos_user u INNER JOIN reserved_area r ON u.reserved=r.id INNER JOIN reserved_area_file f ON f.reserved_area=r.id;
I get in output the right result.
Where i'm going wrong?
Thanks in advice.
I found the solution to the problem and I post it beacause maybe will help someone.
The problem is here:
/**
* #ORM\OneToMany(targetEntity="ReservedAreaBundle\Entity\File", mappedBy="reserved_area", cascade={"persist"})
*/
protected $file;
I can't use in mappedBy the name of the table in #JoinColumn(name="reserved_are") but I have to use the name of the attribute.
So :
mappedBy="reservedArea"
solved the problem.
EDIT: I answered the original question: https://stackoverflow.com/revisions/35969467/1
which was then edited by the author.
The annotation mapping of $reservedArea in File class is incorrect.
The mappedBy and inversedBy attributes must always contain name of the attribute from entity on the other side of the association.
So in your case File::$reservedArea should have inversedBy="file".
See http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-many-bidirectional for more examples.
I have the following model, or as you call them entity, and I also have a controller, everything works in this action, but when I check the database there is no user. So I am curious as what I am missing. So lets start at the beginning as to what I have:
bootstrap.php contains the following code, among other things.
...
/** ---------------------------------------------------------------- **/
// Lets Setup Doctrine.
/** ---------------------------------------------------------------- **/
require_once 'vendor/autoload.php';
$loader = require 'vendor/autoload.php';
\Doctrine\Common\Annotations\AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
/**
* Set up Doctrine.
*/
class DoctrineSetup {
/**
* #var array $paths - where the entities live.
*/
protected $paths = array(APP_MODELS);
/**
* #var bool $isDevMode - Are we considered "in development."
*/
protected $isDevMode = false;
/**
* #var array $dbParams - The database paramters.
*/
protected $dbParams = null;
/**
* Constructor to set some core values.
*/
public function __construct(){
if (!file_exists('db_config.ini')) {
throw new \Exception(
'Missing db_config.ini. You can create this from the db_config_sample.ini'
);
}
$this->dbParams = array(
'driver' => 'pdo_mysql',
'user' => parse_ini_file('db_config.ini')['DB_USER'],
'password' => parse_ini_file('db_config.ini')['DB_PASSWORD'],
'dbname' => parse_ini_file('db_config.ini')['DB_NAME']
);
}
/**
* Get the entity manager for use through out the app.
*
* #return EntityManager
*/
public function getEntityManager() {
$config = Setup::createAnnotationMetadataConfiguration($this->paths, $this->isDevMode, null, null, false);
return EntityManager::create($this->dbParams, $config);
}
}
/**
* Function that can be called through out the app.
*
* #return EntityManager
*/
function getEntityManager() {
$ds = new DoctrineSetup();
return $ds->getEntityManager();
}
/**
* Function that returns the conection to the database.
*/
function getConnection() {
$ds = new DoctrineSetup();
return $ds->getEntityManager()->getConnection();
}
...
So now that we have doctrine set up its time to create a model (entity) and set which fields can and cannot be blank and so on and so forth.
Note At this point, you should know that I am not using Symfony other then its components on top of Doctrine. I am using Slim Framework. So if any suggestion is to use x or y from symfony, please make sure its a component.
Models/User.php
<?php
namespace ImageUploader\Models;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="users", uniqueConstraints={
* #ORM\UniqueConstraint(name="user", columns={"userName", "email"})}
* )
*/
class User {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\Column(type="string", length=32, nullable=false)
* #Assert\NotBlank()
*/
protected $firstName;
/**
* #ORM\Column(type="string", length=32, nullable=false)
* #Assert\NotBlank()
*/
protected $lastName;
/**
* #ORM\Column(type="string", length=100, unique=true, nullable=false)
* #Assert\NotBlank(
* message = "Username cannot be blank"
* )
*/
protected $userName;
/**
* #ORM\Column(type="string", length=100, unique=true, nullable=false)
* #Assert\NotBlank(
* message = "Email field cannot be blank."
* )
* #Assert\Email(
* message = "The email you entered is invalid.",
* checkMX = true
* )
*/
protected $email;
/**
* #ORM\Column(type="string", length=500, nullable=false)
* #Assert\NotBlank(
* message = "The password field cannot be empty."
* )
*/
protected $password;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
protected $created_at;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
protected $updated_at;
/**
* Get the value of Created At
*
* #return mixed
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* Set the value of Created At
*
* #param mixed created_at
*
* #return self
*/
public function setCreatedAt(\DateTime $created_at = null)
{
$this->created_at = $created_at;
return $this;
}
/**
* Get the value of Updated At
*
* #return mixed
*/
public function getUpdatedAt()
{
return $this->updated_at;
}
/**
* Set the value of Updated At
*
* #param mixed updated_at
*
* #return self
*/
public function setUpdatedAt(\DateTime $updated_at = null)
{
$this->updated_at = $updated_at;
return $this;
}
/**
* Get the value of First Name
*
* #return mixed
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Set the value of First Name
*
* #param mixed firstName
*
* #return self
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get the value of Last Name
*
* #return mixed
*/
public function getLastName()
{
return $this->lastName;
}
/**
* Set the value of Last Name
*
* #param mixed lastName
*
* #return self
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Get the value of User Name
*
* #return mixed
*/
public function getUserName()
{
return $this->userName;
}
/**
* Set the value of User Name
*
* #param mixed userName
*
* #return self
*/
public function setUserName($userName)
{
$this->userName = $userName;
return $this;
}
/**
* Get the value of Email
*
* #return mixed
*/
public function getEmail()
{
return $this->email;
}
/**
* Set the value of Email
*
* #param mixed email
*
* #return self
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* Set ths password.
*
* #param string password
*
* #return self
*/
public function setPassword($password) {
$this->password = password_hash($password, PASSWORD_DEFAULT);
return $this;
}
/**
* Check the users password against that which is enterd.
*
* #param string password
*
* #return bool
*/
public function checkPassword($password) {
if (password_hash($password, PASSWORD_DEFAULT) === $this->getPassword()) {
return true;
}
return false;
}
/**
* Return the password value.
*
* #return hash
*/
private function getPassword(){
return $this->password;
}
/**
* #ORM\PrePersist
*/
public function setCreatedAtTimeStamp() {
if (is_null($this->getCreatedAt())) {
$this->setCreatedAt(new \DateTime());
}
}
/**
* #ORM\PreUpdate
*/
public function setUpdatedAtTimeStamp() {
if (is_null($this->getUpdatedAt())) {
$this->setUpdatedAt(new \DateTime());
}
}
}
The above model is correct, as far as I know, I mean when I run "vendor/bin/doctrine migrations:migrate" a database table is created.
Now, where is all this used? it's used in a controller called SignupController under an action called createAction($params)
**createAction($params)**
public static function createAction($params){
$postParams = $params->request()->post();
$flash = new Flash();
if ($postParams['password'] !== $postParams['repassword']) {
$flash->createFlash('error', 'Your passwords do not match.');
self::$createEncryptedPostParams($postParams);
$params->redirect('/signup/error');
}
$user = new User();
$user->setFirstName($postParams['firstname'])
->setLastName($postParams['lastname'])
->setUserName($postParams['username'])
->setEmail($postParams['email'])
->setPassword($postParams['password'])
->setCreatedAtTimeStamp();
$validator = Validator::createValidatorBuilder();
$validator->enableAnnotationMapping();
$errors = $validator->getValidator()->validate($user);
if (count($errors) > 0) {
foreach($errors as $error) {
$flash->createFlash(
$error->getPropertyPath() . 'error',
$error->getMessage()
);
}
self::createEncryptedPostParams($postParams);
$params->redirect('/signup/error');
}
$anyEncryptedErors = self::getEncryptedPostParams();
if ($anyEncryptedErors !== null) {
$anyEncryptedErors->destroy('error');
}
getEntityManager()->flush();
getEntityManager()->persist($user);
$flash->createFlash('success', ' You have signed up successfully! Please sign in!');
$params->redirect('/signin');
}
Now should you enter everything in correctly I show a flash of success and redirect you. THIS WORKS it redirects, it shows a flash message. But its the:
getEntityManager()->flush();
getEntityManager()->persist($user);
That I don't think is working. Why? Because doing a select * from users on the database in question comes back with no records.
Why?
Flush statement should be execute after persist. So Code should be:
getEntityManager()->persist($user);
getEntityManager()->flush();
I had a similar issue and thought I would post it here. I was creating an entity and everything was responding correctly, but when I checked the database no record had been created.
Just wrapped the flush in a try-catch and logged the error.
$this->em->persist($insectLifeCycle);
try {
$this->em->flush();
} catch (\Exception $error) {
$this->logger->debug($error);
}
It turns out that one of properties was exceeding its character limit and the database was throwing an error. Also found out I need to improve my error handling....
As Samiul Amin Shanto said:
getEntityManager()->persist($user);
getEntityManager()->flush();
will be correct way, because persist action prepare the data to be stored in DB and flush "Flushes all changes to now to the database."
If you have the object id and then, the database is not showing it, you might have a "START TRANSACTION" and then you have your insert, after this insert you will have a "COMMIT". If any error appears between your persist and the COMMIT, object won't be stored in your database.
Check your Symfony request profiler information.
You can find it using the developer tool and checking your response for it.
I'm trying to figure out how to do multiple file uploads but without luck. I have not been able to get too much info on this.. Maybe someone here will help me out? :D
Criteria:
I know that in my form type I'm supposed to use multiples for fields; but when I add it, it gives me the this error.
Catchable Fatal Error: Argument 1 passed to
PhotoGalleryBundle\Entity\Image::setFile() must be an instance of
Symfony\Component\HttpFoundation\File\UploadedFile, array given,
called in
/home/action/workspace/www/DecorInterior/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
on line 442 and defined
Image Entity
Here is my code in PHP:
<?php
namespace PhotoGalleryBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* Image
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="PhotoGalleryBundle\Entity\ImageRepository")
* #ORM\HasLifecycleCallbacks
*/
class Image
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="imageCaptation", type="string", length=255)
* #Assert\NotBlank
*/
private $imageCaptation;
/**
* #var string
*
* #ORM\Column(name="imageName", type="string", length=255)
*/
private $imageName;
/**
* #var string
*
* #ORM\Column(name="imageFilePath", type="string", length=255, nullable=true)
*/
private $imageFilePath;
/**
* #var \DateTime
*
* #ORM\Column(name="imageUploadedDate", type="datetime")
* #Gedmo\Timestampable(on="create")
*/
private $imageUploadedDate;
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
/**
* #ORM\ManyToOne(targetEntity="Album", inversedBy="images")
* #ORM\JoinColumn(name="album", referencedColumnName="id")
*/
private $album;
private $temp;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set imageCaptation
*
* #param string $imageCaptation
* #return Image
*/
public function setImageCaptation($imageCaptation)
{
$this->imageCaptation = $imageCaptation;
return $this;
}
/**
* Get imageCaptation
*
* #return string
*/
public function getImageCaptation()
{
return $this->imageCaptation;
}
/**
* Set imageName
*
* #param string $imageName
* #return Image
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* Get imageName
*
* #return string
*/
public function getImageName()
{
return $this->imageName;
}
/**
* Set imageFilePath
*
* #param string $imageFilePath
* #return Image
*/
public function setImageFilePath($imageFilePath)
{
$this->imageFilePath = $imageFilePath;
return $this;
}
/**
* Get imageFilePath
*
* #return string
*/
public function getImageFilePath()
{
return $this->imageFilePath;
}
/**
* Get imageUploadedDate
*
* #return \DateTime
*/
public function getImageUploadedDate()
{
return $this->imageUploadedDate;
}
/**
* Set file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null) {
$this->file = $file;
// check if we have an old image path
if (isset($this->imageFilePath)) {
// store the old name to delete after the update
$this->temp = $this->imageFilePath;
$this->imageFilePath = null;
} else {
$this->imageFilePath = 'initial';
}
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate
*/
public function preUpload() {
if (null !== $this->getFile()) {
// do whatever you want to generate a unique name
$fileName = sha1(uniqid(mt_rand(), true));
$this->imageFilePath = $fileName . '.' . $this->getFile()->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate
*/
public function upload() {
if (null === $this->getFile()) {
return;
}
// if there is an error when moving the file, an exception will
// be automatically thrown by move(). This will properly prevent
// the entity from being persisted to the database on error
$this->getFile()->move($this->getUploadRootDir(), $this->imageFilePath);
// check if we have an old image
if (isset($this->temp)) {
// delete the old image
unlink($this->getUploadRootDir() . '/' . $this->temp);
// clear the temp image path
$this->temp = null;
}
$this->imageFilePath = null;
}
/**
* #ORM\PostRemove()
*/
public function removeUpload() {
$file = $this->getAbsolutePath();
if ($file) {
unlink($file);
}
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile() {
return $this->file;
}
public function getAbsolutePath() {
return null === $this->imageFilePath
? null
: $this->getUploadRootDir() . '/' . $this->imageFilePath;
}
public function getWebPath() {
return null === $this->imageFilePath
? null
: $this->getUploadDir() . '/' . $this->imageFilePath;
}
public function getUploadRootDir() {
// the absolute path where uploaded
// documents should be saved
return __DIR__.'/../../../web/' . $this->getUploadDir();
}
public function getUploadDir() {
// get rid of the __DIR__ so it doesn't screw up
// when displaying uploaded doc/image in the view
return 'uploads/images';
}
/**
* Set album
*
* #param \PhotoGalleryBundle\Entity\Album $album
* #return Image
*/
public function setAlbum(\PhotoGalleryBundle\Entity\Album $album = null)
{
$this->album = $album;
return $this;
}
/**
* Get album
*
* #return \PhotoGalleryBundle\Entity\Album
*/
public function getAlbum()
{
return $this->album;
}
}
ImageType:
<?php
namespace PhotoGalleryBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ImageType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('imageCaptation')
->add('imageName')
->add('file', 'file', array('multiple' => TRUE))
->add('album')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'PhotoGalleryBundle\Entity\Image'
));
}
/**
* #return string
*/
public function getName()
{
return 'photogallerybundle_image';
}
}
In this case the value will be an array of UploadedFile objects. Given that you have a type with multiple files yet only one caption, name, etc I assume you will want to refactor Image to support multiple image files. In this case the file property would be better known as files and setFiles should accept an array.
If however you want have one Image entity per file uploaded, consider implementing a collection instead.
Alternatively you could manually process the form in your action. For example:
public function uploadAction(Request $request)
{
foreach ($request->files as $uploadedFile) {
$uploadedFile = current($uploadedFile['file']);
// Build Image using each uploaded file
}
...
I am new to symfony2.
I am using liipImagineBundle to manage image thumbnail.
I have a product entity class which uses Lifecycle Callbacks to manage product image.
Product.php
<?php
namespace Svipl\AdminBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as GEDMO;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Svipl\AdminBundle\Entity\Product
* #ORM\Entity
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="Svipl\AdminBundle\Entity\ProductRepository")
* #ORM\HasLifecycleCallbacks
*/
class Product{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=25, unique=true)
*/
private $name;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\Column(type="float", length=8)
* #var unknown
*/
private $price;
/**
* #GEDMO\Timestampable(on="update")
* #ORM\Column(name="updated_at", type="datetime")
*/
private $updated_at;
/**
* #GEDMO\Timestampable(on="create")
* #ORM\Column(name="created_at", type="datetime")
*/
private $created_at;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
/**
* #ORM\Column(name="image", type="string", length=50)
*/
private $image;
public function getAbsolutePath()
{
return null === $this->image
? null
: $this->getUploadRootDir().'/'.$this->image;
}
public function getWebPath()
{
return null === $this->image
? null
: $this->getUploadDir().'/'.$this->image;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded
// documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw up
// when displaying uploaded doc/image in the view.
return 'uploads/product';
}
private $file;
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Product
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set description
*
* #param string $description
* #return Product
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set price
*
* #param float $price
* #return Product
*/
public function setPrice($price)
{
$this->price = $price;
return $this;
}
/**
* Get price
*
* #return float
*/
public function getPrice()
{
return $this->price;
}
/**
* Set updated_at
*
* #param \DateTime $updatedAt
* #return Product
*/
public function setUpdatedAt($updatedAt)
{
$this->updated_at = $updatedAt;
return $this;
}
/**
* Get updated_at
*
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updated_at;
}
/**
* Set created_at
*
* #param \DateTime $createdAt
* #return Product
*/
public function setCreatedAt($createdAt)
{
$this->created_at = $createdAt;
return $this;
}
/**
* Get created_at
*
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* Set category
*
* #param \Svipl\AdminBundle\Entity\Category $category
* #return Product
*/
public function setCategory(\Svipl\AdminBundle\Entity\Category $category = null)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return \Svipl\AdminBundle\Entity\Category
*/
public function getCategory()
{
return $this->category;
}
/**
* Set image
*
* #param string $image
* #return Product
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
private $temp;
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
// check if we have an old image path
if (isset($this->image)) {
// store the old name to delete after the update
$this->temp = $this->image;
$this->image = null;
} else {
$this->image = 'initial';
}
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->getFile()) {
// do whatever you want to generate a unique name
$filename = sha1(uniqid(mt_rand(), true));
$this->image = $filename.'.'.$this->getFile()->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->getFile()) {
return;
}
// if there is an error when moving the file, an exception will
// be automatically thrown by move(). This will properly prevent
// the entity from being persisted to the database on error
$this->getFile()->move($this->getUploadRootDir(), $this->image);
// check if we have an old image
if (isset($this->temp)) {
// delete the old image
unlink($this->getUploadRootDir().'/'.$this->temp);
// clear the temp image path
$this->temp = null;
}
$this->file = null;
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
}
config.yml
...
liip_imagine:
filter_sets:
my_thumb:
quality: 75
filters:
thumbnail: { size: [120, 90], mode: outbound }
Thumbnail generation code
...
<img src="{{ asset('uploads/product/' ~ form_object.vars.value.image) | imagine_filter('my_thumb', true) }}" />
...
Thumbnail generating correctly.
But I am not able to find the way to update/remove cached image while original image change or delete with form.
You should register a preUpdate and a preRemove event listener/subscriber, inject the service needed and delete the images in there.
As you don't have access to the service container ( and you should not inject services into your entities ) you can't query LiipImagineBundle's services to obtain the cached files from inside your entity using Lifecycle Events.
You can inject the service liip_imagine.cache.manager and use it's remove() method to delete an image from cache.
You have to create a Listener Entity and create a service.
The service will call this Entity each event you want : here in the PostUpdate and the preRemove of your Product.
In the Entity Listener you have a method for each event you have set, and you just have to clear the cache on each method or do anything else you want.
Here an example comes from this post
Service
services:
project.cacheimage_listener:
class: Acme\Listener\CacheImageListener
arguments: ["#liip_imagine.cache.manager"]
tags:
- { name: doctrine.event_listener, event: postUpdate }
- { name: doctrine.event_listener, event: preRemove }
Entity Listener
<?php
namespace Acme\Listener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\Entity\Image;
class CacheImageListener
{
protected $cacheManager;
public function __construct($cacheManager)
{
$this->cacheManager = $cacheManager;
}
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof Image) {
// clear cache of thumbnail
$this->cacheManager->remove($entity->getUploadDir());
}
}
// when delete entity so remove all thumbnails related
public function preRemove(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof Image) {
$this->cacheManager->remove($entity->getWebPath());
}
}
}
This solution works fine on my site.
Don't forget to call resolve() method of liip_imagine.cache.manager. Otherwise, it won't remove your cached image.
as described here: https://github.com/liip/LiipImagineBundle/issues/132
$cacheManager = $this->get('liip_imagine.cache.manager');
$cacheManager->resolve($this->getRequest(), $pngPath, $filter);
$cacheManager->remove($pngPath, $filter);
I know this question is a bit old, but in case someone looks for some code (I'm using SF 2.3). I had this requirement for file removal. in my project I'm using VichUploaderBundle to handle file uploads and LiipImagineBundle to handle thumbnail generation for those images. When the entity is removed, uploaded file should be removed as well as the thumbnail (if any was generated). I've implemented a doctrine listener, the preRemove method is as follow:
public function preRemove(LifecycleEventArgs $args)
{
$filter = 'thumbnail'; //the filter that imagine bundle uses
$fileEntity = $args->getEntity();
if($fileEntity instanceof FileEntity)
{
//get the path to the uploaded file, relative to the web url
$sourcePath = $this->uploaderStorage->resolveUri($fileEntity, "file");
$this->liipCacheManager->remove($sourcePath, $filter);
}
}