У меня есть много-много отношений между двумя объектами, пользователем и курсом. У меня есть таблица соединений UserCourse для отношений, в которых есть дополнительные поля для даты начала и даты истечения срока действия. Если я настрою отношения ManyToMany в Doctrine, я могу создать форму EntityTypeform, позволяющую добавлять и удалять курсы от пользователя, но я не могу заставить ее установить срок действия UserCourse, который необходимо установить с использованием даты создания записи UserCourse + a Значение термина subScriptionExpiry из сущности «Курс» (строка типа «+1 год»). Я пробовал через PrePersist, но он не срабатывает при обновлении коллекции.
/**
* User
*
* @ORM\Table(name = "user")
*/
class User
{
/**
* @ORM\ManyToMany(targetEntity = "Course", inversedBy = "users")
* @JoinTable(name = "course_user",
* joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
* inverseJoinColumns = {@JoinColumn(name = "course_id", referencedColumnName = "id")}
* )
*/
private $courses;
/**
* Course
*
* @ORM\Table(name = "course")
*/
class Course
/**
* @ORM\ManyToMany(targetEntity = "User", mappedBy = "users")
*/
private $users;
/**
* @var string|null
*
* @ORM\Column(name = "subscription_expiry", type = "string", length=20, nullable=true)
*/
private $subscriptionExpiry;
/**
* CourseUser
*
* @ORM\Table(name = "course_user")
* @ORM\HasLifecycleCallbacks()
*/
class CourseUser
{
/**
* @var DateTime
*
* @Gedmo\Timestampable(on = "create")
* @ORM\Column(name = "created_at", type = "datetime", nullable=false, options = {"default": "CURRENT_TIMESTAMP"})
*/
private $createdAt;
/**
* @var DateTime|null
*
* @ORM\Column(name = "expiry", type = "datetime", nullable=true)
*/
private $expiry;
/**
* Constructor
*/
public function __construct()
{
$this->createdAt = new DateTime();
}
/**
* @ORM\PrePersist
*/
public function updateExpiry()
{
$this->expiry = new DateTime($this->getCourse()->getSubscriptionExpiry());
}
// UserController Create form
$form = $this->createForm(UserFormType::class, $user);
...
return $this->render('user/editb.html.twig', [
'userForm' => $form->createView()
]);
// UserFormType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(...) // some user fields
->add('courses', EntityType::class, [
'class' => Course::class,
'multiple' => true,
'expanded' => true,
'choice_label' => 'name',
'label' => 'Courses',
'query_builder' => function(CourseRepository $repo) {
return $repo->createIsActiveQueryBuilder();
}
])
;
{{ form_start(userForm) }}
...
{{ form_row(userForm.courses) }}
<button type = "submit" class = "btn btn-primary" formnovalidate>Save</button>
{{ form_end(userForm) }}

Если я настрою его как OneToMany/ManyToOne, я смогу создать встроенную форму CollectionType, но мне нужно запретить редактирование существующих записей UserCourse, а CollectionType отображает поле курса записей UserCourse как поля выбора, в которых курс можно изменить.
/**
* User
*
* @ORM\Table(name = "user")
*/
class User
{
/**
* @var Collection
*
* @ORM\OneToMany(targetEntity = "CourseUser", mappedBy = "user", fetch = "EXTRA_LAZY", orphanRemoval=true, cascade = {"persist"})
*/
private $userCourses;
/**
* Course
*
* @ORM\Table(name = "course")
*/
class Course
/**
* @var Collection
*
* @ORM\OneToMany(targetEntity = "CourseUser", mappedBy = "course", fetch = "EXTRA_LAZY", orphanRemoval=true, cascade = {"persist"})
*/
private $courseUsers;
// UserFormType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(...) // some user fields
->add('userCourses',CollectionType::class, [
'entry_type' => UserCourseEmbeddedForm::class,
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
'label' => false
])
;
// UserCourseEmbeddedForm
$builder
->add('course', EntityType::class, [
'class' => Course::class,
'choice_label' => 'name',
'label' => false,
'query_builder' => function(CourseRepository $repo) {
return $repo->createIsActiveQueryBuilder();
}
])
{{ form_start(userForm) }}
...
<h3>Courses</h3>
<div class = "row js-user-course-wrapper"
data-prototype = "{{ form_widget(userForm.userCourses.vars.prototype)|e('html_attr') }}"
data-index = "{{ userForm.userCourses|length }}"
>
{% for userCourseForm in userForm.userCourses %}
<div class = "col-xs-4 js-user-course-item">
{{ form_errors(userCourseForm) }}
{{ form_row(userCourseForm.course) }}
</div>
{% endfor %}
<a href = "#" class = "js-user-course-add">
<span class = "fa fa-plus-circle"></span>
Add Another Course
</a>
</div>
<button type = "submit" class = "btn btn-primary" formnovalidate>Save</button>
{{ form_end(userForm) }}
Код для добавления курса находится в другом шаблоне и создает пустую форму с помощью прототипа определения формы.

Есть ли способ исправить эти проблемы? Или лучший способ сделать модель данных или формы, чтобы сделать эту работу более плавной?




Используя ваш последний подход (ManyToOne/OneToMany), используйте css, чтобы пометить раскрывающиеся списки как отключенные (во встроенной форме используйте 'attr' => ['disabled' => true], затем включите вновь отображаемые select элементы с помощью javascript. Вы также можете пометить 'allowDelete' => false в своем UserFormType, в противном случае вам может потребоваться повторно включить все элементы select при отправке формы.
Что вы подразумеваете под «включить недавно отображаемые выбранные элементы с помощью javascript»? Вы имеете в виду форму «Добавить другой курс»?
@CharlotteMoller - предположительно, у вас есть код javascript, который запускается, когда вы нажимаете «Добавить другой курс», который создает новый элемент select с параметрами курса. Поскольку по моему предложению они будут отмечены как disabled, вам придется удалить атрибут disabled для вновь отображаемого элемента.
@LiamRoels - я не знаю, о каком «элементе проверки» вы упоминаете?
@ehymel, если бы кто-то просто изменил html / css в используемом им браузере и включил раскрывающийся список, чтобы выбрать то, что он хочет. Затем он мог отправить форму с некоторыми значениями, которые вы не хотели видеть отправленными. Это проблема безопасности, я думаю
@ehymel с этим я просто имею в виду, что css здесь не правильное решение ...
@LiamRoels - хороший момент, это легко обойти. Я полагаю, что некоторая внутренняя проверка могла бы проверить это. Я не знаю другого предложения.
@LiamRoels, ваши предложения работают, TX! Я бы предпочел отображать выбранные курсы в виде текста, а не отключенного поля выбора, которое не так привлекательно визуально. Должны ли назначенные встроенные формы курса быть элементами выбора для обработки при отправке формы для работы? Мне также нужно выяснить, как отображать только неназначенные курсы в форме «Добавить другой курс», какие-либо предложения?
разве нельзя было бы просто включить раскрывающийся список с помощью элемента проверки, если вы сделаете это таким образом?