File fixture with Alice - symfony

Is there a way to have a fixture with a file, with hautelook/alice-bundle ?
I'm trying to do :
App\Entity\MediaObject:
media_object_1:
file: '<file("./fixtures/files", "./public/media", true)>'
But this is not working, because the file function return a string, and not the file itself
<?php
namespace App\Entity;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use App\Controller\CreateMediaObjectAction;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
#[Vich\Uploadable]
#[ORM\Entity]
#[ApiResource(
normalizationContext: ['groups' => ['media_object:read']],
types: ['https://schema.org/MediaObject'],
operations: [
new Get(),
new GetCollection(),
new Post(
controller: CreateMediaObjectAction::class,
deserialize: false,
validationContext: ['groups' => ['Default', 'media_object_create']],
openapiContext: [
'requestBody' => [
'content' => [
'multipart/form-data' => [
'schema' => [
'type' => 'object',
'properties' => [
'file' => [
'type' => 'string',
'format' => 'binary'
]
]
]
]
]
]
]
),
new Delete()
]
)]
class MediaObject
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
#[Groups(['media_object:read'])]
private ?int $id = null;
#[ApiProperty(types: ['https://schema.org/contentUrl'])]
#[Groups(['media_object:read'])]
public ?string $contentUrl = null;
#[Vich\UploadableField(mapping: "media_object", fileNameProperty: "filePath")]
#[Assert\NotNull(groups: ['media_object_create'])]
public ?File $file = null;
#[ORM\Column(nullable: true)]
public ?string $filePath = null;
public function getId(): ?int
{
return $this->id;
}
}
When I launch the fixture command, i got :
In HydrationExceptionFactory.php line 67:
Invalid value given for the property "file" of the object "media_object_1" (class: App\Entity\MediaObject).
In PropertyAccessor.php line 213:
Expected argument of type "?Symfony\Component\HttpFoundation\File\File", "string" given at property path "file".
In PropertyAccessor.php line 530:
Cannot assign string to property App\Entity\MediaObject::$file of type ?Symfony\Component\HttpFoundation\File\File

Related

VichUploader : MediaObject is not uploadable

I try to upload file throught a REST API with API Plateform
I followed the doc, but I got :
"The class \"App\\Entity\\MediaObject\" is not uploadable. If you use attributes to configure VichUploaderBundle, you probably just forgot to add `#[Vich\\Uploadable]` on top of your entity. If you don't use attributes, check that the configuration files are in the right place. In both cases, clearing the cache can also solve the issue.",
I'am using :
"api-platform/core": "^3.0",
"vich/uploader-bundle": "^2.0"
My config :
# api/config/packages/vich_uploader.yaml
vich_uploader:
db_driver: orm
metadata:
type: attribute
mappings:
media_object:
uri_prefix: /media
upload_destination: '%kernel.project_dir%/public/media'
namer: Vich\UploaderBundle\Naming\OrignameNamer
My entity :
<?php
// api/src/Entity/MediaObject.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use App\Controller\CreateMediaObjectAction;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
#[Vich\Uploadable]
#[ORM\Entity]
#[ApiResource(
normalizationContext: ['groups' => ['media_object:read']],
types: ['https://schema.org/MediaObject'],
operations: [
new Get(),
new GetCollection(),
new Post(
controller: CreateMediaObjectAction::class,
deserialize: false,
validationContext: ['groups' => ['Default', 'media_object_create']],
openapiContext: [
'requestBody' => [
'content' => [
'multipart/form-data' => [
'schema' => [
'type' => 'object',
'properties' => [
'file' => [
'type' => 'string',
'format' => 'binary'
]
]
]
]
]
]
]
)
]
)]
class MediaObject
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
#[ApiProperty(types: ['https://schema.org/contentUrl'])]
#[Groups(['media_object:read'])]
public ?string $contentUrl = null;
#[Vich\UploadableField(mapping: "media_object", fileNameProperty: "filePath")]
#[Assert\NotNull(groups: ['media_object_create'])]
public ?File $file = null;
#[ORM\Column(nullable: true)]
public ?string $filePath = null;
public function getId(): ?int
{
return $this->id;
}
}
In my case this resolved the problem:
composer require doctrine/annotations

PDF document creation EasyAdmin symfony 5

