У меня есть сущность «член» с изображением профиля внутри (не путь к образу), хранящаяся в базе данных mysql.
Это работает, когда я показываю изображение в действии index и в действии show, но когда я хочу его отредактировать, я хотел бы показать текущее сохраненное изображение, как это сделано с другими данными Entity.
Я следил за инструкциями в документации Symfony: https://symfony.com/doc/current/form/create_form_type_extension.html, но это не работает.
Также пробовал это решение: Symfony / Twig: как передать в форму дополнительные данные?
Включаю код:
src / Entity / Member.php
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* Member
*
* @ORM\Table(name = "members", uniqueConstraints = {@ORM\UniqueConstraint(name = "ix_first_name_last_name", columns = {"first_name","last_name"})})
* @ORM\Entity(repositoryClass = "App\Repository\MemberRepository")
* @UniqueEntity(fields = "email", message = "Email already taken")
* @UniqueEntity(fields = "username", message = "Username already taken")
* @ORM\HasLifecycleCallbacks()
*/
class Member implements UserInterface, \Serializable
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type = "integer")
*/
private $id;
/**
* @ORM\Column(type = "string", length=100)
*/
private $firstName;
/**
* @ORM\Column(type = "string", length=100)
*/
private $lastName;
/**
* @ORM\Column(type = "blob", nullable=true)
*/
private $pictureProfile;
/**
* @ORM\Column(type = "string", length=255, unique=true)
* @Assert\NotBlank()
* @Assert\Email()
*/
private $email;
public function getId()
{
return $this->id;
}
public function getFirstName(): ?string
{
return $this->firstName;
}
public function setFirstName(string $firstName): self
{
$this->firstName = $firstName;
return $this;
}
public function getLastName(): ?string
{
return $this->lastName;
}
public function setLastName(string $lastName): self
{
$this->lastName = $lastName;
return $this;
}
public function getPictureProfile()
{
if ($this->pictureProfile != null) {
$image = base64_encode(stream_get_contents($this->pictureProfile));
} else {
$image = null;
}
return $image;
}
public function setPictureProfile($pictureProfile): self
{
$this->pictureProfile = $pictureProfile;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
}
SRC / Контроллер / MemberController.php
<?php
namespace App\Controller;
use App\Entity\Member;
use App\Form\MemberType;
use App\Repository\MemberRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* @Route("/member")
*/
class MemberController extends Controller
{
/**
* @Route("/", name = "member_index", methods = "GET")
*/
public function index(MemberRepository $memberRepository): Response
{
return $this->render('member/index.html.twig', ['members' => $memberRepository->findAll()]);
}
/**
* @Route("/new", name = "member_new", methods = "GET|POST")
*/
public function new(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
{
$member = new Member();
$form = $this->createForm(MemberType::class, $member);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($_FILES['member']['size']['pictureProfile'] > 0) {
$image = fread(fopen($_FILES['member']['tmp_name']['pictureProfile'], 'r'), filesize($_FILES['member']['tmp_name']['pictureProfile']));
$member->setPictureProfile($image);
}
$em = $this->getDoctrine()->getManager();
$em->persist($member);
$em->flush();
return $this->redirectToRoute('member_index');
}
return $this->render('member/new.html.twig', [
'member' => $member,
'form' => $form->createView(),
]);
}
/**
* @Route("/{id}", name = "member_show", methods = "GET")
*/
public function show(Member $member): Response
{
return $this->render('member/show.html.twig', ['member' => $member]);
}
/**
* @Route("/{id}/edit", name = "member_edit", methods = "GET|POST")
*/
public function edit(Request $request, Member $member, UserPasswordEncoderInterface $passwordEncoder): Response
{
$form = $this->createForm(MemberType::class, $member);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($_FILES['member']['size']['pictureProfile'] > 0) {
$image = fread(fopen($_FILES['member']['tmp_name']['pictureProfile'], 'r'), filesize($_FILES['member']['tmp_name']['pictureProfile']));
$member->setPictureProfile($image);
}
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('member_index');
}
return $this->render('member/edit.html.twig', [
'member' => $member,
'form' => $form->createView(),
]);
}
/**
* @Route("/{id}", name = "member_delete", methods = "DELETE")
*/
public function delete(Request $request, Member $member): Response
{
if ($this->isCsrfTokenValid('delete' . $member->getId(), $request->request->get('_token'))) {
$em = $this->getDoctrine()->getManager();
$em->remove($member);
$em->flush();
}
return $this->redirectToRoute('member_index');
}
}
src / Form / MemberType.php
<?php
namespace App\Form;
use App\Entity\Member;
use App\Entity\ResearchProject;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MemberType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', TextType::class, array('attr' => array('class' => 'form-control')))
->add('lastName', TextType::class, array('attr' => array('class' => 'form-control')))
->add('pictureProfile', FileType::class, array(
'data_class' => null,
'attr' => array('class' => 'form-control'),
'image_property' => 'pictureProfile'))
->add('email', EmailType::class, array('attr' => array('class' => 'form-control')))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Member::class,
]);
}
}
SRC / Форма / Расширение / ImageTypeExtension.php
<?php
namespace App\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class ImageTypeExtension extends AbstractTypeExtension
{
public function getExtendedType()
{
return FileType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
// makes it legal for FileType fields to have an image_property option
$resolver->setDefined(array('image_property'));
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (isset($options['image_property'])) {
// this will be whatever class/entity is bound to your form (e.g. Media)
$parentData = $form->getParent()->getData();
$imageData = null;
if (null !== $parentData) {
$accessor = PropertyAccess::createPropertyAccessor();
$imageData = $accessor->getValue($parentData, $options['image_property']);
}
else{
$imageData = "Didn't load anything";
}
// sets an "image_data" variable that will be available when rendering this field
$view->vars['image_data'] = $imageData;
}
}
}
SRC / шаблоны / форма / fields.html.twig
{% extends 'form_div_layout.html.twig' %}
{% block file_widget %}
{% spaceless %}
{{ block('form_widget') }}
{% if image_data is not null %}
<img height = "80" alt = "No Image" src = "data:image/png;base64,{{ image_data }}" class = "img img-responsive img-preview thumbnail"/>
{% endif %}
{% endspaceless %}
{% endblock %}
SRC / шаблоны / член / edit.html.twig
{% extends 'base.html.twig' %}
{% block title %}Edit Member{% endblock %}
{% block body %}
<h1>Edit Member</h1>
{{ include('member/_form.html.twig', {'button_label': 'Update'}) }}
{{ include('member/_delete_form.html.twig') }}
<a class = "btn btn-secondary" href = "{{ path('member_index') }}">Back to list</a>
{% endblock %}
SRC / шаблоны / член / _form.html.twig
{% form_theme form 'form/fields.html.twig' %}
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
{{ form_widget(form) }}
<button class = "btn btn-primary">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}
SRC / шаблоны / член / index.html.twig
{% extends 'base.html.twig' %}
{% block title %}Member index{% endblock %}
{% block body %}
<h1>Member index</h1>
<table class = "table table-hover">
<thead>
<tr>
<!-- <th scope = "col">Id</th> -->
<th scope = "col">First Name</th>
<th scope = "col">Last Name</th>
<th scope = "col">Picture Profile</th>
<th>Email</th>
<th scope = "col">Actions</th>
</tr>
</thead>
<tbody>
{% for key, member in members %}
<tr>
<td>{{ member.firstName }}</td>
<td>{{ member.lastName }}</td>
<td><img height = "80" alt = "No Image" src = "data:image/png;base64,{{ member.pictureProfile }}" class = "img img-responsive img-preview thumbnail"/></td>
<td>{{ member.email }}</td>
<td>
<a href = "{{ path('member_show', {'id': member.id}) }}">show</a>
<a href = "{{ path('member_edit', {'id': member.id}) }}">edit</a>
</td>
</tr>
{% else %}
<tr>
<td colspan = "29">no records found</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href = "{{ path('member_new') }}">Create new</a>
{% endblock %}
config / services.yaml
services:
# ...
App\Form\Extension\ImageTypeExtension:
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FileType }
После всего этого кода я знаю, что ключ к моей проблеме находится в MemberType.php, из-за этой строки:
->add('pictureProfile', FileType::class, array(
'data_class' => null,
'attr' => array('class' => 'form-control'),
'image_property' => 'pictureProfile'))
Я получаю этот отрисованный html (обратите внимание на свойство src):
<div>
<label for = "member_pictureProfile" class = "required">Picture profile</label>
<input id = "member_pictureProfile" name = "member[pictureProfile]" required = "required" class = "form-control" type = "file">
<img alt = "No Image" src = "data:image/png;base64," class = "img img-responsive img-preview thumbnail" height = "80">
</div>
И если я изменю значение image_property на другое свойство Entity, например firstName, я уже получу значение этого свойства, например:
->add('pictureProfile', FileType::class, array(
'data_class' => null,
'attr' => array('class' => 'form-control'),
'image_property' => 'firstName'))
Получаю (опять же, обратите внимание на свойство src):
<div>
<label for = "member_pictureProfile" class = "required">Picture profile</label>
<input id = "member_pictureProfile" name = "member[pictureProfile]" required = "required" class = "form-control" type = "file">
<img alt = "No Image" src = "data:image/png;base64,John" class = "img img-responsive img-preview thumbnail" height = "80">
</div>
Итак, данные получены!
Почему не получает изображение ??
Кстати, я уже пробовал использовать метод getPictureProfile прямо в этой строке, и он не сработал.
Большое спасибо.




