I am using the Sonata Admin back-end and I would like to add a new image field to my user entity which is an avatar. Since I am already using the SonataMediaBundle I followed this tutorial: https://sonata-project.org/blog/2013/10/11/mediabundle-mediatype-improved
Here is my entity configuration:
/**
* #var \Application\Sonata\MediaBundle\Entity\Media
*
* #ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Media", cascade={"all"}, fetch="LAZY")
* #ORM\JoinColumn(name="avatar_id", referencedColumnName="id")
*/
protected $avatar;
Unfortunately I have many problems:
In my back-end the preview is not shown:
If I delete the media in the gallery I receive this error when editing the user: Entity of type 'Application\Sonata\MediaBundle\Entity\Media' for IDs id(6) was not found
The resulting API (generated with FOSRestBundle) is unusable by the client:
"avatar": {
"provider_metadata": {
"filename": "Test.png"
},
"name": "Test.png",
"description": null,
"enabled": false,
"provider_name": "sonata.media.provider.image",
"provider_status": 1,
"provider_reference": "325564b03489a6473e7c9def01dc58bab611eccb.png",
"width": 1430,
"height": 321,
"length": null,
"copyright": null,
"author_name": null,
"context": "default",
"cdn_is_flushable": null,
"cdn_flush_at": null,
"cdn_status": null,
"updated_at": "2017-08-08T12:31:19+02:00",
"created_at": "2017-08-08T12:31:19+02:00",
"content_type": "image/png",
"size": 24978,
"id": 7
}
I resolved all the 3 problems! I put here my solutions for all those who have the same difficulties.
In my back-end the preview is not shown:
As explained here I have to add a custom form widget to my config.yml file:
twig:
# Sonata form themes
form_themes:
- 'SonataMediaBundle:Form:media_widgets.html.twig'
And in my UserAdmin:
->with('Profile')
->add('avatar', 'sonata_media_type', array(
'provider' => 'sonata.media.provider.image',
'context' => 'default',
))
->end()
Now the preview will be shown :)
If I delete the media in the gallery I receive this error when editing the user: Entity of type
'Application\Sonata\MediaBundle\Entity\Media' for IDs id(6) was not
found
As explained here I need to add onDelete="SET NULL" on my entity:
/**
* #var \Application\Sonata\MediaBundle\Entity\Media
*
* #ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Media", cascade={"persist"}, fetch="LAZY")
* #ORM\JoinColumn(name="avatar_id", referencedColumnName="id", onDelete="SET NULL")
*/
protected $avatar;
The resulting API (generated with FOSRestBundle) is unusable by the client:
This one was very tricky but I was able to implement a custom JMS handler getting started from this post.
I peeked into the SonataMediaBundle source code and I found this snippet:
/**
* Returns media urls for each format.
*
* #ApiDoc(
* requirements={
* {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="media id"}
* },
* statusCodes={
* 200="Returned when successful",
* 404="Returned when media is not found"
* }
* )
*
* #param $id
*
* #return array
*/
public function getMediumFormatsAction($id)
{
$media = $this->getMedium($id);
$formats = array(MediaProviderInterface::FORMAT_REFERENCE);
$formats = array_merge($formats, array_keys($this->mediaPool->getFormatNamesByContext($media->getContext())));
$provider = $this->mediaPool->getProvider($media->getProviderName());
$properties = array();
foreach ($formats as $format) {
$properties[$format]['url'] = $provider->generatePublicUrl($media, $format);
$properties[$format]['properties'] = $provider->getHelperProperties($media, $format);
}
return $properties;
}
So I included it into my source and the complete handler is the following:
<?php
namespace AppBundle\Serializer;
use Application\Sonata\MediaBundle\Entity\Media;
use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\JsonSerializationVisitor;
use Sonata\MediaBundle\Provider\MediaProviderInterface;
class MediaHandler implements SubscribingHandlerInterface
{
private $mediaPool;
public function __construct($mediaPool)
{
$this->mediaPool = $mediaPool;
}
public static function getSubscribingMethods()
{
return array(
array(
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => 'Application\Sonata\MediaBundle\Entity\Media',
'method' => 'serializeToJson',
),
);
}
public function serializeToJson(JsonSerializationVisitor $visitor, Media $media, array $type, Context $context)
{
$formats = array(MediaProviderInterface::FORMAT_REFERENCE);
$formats = array_merge($formats, array_keys($this->mediaPool->getFormatNamesByContext($media->getContext())));
$provider = $this->mediaPool->getProvider($media->getProviderName());
$properties = array();
foreach ($formats as $format) {
$properties[$format]['url'] = $provider->generatePublicUrl($media, $format);
$properties[$format]['properties'] = $provider->getHelperProperties($media, $format);
}
return $properties;
}
}
Service settings:
app.serializer.media:
class: AppBundle\Serializer\MediaHandler
arguments:
- '#sonata.media.pool'
tags:
- { name: jms_serializer.subscribing_handler }
And that's all!
Related
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 %}
I've inherited some code which uses Symfony (v3.3) to generate forms. Some elements are being created with no space between the element type and the auto-generated ID. This means the element doesn't display:
<selectid="someID">
...
</selectid="someID">
This is happening on select elements and textarea elements.
I'm not familiar with Symfony so don't know how to troubleshoot this... any help is much appreciated!
Edit: added code as requested. The problem is I don't know where the issue lies and there are a lot of classes.
Twig template
<form action="" method="post" name="callback" id="request-callback" class="contact-form">
<input type="hidden" name="form-type" value="callback">
{#<input type="hidden" name="mc4wp-subscribe" value="1">#}
<div{% if form.name.vars.errors | length > 0 %} class="form-error"{% endif %}>
{{ form_label(form.name) }} {{ form_errors(form.name) }}
{{ form_widget(form.name) }}
</div>
<div{% if form.phone_number.vars.errors | length > 0 %} class="form-error"{% endif %}>
{{ form_label(form.phone_number) }} {{ form_errors(form.phone_number) }}
{{ form_widget(form.phone_number) }}
</div>
<div{% if form.email.vars.errors | length > 0 %} class="form-error"{% endif %}>
{{ form_label(form.email) }} {{ form_errors(form.email) }}
{{ form_widget(form.email) }}
</div>
<div{% if form.treatment.vars.errors | length > 0 %} class="form-error"{% endif %}>
{{ form_label(form.treatment) }} {{ form_errors(form.treatment) }}
{{ form_widget(form.treatment) }}
</div>
<div class="text-center">
<button class="button bg-darkblue" type="submit" id="contact_send" name="contact[send]">Send My Request</button>
</div>
</form>
Form class
<?php
namespace REDACTED;
use DrewM\MailChimp\MailChimp;
use GuzzleHttp\Exception\ConnectException;
use Symfony\Component\Form\Forms;
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Translation\Translator;
use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Form\TwigRenderer;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Validator\Validation;
use GuzzleHttp\Client;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormFactoryInterface;
abstract class Form
{
/**
* Recaptcha endpoint
*/
const RECAPTCHA_VERIFY = 'https://www.google.com/recaptcha/api/siteverify';
/**
* Default from name
*/
const EMAIL_FROMNAME = '';
/**
* #var \Twig_Environment
*/
protected $twig;
/**
* #var \Symfony\Component\Form\FormInterface
*/
protected $form;
/**
* #var \Symfony\Component\HttpFoundation\Request
*/
private $request;
/**
* Capture failed
*
* #var bool
*/
protected $captchaFailed = false;
/**
* #var string
*/
protected $template;
/**
* #var string
*/
protected $messageTemplate;
/**
* #var string
*/
protected $subject;
/**
* #var string
*/
protected $emailTo;
/**
* #var string
*/
protected $emailFromName;
/**
* #var array
*/
protected $params = [];
protected $mailchimpList;
private $mailchimpApiKey = '6542760048f1c73d69df8f552d4a2b87-us18';
public $mailerError;
public $redirectTo;
/**
* SunstoneForm constructor
*
* #param Request $request
* #param $emailTo
* #param $emailFromName
* #param array $params
*/
private function __construct(
Request $request = null,
$emailTo = null,
$emailFromName = null,
array $params = []
) {
$this->request = $request;
$this->emailTo = $emailTo;
$this->emailFromName = $emailFromName;
$this->params = $params;
}
/**
* Make the contact form
*
* #param Request $request
* #param string $emailTo
* #param string $emailFromName
* #param array $params
* #return static
*/
public static function make(
Request $request = null,
$emailTo = null,
$emailFromName = self::EMAIL_FROMNAME,
array $params = []
) {
return (new static($request, $emailTo, $emailFromName, $params))
->twig()
->form();
}
/**
* Render the form
*
* #return string
*/
public function renderForm()
{
return $this->twig->render($this->template, [
'form' => $this->form->createView(),
'captchaFailed' => $this->captchaFailed,
]);
}
/**
* Handle a form submission and check form is valid
*
* #return bool
*/
public function handleRequest()
{
$this->form->handleRequest($this->request);
if ($this->form->isSubmitted() && $this->form->isValid()) {
// send the message
return $this->process();
}
return false;
}
/**
* Instantiate Twig
*
* #return $this
*/
protected function twig()
{
// instantiate twig
$translator = new Translator('en');
$loader = new \Twig_Loader_Filesystem([
TWIG_TEMPLATE_DIR,
ABSPATH.'vendor/symfony/twig-bridge/Resources/views/Form',
]);
$twig = new \Twig_Environment($loader, [
'debug' => WP_DEBUG,
]);
$twig->addExtension(new FormExtension());
$twig->addExtension(new TranslationExtension($translator));
if (WP_DEBUG) {
$twig->addExtension(new \Twig_Extension_Debug);
}
// get form engine
$formEngine = new TwigRendererEngine(['form_div_layout.html.twig'], $twig);
$twig->addRuntimeLoader(new \Twig_FactoryRuntimeLoader([
TwigRenderer::class => function() use ($formEngine) {
return new TwigRenderer($formEngine);
},
]));
$this->twig = $twig;
return $this;
}
public function getForm()
{
return $this->form;
}
public function getSubmissionComplete()
{
return sprintf('<div class="form-sent">%s</div>',
get_field('form_submitted_content', 'options')
);
}
/**
* Generate the form
*
* #return $this
*/
protected function form()
{
$this->form = $this->formFields(
Forms::createFormFactoryBuilder()
->addExtension(new HttpFoundationExtension)
->addExtension(new ValidatorExtension(Validation::createValidator()))
->getFormFactory()
)
->getForm();
return $this;
}
/**
* #param array $additionalData
* #return bool
*/
protected function process(array $additionalData = [])
{
$data = $this->form->getData();
$mailer = new \PHPMailer(true);
$mailer->addAddress($this->emailTo);
if (WP_DEBUG && defined('DEBUG_BCC')) {
$mailer->addBCC(DEBUG_BCC);
}
$mailer->From = $this->emailTo;
$mailer->FromName = 'drpuneetgupta.co.uk';
$mailer->Subject = $this->subject;
$mailer->Body = $this->twig->render($this->messageTemplate, [
'data' => $data + $additionalData,
]);
$mailer->isHTML(true);
if ($this->mailchimpList) {
try {
$mailchimp = new MailChimp($this->mailchimpApiKey);
$mailchimp->post("lists/{$this->mailchimpList}/members", [
'email_address' => $data['email'],
'status' => 'subscribed',
]);
} catch (\Exception $e) {}
}
try {
return $mailer->send();
} catch (\phpmailerException $e) {
$this->mailerError = $e->getMessage();
}
return false;
}
/**
* Define form fields
*
* #param FormFactoryInterface $formFactory
* #return mixed
*/
abstract protected function formFields(FormFactoryInterface $formFactory);
}
RequestCallback extends Form class
<?php
namespace REDACTED;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Email;
use DrewM\MailChimp\MailChimp;
class RequestCallback extends Form
{
protected $template = 'request-callback.twig';
protected $messageTemplate = 'email-callback.twig';
protected $mailchimpList = 'REDACTED';
protected $subject = 'Callback request';
/**
* #param FormFactoryInterface $formFactory
* #return FormBuilderInterface
*/
protected function formFields(FormFactoryInterface $formFactory)
{
return $formFactory->createNamedBuilder('request_callback', FormType::class, null, [
'allow_extra_fields' => true,
])
->add('mc4wp-subscribe', HiddenType::class, [
'data' => 1,
])
->add('name', TextType::class, [
'required' => true,
'label' => 'Your Name',
'attr' => [
'placeholder' => 'Your Name',
],
'label_attr' => [
'class' => 'sr-only',
],
'constraints' => [
new NotBlank(['message' => 'Please enter your name']),
],
])
->add('phone_number', TextType::class, [
'required' => true,
'label' => 'Phone Number',
'attr' => [
'placeholder' => 'Phone Number',
],
'label_attr' => [
'class' => 'sr-only',
],
'constraints' => [
new NotBlank(['message' => 'Please enter your phone number']),
],
])
->add('email', EmailType::class, [
'required' => true,
'label' => 'Your email address',
'attr' => [
'placeholder' => 'Email address',
],
'label_attr' => [
'class' => 'sr-only',
],
'constraints' => [
new NotBlank(['message' => 'Please enter your email address']),
new Email(['message' => 'Please enter a valid email address']),
],
])
->add('treatment', ChoiceType::class, [
'required' => true,
'label' => 'Which treatment would you like to discuss?',
'label_attr' => [
'class' => 'sr-only',
],
'constraints' => [
new NotBlank(['message' => 'Please select a treatment']),
],
'choices' => [
'Which treatment would you like to discuss?' => '',
'Liposuction' => 'Liposuction',
'Lipoedema' => 'Lipoedema',
'Breast reduction' => 'Breast reduction',
'Male chest reduction' => 'Male chest reduction',
],
]);
}
}
I thought I'll create an answer for this as finding the right answer in a comment is not straightforward.
As #DarkBee mentions in one of the question comments the fix on the question PHP 7.4 trimming whitespace between string variables solves this issue.
There is a fix in Twig that prevents the whitespace from being trimmed so updating to a recent Twig version fixes the issue:
composer require "twig/twig:^2.0"
I work on a Symfony 3.4 project and I have a issue with a specific Sonata Admin case, here is my situation :
I have a Lesson entity who have elements :
/**
* #ORM\OneToMany(targetEntity="App\Entity\Lessons\ElementLesson", mappedBy="lesson", cascade={"persist"})
*/
protected $elements;
Elements is abstract class with Inheritance
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({
* "element_lesson" = "ElementLesson",
* "popup_element_lesson" = "PopupElementLesson",
* "advice_element_lesson" = "AdviceElementLesson",
* "accordion_element_lesson" = "AccordionElementLesson",
* "title_element_lesson" = "TitleElementLesson",
* "text_element_lesson" = "TextElementLesson",
* "separator_element_lesson" = "SeparatorElementLesson"
* })
*/
abstract class ElementLesson
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Lessons\Lesson", inversedBy="elements")
* #ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $lesson;
So I want in my Sonata admin, create a Lesson and add some Element to her, for that I process like this :
I define this 2 services :
app.admin.lesson.all:
class: App\Admin\Lessons\LessonAdmin
arguments: [~, App\Entity\Lessons\Lesson, ~]
tags:
- { name: sonata.admin, manager_type: orm, label: Cours }
public: true
app.admin.lesson.element:
class: App\Admin\Lessons\ElementLessonAdmin
arguments: [~, App\Entity\Lessons\ElementLesson, ~]
tags:
- { name: sonata.admin, manager_type: orm, group: lesson, label: Element }
calls:
- [setSubclasses, [{'TitleElementLesson': App\Entity\Lessons\TitleElementLesson, 'TextElementLesson': App\Entity\Lessons\TextElementLesson}]]
public: true
And create a ElementLessonAdmin :
class ElementLessonAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $form)
{
$subject = $this->getSubject();
if ($subject instanceof TitleElementLesson) {
$form->add('title');
}
if ($subject instanceof TextElementLesson) {
$form->add('text');
}
}
And LessonAdmin :
class LessonAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Contenu', ['class' => 'col-md-9'])
->add('elements', CollectionType::class, array(
'btn_add' => 'New Element',
'type_options' => array(
// Prevents the "Delete" option from being displayed
'delete' => false,
'delete_options' => array(
// You may otherwise choose to put the field but hide it
'type' => 'hidden',
// In that case, you need to fill in the options as well
'type_options' => array(
'mapped' => false,
'required' => false,
)
)
)
), array(
'edit' => 'standard',
'inline' => 'natural',
'sortable' => 'position'
))
->end()
;
}
public function toString($lesson)
{
return $lesson instanceof Lesson
? $lesson->getTitle()
: 'Cours'; // shown in the breadcrumb on the create view
}
public function prePersist($lesson)
{
foreach ($lesson->getElements() as $element) {
$element->setLesson($lesson);
}
}
public function preUpdate($lesson)
{
foreach ($lesson->getElements() as $element) {
$element->setLesson($lesson);
}
}
}
The result looks like what I want :
Result 1
Result 2
Result 3
But I have no connection between the 2 Entities :
Error 1
Error 2
I'm not sure than what I want it's possible like that or if I need to do that with a other method.
Thanks
I am building a custom module for Drupal 8.4.4 and is not detecting the hook_theme from a block. I get an error message saying "Theme hook gardentheme not found".
If I uninstall the module and install it again, it works fine, but as soon as I clear the cache, it doesn't find the theme_hook anymore.
I notice that die() and exit; wont do anything on my .module file after clearing the cache, I feel as after clearing the cache the .module is not run anymore.
My module file called garden.module
<?php
/**
* #file
*
*/
/**
* Implements hook_theme()
*/
function garden_theme($existing, $type, $theme, $path){
return array('gardentheme' =>
array(
'variables' => array(
'description' => NULL
),
)
);
}
My block placed on src/Plugin/Block/GardenScheduleBlock.php
<?php
namespace Drupal\garden\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'GardenSchedule' Block.
*
* #Block(
* id = "garden_schedule_block",
* admin_label = #Translation("Garden Schedule"),
* category = #Translation("Garden Schedule_Category"),
* )
*/
class GardenScheduleBlock extends BlockBase {
/**
* {#inheritdoc}
*/
public function build() {
return array(
'#theme' => 'gardentheme',
'#description' => "description test"
);
}
}
Thanks in advance for any tips.
public function build() {
$renderable = [
'#theme' => 'my_template',
'#test_var' => 'test variable',
];
return $renderable;
}
try this
I have been struggling to create a custom entity type programmatically in Drupal 8.2.x. The entity type should be translatable and revisionable.
Creating a default configuration (https://www.drupal.org/node/2087879) wouldn't solve the problem, as it doesn't allow defining a class with its handlers.
When trying an example from a book (Drupal 8 Development Cookbook, 2016 by Matt Glaman, Chapter Creating a content entity type), after enabling the module got the error:
Fatal error: Declaration of Drupal\mymodule\Entity\Message::baseFieldDefinitions(Drupal\mymodule\Entity\EntityTypeInterface $entity_type) must be compatible with Drupal\Core\Entity\FieldableEntityInterface::baseFieldDefinitions(Drupal\Core\Entity\EntityTypeInterface $entity_type) in /srv/www/vhosts/test/modules/custom/mymodule/src/Entity/Message.php on line 45
It looked as if the code in the book was outdated. So I was wondering what made the code incompatible. If someone could help me out, please...
The class definition and declaration was as below.
/**
* #file Contains \Drupal\mymodule\Entity\Message
*/
namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\ContentEntityBase;
/**
* Defines the profile entity class.
*
* #ContentEntityType(
* id = "message",
* label = #Translation("Message"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageListBuilder",
* "form" = {
* "default" = "Drupal\Core\Entity\ContentEntityForm",
* "add" = "Drupal\Core\Entity\ContentEntityForm",
* "edit" = "Drupal\Core\Entity\ContentEntityForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* base_table = "message",
* fieldable = TRUE,
* entity_keys = {
* "id" = "message_id",
* "label" = "title",
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/messages/{message}",
* "edit-form" = "/messages/{message}/edit",
* "delete-form" = "/messages/{message}/delete",
* "collection" = "/admin/content/messages"
* },
* )
*/
/**
* {#inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface entity_type) {
$fields['message_id'] = BaseFieldDefinition::create('integer')
->setLabel(t('Message ID'))
->setDescription(t('The message ID.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language code'))
->setDescription(t('The message language code.'))
->setRevisionable(TRUE);
$fields['uuid'] = BaseFieldDefinition::create('uuid')
->setLabel(t('UUID'))
->setDescription(t('The message UUID.'))
->setReadOnly(TRUE);
$fields['title'] = BaseFieldDefinition::create('string')
->setLabel(t('Title'))
->setRequired(TRUE)
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setSetting('max_length', 255)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'string',
'weight' => -5,
))
->setDisplayOptions('form', array(
'type' => 'string_textfield',
'weight' => -5,
))
->setDisplayConfigurable('form', TRUE);
$fields['content'] = BaseFieldDefinition::create('text_long')
->setLabel(t('Content'))
->setDescription(t('Content of the message'))
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'text_default',
'weight' => 0,
))
->setDisplayConfigurable('view', TRUE)
->setDisplayOptions('form', array(
'type' => 'text_textfield',
'weight' => 0,
))
->setDisplayConfigurable('form', TRUE);
return $fields;
}
Author here, sorry about that. I think the errata has since been updated. But here is a fix.
It looks like the reason is that EntityTypeInterface was not imported. Try adding use Drupal\Core\Entity\EntityTypeInterface; to the beginning of your code.
<?php
/**
* #file Contains \Drupal\mymodule\Entity\Message
*/
namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
You forgot the class definition like:
class MyEntity extends ContentEntityBase {}