I have this configuration which allows me to create a pdf document in the CRUD, is there a way to add this code in the CRUD easyAdmin or link the CRUD of my EasyAdmin documentos to the CRUD of symfony.
I have problems creating the document in the EasyAdmin table
DocumentController.php
<?php
namespace App\Controller;
use App\Entity\Document;
use App\Form\DocumentType;
use App\Repository\DocumentRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\FileUploader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
/**
* #IsGranted("ROLE_USER")
* #Route("/documents")
*/
class DocumentController extends AbstractController
{
/**
* #Route("/", name="document_index", methods={"GET"})
*/
public function index(DocumentRepository $documentRepository): Response
{
return $this->render('document/index.html.twig', [
'documents' => $documentRepository->findAll([], ['created_at' => 'desc']),
]);
}
/**
* #Route("/new", name="document_new", methods={"GET","POST"})
*/
public function new(Request $request, FileUploader $fileUploader): Response
{
$document = new Document();
$document->setCreatedAt(new \DateTime('now'));
$form = $this->createForm(DocumentType::class, $document);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$file = $form['fileDocument']->getData();
$originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
// this is needed to safely include the file name as part of the URL
$fileName = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
$fileName = md5(uniqid()) . '.' . $file->guessExtension();
$file->move(
$this->getParameter('brochures_directory'),
$fileName
);
$document->setFileDocument($fileName);
$entityManager->persist($document);
$entityManager->flush();
return $this->redirectToRoute('document_index', array('id' => $document->getId()));
}
return $this->render('document/new.html.twig', [
// 'document' => $document,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="document_show", methods={"GET"})
*/
public function show(Document $document): Response
{
return $this->render('document/show.html.twig', [
'document' => $document,
]);
}
/**
* #Route("/{id}/edit", name="document_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Document $document): Response
{
$form = $this->createForm(DocumentType::class, $document);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$file = $form['fileDocument']->getData();
$originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
// this is needed to safely include the file name as part of the URL
$fileName = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
$fileName = md5(uniqid()) . '.' . $file->guessExtension();
$file->move(
$this->getParameter('brochures_directory'),
$fileName
);
$document->setFileDocument($fileName);
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('document_index');
}
return $this->render('document/edit.html.twig', [
'document' => $document,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="document_delete", methods={"DELETE"})
*/
public function delete(Request $request, Document $document): Response
{
if ($this->isCsrfTokenValid('delete' . $document->getId(), $request->request->get('_token'))) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($document);
$entityManager->flush();
}
return $this->redirectToRoute('document_index');
}
}
DocumentCrudController Easy Admin
<?php
namespace App\Controller\Admin;
use App\Entity\Document;
use App\Entity\Publication;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Validator\Constraints\File;
class DocumentCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Document::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setPageTitle(Crud::PAGE_INDEX, 'Liste de Documents')
;
}
public function configureFields(string $pageName): iterable
{
ImageField::new('fileDocument', 'Document PDF')->setFormType(FileType::class)
->setBasePath('docs');
return [
IdField::new('id')->onlyOnIndex(),
TextField::new('nomDocument', 'Titre'),
DateTimeField::new('created_at', 'Date de création'),
TextField::new('fileDocument', 'Document PDF')
->hideOnIndex()
->setFormType(FileType::class, [
'constraints' => [
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'application/pdf',
'application/x-pdf',
],
'mimeTypesMessage' => 'Veuillez télécharger un document PDF valide',
])
],
]),
];
}
public function configureActions(Actions $actions): Actions
{
return $actions
->add(Crud::PAGE_INDEX, Action::DETAIL);
}
}
I don't know how I can implement the same configuration in easy admin.
Look Here this is what happens when i create a document from table EasyAdmin.
Thank you.
That's a little weird but you need to use the ImageField (https://symfony.com/bundles/EasyAdminBundle/current/fields/ImageField.html)
And in you CrudController:
public function configureFields(string $pageName): iterable{
return [
ImageField::new('pdf', 'Your PDF')
->setFormType(FileUploadType::class)
->setBasePath('documents/') //see documentation about ImageField to understand the difference beetwen setBasePath and setUploadDir
->setUploadDir('public/documents/')
->setColumns(6)
->hideOnIndex()
->setFormTypeOptions(['attr' => [
'accept' => 'application/pdf'
]
]),
];
}
See documentation about ImageField to understand the difference beetwen setBasePath and setUploadDir
----- EDIT ----
In your index page of CRUD, you can create a link for your file like this:
public function configureFields(string $pageName): iterable{
return [
ImageField::new('pdf', 'Your PDF')
->setFormType(FileUploadType::class)
->setBasePath('documents/') //see documentation about ImageField to understand the difference beetwen setBasePath and setUploadDir
->setUploadDir('public/documents/')
->setColumns(6)
->hideOnIndex()
->setFormTypeOptions(['attr' => [
'accept' => 'application/pdf'
]
]),
TextField::new('pdf')->setTemplatePath('admin/fields/document_link.html.twig')->onlyOnIndex(),
];
}
Your templates/admin/fields/document_link.html.twig :
{% if field.value %}
Download file
{% else %}
--
{% endif %}