Без какого-либо тестирования похоже, что вы используете неправильное расширение типа.
Вы столкнулись с проблемой создания ImageTypeExtension, расширяющего FileType, но вы все еще используете FileType в своей конфигурации формы;
->add('pictureProfile', FileType::class, array(
'data_class' => null,
'attr' => array('class' => 'form-control'),
'image_property' => 'firstName'))
Разве это не должно быть что-то вроде;
->add('pictureProfile', ImageType::class, array(
'data_class' => null,
'attr' => array('class' => 'form-control'),
'image_property' => 'firstName'))
@PepeAlvarez, моя ошибка, я не осознавал, что вы использовали расширенный тип для переопределения, а не для расширения. В противном случае я не совсем уверен, что пытаюсь создать это как новый тип, а не переопределять существующий FileType.
Извините, но, согласно этому: symfony.com/doc/current/form/create_form_type_extension.html, это неверно. Или это то, что я понимаю. С другой стороны, я попробовал ваше предложение, но оно не сработало; Я получаю ошибку
Could not load type "App\Form\ImageType": class does not exist.. Или, если я заменюFileTypeнаImageTypeExtension, я получаю ошибкуCould not load type "App\Form\Extension\ImageTypeExtension": class does not implement "Symfony\Component\Form\FormTypeInterface". Все равно спасибо.