首先,我想感谢你的帮助和你的时间。我是新的编码与symfony。所以如果你看到的东西要改进,你想让我知道,我会采取任何好的意见。谢谢你的理解。
我想制作一个有几个步骤的食谱,每个步骤都有一个描述和一个图像。我使用collectionType。我设法恢复步骤及其描述和图像。但当我刷新发送到数据库时,第一个图像被覆盖,因为它处于循环中。
一切都工作正常,直到这段代码我做了一个var_dump,我恢复了名称更改的图像
- 变量转储($imageFileName2);**
但是在foreach循环中,我想设置我的映像,第一个映像被覆盖
foreach($stepsImgs as $s) {
$s->setStepsImage($imageFileName2);
}
- 在此编辑,如果这可以帮助您了解更多 *
foreach($imgs as $im) {
$imageFileName2 = $fileUploader->upload($im);
var_dump('<br>HERE ARE THE NAME OF THE IMAGE AFTER THE FILEUPLOAD : '.$imageFileName2.'<br>');
foreach($stepsImgs as $s) {
$s->setStepsImage($imageFileName2);
}
}
输出:
并且在其末尾是刷新之前的dd
我允许您检查我的代码,再次感谢您抽出时间。
这是我的配方实体:
<?php
namespace App\Entity;
use App\Repository\RecipeRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=RecipeRepository::class)
*/
class Recipe
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $recipeName;
/**
* @ORM\Column(type="integer")
*/
private $duration;
/**
* @ORM\OneToMany(targetEntity=RecipeIngredients::class, mappedBy="recipes", cascade={"persist"}, orphanRemoval=true)
* @ORM\JoinColumn(onDelete="CASCADE")
*/
private $recipeIngredients;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $recipeImage;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $recipeDescription;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $createdAt;
/**
* @ORM\ManyToOne(targetEntity=Category::class, inversedBy="recipes")
*/
private $category;
/**
* @ORM\ManyToOne(targetEntity=Difficulty::class, inversedBy="recipes")
*/
private $difficulty;
/**
* @ORM\OneToMany(targetEntity=Steps::class, mappedBy="recipe", cascade={"persist"}, orphanRemoval=true)
* @ORM\JoinColumn(onDelete="CASCADE")
*/
private $steps;
public function __construct()
{
$this->recipeIngredients = new ArrayCollection();
$this->createdAt = new \DateTime('now');
$this->steps = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getRecipeName(): ?string
{
return $this->recipeName;
}
public function setRecipeName(string $recipeName): self
{
$this->recipeName = $recipeName;
return $this;
}
public function getDuration(): ?int
{
return $this->duration;
}
public function setDuration(int $duration): self
{
$this->duration = $duration;
return $this;
}
public function getRecipeImage(): ?string
{
return $this->recipeImage;
}
public function setRecipeImage(?string $recipeImage): self
{
$this->recipeImage = $recipeImage;
return $this;
}
public function getRecipeDescription(): ?string
{
return $this->recipeDescription;
}
public function setRecipeDescription(?string $recipeDescription): self
{
$this->recipeDescription = $recipeDescription;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(?\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getCategory(): ?Category
{
return $this->category;
}
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
/**
* @return Collection<int, RecipeIngredients>
*/
public function getRecipeIngredients(): Collection
{
return $this->recipeIngredients;
}
public function addRecipeIngredient(RecipeIngredients $recipeIngredient): self
{
if (!$this->recipeIngredients->contains($recipeIngredient)) {
$this->recipeIngredients[] = $recipeIngredient;
$recipeIngredient->setRecipes($this);
}
return $this;
}
public function removeRecipeIngredient(RecipeIngredients $recipeIngredient): self
{
if ($this->recipeIngredients->removeElement($recipeIngredient)) {
// set the owning side to null (unless already changed)
if ($recipeIngredient->getRecipes() === $this) {
$recipeIngredient->setRecipes(null);
}
}
return $this;
}
public function getMinuteFromLastUpdate($id)
{
// $updateDate = new \DateTime($this->getCreatedAt());
$updateDate = $this->getCreatedAt();
$now = new \DateTime('now');
$calculateDate = $updateDate->diff($now)->format('%H:%i');
return $calculateDate;
}
public function getDifficulty(): ?Difficulty
{
return $this->difficulty;
}
public function setDifficulty(?Difficulty $difficulty): self
{
$this->difficulty = $difficulty;
return $this;
}
/**
* @return Collection<int, Steps>
*/
public function getSteps(): Collection
{
return $this->steps;
}
public function addStep(Steps $step): self
{
if (!$this->steps->contains($step)) {
$this->steps[] = $step;
$step->setRecipe($this);
}
return $this;
}
public function removeStep(Steps $step): self
{
if ($this->steps->removeElement($step)) {
// set the owning side to null (unless already changed)
if ($step->getRecipe() === $this) {
$step->setRecipe(null);
}
}
return $this;
}
public function __toString()
{
return $this->getRecipeName();
}
}
这里我的RecipeController和方法,当我想要创建配方:
/**
* @Route("admin/create/recipe/{id}", name="create_recipe")
*/
public function createRecipe(Request $request, FileUploader $fileUploader, EntityManagerInterface $em, Category $category=null, Steps $steps=null): Response
{
$recipe = new Recipe;
$form = $this->createForm(RecipeFormType::class, $recipe);
$form->handleRequest($request);
$imgs = [];
$stepsImgs =[];
if ($form->isSubmitted() && $form->isValid()) {
$recipe->setCategory($category);
$imageFile = $form->get('recipeImage')->getData();
$imageFilesData = $request->files;
foreach($imageFilesData as $imageFiles) {
foreach($imageFiles as $value) {
foreach($value as $images) {
foreach($images as $img){
$imgs[] = $img;
}
}
}
}
foreach($recipe->getSteps() as $stepImg) {
$stepsImgs[] = $stepImg;
}
foreach($imgs as $im) {
$imageFileName2 = $fileUploader->upload($im);
foreach($stepsImgs as $s) {
$s->setStepsImage($imageFileName2);
}
}
if ($imageFile) {
$imageFileName = $fileUploader->upload($imageFile);
$recipe->setRecipeImage($imageFileName);
}
// dd($recipe);
$em->persist($recipe);
$em->flush();
return $this->redirectToRoute('dashboard', []);
}
$formView = $form->createView();
return $this->render('admin/recipe/createRecipe.html.twig', [
'formView' => $formView,
'recipe' => $recipe,
'recipeId' => $recipe->getId(),
]);
}
此处为我配方表单类型:
<?php
namespace App\Form;
use App\Entity\Difficulty;
use App\Entity\Recipe;
use App\Form\CategoriesFormType;
use App\Form\RecipeIngredientsFormType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class RecipeFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('recipeName', TextType::class, [
'label' => 'Name of the recipe',
'attr' => [
'placeholder' => 'Type the name of the recipe',
],
])
->add('recipeImage', FileType::class, [
'label' => 'Image of the recipe',
"attr" => ['class' => "form-control"],
'mapped' => false,
'required' => false,
'constraints' => [
new File([
'maxSize' => '10254k',
'mimeTypes' => [
'image/jpeg',
'image/png',
'image/webp',
'image/jpg',
],
'mimeTypesMessage' => 'Please upload une image valide',
]),
],
])
->add('recipeDescription', TextareaType::class, [
'label' => 'Description of the recipe',
'attr' => [
'placeholder' => 'Description of the recipe',
],
])
->add('duration', NumberType::class, [
'label' => 'Duration of the recipe',
'attr' => [
'placeholder' => 'The duration of the recipe in minutes',
],
])
->add('difficulty', EntityType::class, [
'label' => 'Difficulty',
'placeholder' => 'Difficulty',
'class' => Difficulty::class,
'choice_label' => function(Difficulty $difficulty){
return strtoupper($difficulty->getDifficultyName());
}
])
->add('recipeIngredients', CollectionType::class, [
//le collection type need a element 'form , entity, ...
'entry_type' => RecipeIngredientsFormType::class,
'prototype' => true,
'delete_empty' => true,
// we allow adds on persist with the cascade_persist;
//its gonna be a html and we can manipulate it with js
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'attr' => [
'class' => 'collection-recipeIngredients',
]
])
->add('steps', CollectionType::class, [
//le collection type need a element 'form , entity, ...
'entry_type' => StepsFormType::class,
'prototype' => true,
'delete_empty' => true,
// we allow adds on persist with the cascade_persist;
//its gonna be a html and we can manipulate it with js
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'attr' => [
'class' => 'collection-steps',
]
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Recipe::class,
]);
}
}
下面是我的步骤表单类型:
<?php
namespace App\Form;
use App\Entity\Steps;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
class StepsFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('stepDescription', TextareaType::class, [
'label' => 'Description of the step',
'attr' => [
'placeholder' => 'Description of the step',
],
])
->add('stepsImage' , FileType::class, [
'label' => 'Image of the step',
"attr" => ['class' => "form-control"],
'mapped' => true,
'required' => false,
'constraints' => [
new File([
'maxSize' => '10254k',
'mimeTypes' => [
'image/jpeg',
'image/png',
'image/webp',
'image/jpg',
],
'mimeTypesMessage' => 'Please upload a valid image ',
]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Steps::class,
]);
}
}
这里是我用于collectionType的JQuery代码;
$(document).ready(function() { // Une fois que le document (base.html.twig) HTML/CSS a bien été complètement chargé...
// add-collection-widget.js : fonction permettant d'ajouter un nouveau bloc "programme" au sein d'une question (pour agrandir la collection)
$('.add-another-collection-widget').click(function (e) {
let list = $($(this).attr('data-list-selector'))
// Récupération du nombre actuel d'élément "programme" dans la collection (à défaut, utilisation de la longueur de la collection)
let counter = list.data('widget-counter') || list.children().length
// Récupération de l'identifiant de la question concernée, en cours de création/modification
let recipe = list.data('recipe')
console.log(recipe)
// Extraction du prototype complet du champ (que l'on va adapter ci-dessous)
let newWidget = list.attr('data-prototype')
console.log(newWidget)
// Remplacement des séquences génériques "__name__" utilisées dans les parties "id" et "name" du prototype
// par un numéro unique au sein de la collection de "programmes" : ce numéro sera la valeur du compteur
// courant (équivalent à l'index du prochain champ, en cours d'ajout).
// Au final, l'attribut ressemblera à "question[programmes][n°]"
newWidget = newWidget.replace(/__name__/g, counter)
// Ajout également des attributs personnalisés "class" et "value", qui n'apparaissent pas dans le prototype original
newWidget = newWidget.replace(/><input type="hidden"/, ' class="borders"><input type="hidden" value="'+recipe+'"')
// Incrément du compteur d'éléments et mise à jour de l'attribut correspondant
counter++
list.data('widget-counter', counter)
// Création d'un nouvel élément (avec son bouton de suppression), et ajout à la fin de la liste des éléments existants
var newElem = $(list.attr('data-widget-tags')).html(newWidget)
addDeleteLink($(newElem).find('div.borders'))
newElem.appendTo(list)
})
// anonymize-collection-widget.js : fonction permettant de supprimer un bloc "programme" existant au sein d'une question
$('.remove-collection-widget').find('div.borders').each(function() {
addDeleteLink($(this))
})
// fonction permettant l'ajout d'un bouton "Supprimer ce answer" dans un bloc "programme", et d'enregistrer l'évenement "click" associé
function addDeleteLink($recipeIngredientsForm) {
var $removeFormButton = $('<div class="block"><button type="button" class="button">Supprimer</button></div>');
$recipeIngredientsForm.append($removeFormButton)
$removeFormButton.on('click', function(e) {
$recipeIngredientsForm.remove()
})
}
1条答案
按热度按时间vxf3dgd41#
你能做一个控制器的方案吗?我认为你在这方面有一个问题:
任何时候当你的操作进入这个函数,第一个索引都是0..有可能有这样的代码:
或者使用递增索引,在开头声明。($imgs[$index] = $img;$指数++ ;)