EntityType - Can not use query_builder

I have a problem with my FormType in Symfony. I have a field that allows me to check one or more checkboxs whose values represent objects from another entity. (In this case, types of holidays).
Except that I do not want to display them all, so I use a query_builder as such:
->add('typesConges', EntityType::class, [
'class' => TypeConge::class,
'choice_label' => 'nom',
'expanded' => true,
'multiple' => true,
'query_builder' => function (TypeCongeRepository $repoTypes) {
return $repoTypes->getTypesNotNull();
}
])
But it raised this error :
The name "Heures supp" contains illegal characters. Names should start
with a letter, digit or underscore and only contain letters, digits,
numbers, underscores ("_"), hyphens ("-") and colons (":").
However, if I remove the query_builder, I have all my TypeConge ( the "Heures supp" aswell).
GestionSoldes.php
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
class GestionSoldes
{
/**
* Types de congés
*
* #var Collection|TypeConge[]
*/
private $typesConges;
/**
* All types
*
* #var boolean
*/
private $allTypes;
public function __construct()
{
$this->typesConges = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
/**
* #return Collection|TypeConge[]
*/
public function getTypesConges(): Collection
{
return $this->typesConges;
}
public function addTypesConge(TypeConge $typesConge): self
{
if (!$this->typesConges->contains($typesConge)) {
$this->typesConges[] = $typesConge;
}
return $this;
}
public function removeTypesConge(TypeConge $typesConge): self
{
if ($this->typesConges->contains($typesConge)) {
$this->typesConges->removeElement($typesConge);
}
return $this;
}
public function getAllTypes(): ?bool
{
return $this->allTypes;
}
public function setAllTypes(bool $allTypes): self
{
$this->allTypes = $allTypes;
return $this;
}
}
Form:
<?php
namespace App\Form;
use App\Entity\TypeConge;
use App\Entity\GestionSoldes;
use App\Repository\TypeCongeRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
class GestionSoldesType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('typesConges', EntityType::class, [
'class' => TypeConge::class,
'choice_label' => 'nom',
'expanded' => true,
'multiple' => true,
'query_builder' => function (TypeCongeRepository $repoTypes) {
return $repoTypes->getTypesNotNull();
}
])
->add('allTypes', CheckboxType::class, [
'required' => false,
'label' => 'Tous les types de congés',
'label_attr' => [
'class' => 'custom-control-label',
'for' => 'allTypes'
],
'attr' => [
'class' => 'custom-control-input',
'id' => 'allTypes'
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => GestionSoldes::class,
]);
}
}
My repo function:
/**
* Retourne les types de congés qui ont un solde initial différent de null ( pour form EntityType )
*
* #return void
*/
public function getTypesNotNull()
{
return $this->createQueryBuilder('t')
->where('t.soldeInitial is not null')
->orderBy('t.nom', 'ASC');
}
As it shows no errors when you remove te query-builer, The problem must come from the query-builder.
As the only moment you use the property nom in the query-builder is in
->orderBy('t.nom', 'ASC');
The problem is here.
Make sure that your ORM (doctrine or else) is ok with string containing spaces.
to verify my point try to remove the orderBy from your query

Guess custom form type when embedding custom classes

Two simple classes form my app model: Money and Product.
As Money app form being reusable, I've decided to create MoneyType extending AbstractType.
// App\Entity\Product
/**
* #ORM\Embedded(class="Money\Money")
*/
private $price;
// App\Form\ProductType
$builder->add('price', MoneyType::class)
// App\Form\Type\MoneyType
class MoneyType extends AbstractType
{
private $transformer;
public function __construct(MoneyToArrayTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('amount', NumberType::class, [
'html5' => true,
'constraints' => [
new NotBlank(),
new PositiveOrZero(),
],
'attr' => [
'min' => '0',
'step' => '0.01',
],
])
->add('currency', ChoiceType::class, [
'choices' => $this->getCurrenciesChoices(),
'constraints' => [
new NotBlank(),
],
]);
$builder->addModelTransformer($this->transformer);
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'data_class' => null
]);
}
...
}
Is it possible to guess the field type without specifying it explicitly for obtaining the following code?
// App\Form\ProductType
$builder->add('price')
Any help is welcome. Thank you in advance.
You can implement a custom TypeGuesser that reads the doctrine metadata and checks if the field is an embeddable of the desired type. This is a basic implementation
namespace App\Form\TypeGuesser;
use App\Form\Type\MoneyType;
use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\TypeGuess;
use Symfony\Component\Form\FormTypeGuesserInterface;
use Doctrine\ORM\EntityManagerInterface;
class MoneyTypeGuesser implements FormTypeGuesserInterface
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function guessType($class, $property)
{
if (!$metadata = $this->em->getClassMetadata($class)) {
return null;
}
if (
isset($metadata->embeddedClasses[$property]) &&
'Money\Money' == $metadata->embeddedClasses[$property]['class']
) {
return new TypeGuess(MoneyType::class, [], Guess::HIGH_CONFIDENCE);
}
}
// Other interface functions ommited for brevity, you can return null
}
You can see all the interface methods that you need to implement here.
The Form TypeGuesser is mostly based on the annotation #var Money\Money and you should be able to build your down guesser for your own types, see https://symfony.com/doc/current/form/type_guesser.html
Also take a look at https://github.com/symfony/symfony/blob/4.3/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php on how to guess the type by doctrine orm specific types.
You could derive your own app specific guesser with those two examples.

Object vs array result

In my search filter (FormBuilder) to return a list of filtering results, I have a form with a relation to an object (multiple checkbox). It doesn't work.
I think I have a problem in my repository to get an array vs object result.
<?php
namespace AdminBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints as Assert;
use AdminBundle\Entity\Products;
use AdminBundle\Form\ProductsType;
class ProductsController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$form = $this->createFormBuilder()
->add('category', 'entity', array(
'class' => 'AdminBundle:Category',
'label' => false,
'choice_label' => 'name',
'required' => false,
'expanded' => true,
'placeholder' => 'Tous',
'multiple' => true
))
// ...
;
This is my repository:
<?php
namespace AdminBundle\Repository;
class ProductsRepository extends \Doctrine\ORM\EntityRepository
{
public function ListAll($data)
{
$query = $this->createQueryBuilder('a')->orderBy('a.updated','DESC');
// CATEGORY
if($data['category'] !== null)
{
$query
->innerJoin('a.category', 'cat')
->andWhere('cat.id = :catid')
->setParameter('catid', $data['category'])
;
/*
$query
->leftJoin('a.category', 'category')
->setParameter('category', $data['category'])
;
*/
}
// ...
}
}
This repository works for just a single select: multiple => false. When I set multiple => true, I get an SQL violation about object vs. array.
How can I deal with the repository for multiple checkboxes?
You must use ChoiceType instead of entityType
class CategoryRepository extends \Doctrine\ORM\EntityRepository
{
public function getForSearch()
{
$qb = $this->createQueryBuilder('category');
$qb->orderBy('category.name');
$query = $qb->getQuery();
$results = $query->getResult();
$categories = array();
foreach ($results as $category) {
$categories[$category->getName()] = $category->getId();
}
return $categories;
}
In form
$categories = $em->getRepository(Category::class)->getForSearch();
->add('categories', ChoiceType::class, array(
'choices' => $categories,
'required' => false,
'placeholder' => 'Select',
))
And in controller :
if ($search_form->isSubmitted() && $search_form->isValid()) {
$data = $search_form->getData();
$em->getRepository(Product::class)->ListAll($data);
I also think we can use a data transformer but I do not how to do that

Resources