Symfony Form CollectionType with a media field (VichUpload for example) - forms

I've got a problem with my collection of object which have a media field.
Entity Section with a collection of sectionContent:
class Section
{
/**
* #var ArrayCollection|Collection|SectionContent[]
* #ORM\OneToMany(targetEntity="MyBundle\Entity\SectionContent", mappedBy="section", cascade={"persist", "remove"})
* #Groups({"section_full"})
*/
protected Collection $sectionContents;
}
Form :
->add(
'sectionContents',
CollectionType::class, [
'entry_type' => SectionContentType::class,
'required' => false,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'delete_empty' => true,
]
)
SectionContent entity with a picture field
class SectionContent
{
/**
* #Groups({"section_full"})
*
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\PublicMedia", cascade={"persist", "remove"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="public_media_id", referencedColumnName="id")
* })
*
* #var PublicMedia
*/
protected $picture;
}
Form:
->add(
'picture',
SectionPictureType::class,
[
'label' => false,
'required' => false
]
)
PublicMedia entity is a Vich/Uploadable class
/**
* #ORM\MappedSuperclass
* #Vich\Uploadable
*/
abstract class PublicMedia
{
/**
* #Vich\UploadableField(mapping="public_media_uploader", fileNameProperty="filename")
*
* #var File|string
*/
private $file;
}
If I've got an Section like this :
"sections": {
"0": {
"id": 1,
"sectionContents": {
"0": {
"id": 1,
"picture": {
"id": 1,
"uuid": "uuid1",
"filename": "test_1.png"
}
},
"1": {
"id": 2,
"picture": {
"id": 2,
"uuid": "uuid2",
"filename": "test_2.png"
}
}
}
}
},
I've got two pictures test_1 and test_2 saved in a directory on my project (web/upload/section/setion_content for example), but when I delete the first one (test_1), symfony didn't delete the linked picture as I expected.
It update the first section with the data of the second so I've got:
"sections": {
"0": {
"id": 1,
"sectionContents": {
"0": {
"id": 1,
"picture": {
"id": 1,
"uuid": "uuid1",
"filename": "test_2.png"
}
}
}
}
},
As I understand, symfony update the first section data with the second and delete the second, but when it delete the second section, it delete the linked picture as well so I've got a section with test_2 picture on data and test_1 linked picture on the directory. So my sectionContent is linked to a deleted picture and I've got a picture without linked sectionContent.
Before:
[{picture: {filename: test_1}}, {picture: {filename: test_2}}] -> web/upload/.../test_1 and web/upload/.../test_2
After:
[{picture: {filename: test_2}}] -> web/upload/.../test_1
I don't know how to fix that ...
When I delete a sectionContent I want to delete the linked picture and not the last one !
Do you have some ideas please ?

Related

API Request body format accepted by form

I build an API with Symfony, so I create a CRUD for an entity, and I import Nelmio Api Doc Bundle for generate a doc with Swagger.
I build POST action with a form, and in the request body, I need to add products relation to the cart I create. So this is the body request I got in my Swagger documentation.
{
"datetime": "2023-02-03T12:54:05.661Z",
"customer": {},
"products": [
{
"id": 0
}
]
}
But this body don't work, only this format is accepted :
"products": [
2, 4
]
Or this one :
"products": {
"a": 2,
"b": 4
}
Can you help me to find the good configuration in my form, for my form can accept the JSON body format :
"products": [
{
"id": 0
}
]
Cart.php
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $datetime = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
private Customer $customer;
#[ORM\ManyToMany(targetEntity: Product::class)]
private ?Collection $products = null;
(getters and setters...)
CartType.php
->add('products', EntityType::class, [
'class' => Product::class,
'multiple' => true,
'constraints' => [
new NotNull(),
],
])
CartController.php
#[Route('/api/v1/customer/cart', name: 'cart_create', methods: ['POST'])]
public function createAction(EntityManagerInterface $em, Request $request): Response
{
$form = $this->buildForm(CartType::class);
$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
return $this->respond($form, Response::HTTP_BAD_REQUEST);
}
/** #var Cart $cart */
$cart = $form->getData();
$em->persist($cart);
$em->flush();
return $this->respond($cart);
}

load cities in choiceType after select one gouvernorat in a second ChoiceType

I have two entities with association Gouvernorat -> Villes.
This is the Gouvernorat entity:
class Gouvernorat
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100, nullable=false)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="EntiteBundle\Entity\Ville", mappedBy="idGouvernorat")
*/
private $villes;
//getters & setters & constructor
}
and this is the City Entity:
class Ville
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=200, nullable=false)
*/
private $name;
/**
* #var \Gouvernorat
*
* #ORM\ManyToOne(targetEntity="EntiteBundle\Entity\Gouvernorat",inversedBy="villes")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_gouvernorat", referencedColumnName="id")
* })
*/
private $idGouvernorat;
}
In a third entity called Etablissement, i have string gouvernorat and string string ville.So i am trying to create an EtablissementType. This form contains
two Entity type for the child gouvernorat and ville
Brief copy :
class EtablissementType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('gouvernorat', EntityType::class, array(
'class' => 'EntiteBundle\Entity\Gouvernorat',
'choice_label' => 'name',
'multiple' => false
))
->add('ville',EntityType::class,
array(
'class' => 'EntiteBundle\Entity\Ville',
'choice_label' => 'name',
'multiple' => false
))
->add('Enregistrer',SubmitType::class)
-> setMethod('POST');
}
when i select gouvernorat i want that all cities of this gouvernorat are loaded i the second ChoiceType so i added this event:
public function buildForm(FormBuilderInterface $builder, array $options)
{
//$builder-> ...
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$gouv = $data-> getGouvernorat();
$villes = null === $gouv ? array() : $gouv->getVilles();
$names = array_map(function ($value) {
return $value['name'];
}, $villes);
$form->add('ville', ChoiceType::class,
array(
'choices' => $names
));
}
);
}
shortcut for the data(in json forma):
[
{
"id": 1,
"name": "ARIANA",
"villes": [
{
"id": 1,
"name": "RAOUED",
"idGouvernorat": null
},
{
"id": 2,
"name": "SIDI THABET",
"idGouvernorat": null
},
{
"id": 3,
"name": "ARIANA VILLE",
"idGouvernorat": null
},
{
"id": 4,
"name": "LA SOUKRA",
"idGouvernorat": null
},
{
"id": 5,
"name": "KALAAT LANDLOUS",
"idGouvernorat": null
},
{
"id": 6,
"name": "ETTADHAMEN",
"idGouvernorat": null
},
{
"id": 7,
"name": "MNIHLA",
"idGouvernorat": null
}
]
},
{
"id": 2,
"name": "BEJA",
"villes": [
{
"id": 8,
"name": "BEJA NORD",
"idGouvernorat": null
},
{
"id": 9,
"name": "NEFZA",
"idGouvernorat": null
},
{
"id": 10,
"name": "GOUBELLAT",
"idGouvernorat": null
},
{
"id": 11,
"name": "MEJEZ EL BAB",
"idGouvernorat": null
},
{
"id": 12,
"name": "AMDOUN",
"idGouvernorat": null
},
{
"id": 13,
"name": "TEBOURSOUK",
"idGouvernorat": null
},
{
"id": 14,
"name": "TESTOUR",
"idGouvernorat": null
},
{
"id": 15,
"name": "THIBAR",
"idGouvernorat": null
},
{
"id": 16,
"name": "BEJA SUD",
"idGouvernorat": null
}
]
},
etc...
The problem, is that the second choiceType always empty that mean $gouv->getVilles(); doesn't work
Where is the problem, i didn't find the issue exactly , i usedsymfony documentation--Dynamic Generation for Submitted Forms
<script>
var $gouvernorat = $('#etablissement_gouvernorat');
// When gouvernorat gets selected ...
$gouvernorat.change(function() {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected gouvernorat value.
var data = {};
data[$gouvernorat.attr('name')] = $gouvernorat.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
// Replace current ville field ...
$('#etablissement_ville').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#etablissement_ville')
);
// Ville field now displays the appropriate Villes.
}
});
});
</script>
First when you get your gouv data in the form, I am not sure if it works as expected.
Replace
$form = $event->getForm();
$data = $event->getData();
$gouv = $data->getGouvernorat();
By:
$form = $event->getForm();
$data = $event->getData();
$gouv = $data->get('gouvernorat')->getData(); // basically, you get the form data for the gouvernorat field
At this moment I think gouvernorat is not populated with its own villes which are stored in the database (I suppose).
Now you will need to check if the getGouvernorat method of your $data (which is an Etablissement instance) returns a Gouvernorat instance.
If yes you will get this gouvernerat from your database, in order to access all its cities.
if (!is_null($data->getGouvernorat())) {
$persistedGouvernorat = $gouvernoratRepository->find($event->getData()->getGouvernorat());;
if (!is_null($persistedGouvernorat)) {
$gouverorat = $persistedGouvernorat;
}
}
$villes = $gouvernorat->getVilles();
Then I think you can get rid your issue.

How to do conditional update in mongo db?

I am trying to update document's sub document like below lines of code
$bulkbatch = new MongoDB\Driver\BulkWrite(['ordered' => true]);
$subDocumentStatus = array(
"Status" => array(
"CurrentStatus" => $this->EmployeeStatus,
"StatusDate" => new MongoDB\BSON\UTCDateTime(strtotime($this->EmployeeStatusDate)*1000),
"IsActive" => $IsActive
)
);
$bulkbatch->update(
array(
'_id' => new MongoDB\BSON\ObjectID($this->id)
),
array(
'$addToSet' => $subDocumentStatus
),
[
'multi' => true,
'upsert' => false
]
);
$this->manager->executeBulkWrite($this->collection, $bulkbatch);
I have document like
{
"EmployeeNumber": "123456",
"Instructor": NumberInt(1),
"Department": ObjectId("5a8173e6d2ccda13240015a4"),
"FirstName": "Faiza",
"MiddleName": "Yousuf",
"Status": [
{
"CurrentStatus": "Joined",
"StatusDate": ISODate("2018-02-28T23:00:00.0Z"),
"IsActive": NumberInt(1)
}
],
...
...
}
If $this->EmployeeStatus contains "Joined" and $this->EmployeeStatusDate contains some different values like for above document it contains "2019-05-28T23:00:00.0Z" (other than previous value) then it should be updated.
The above code is inserting another sub document
with values like
"Status": [
{
"CurrentStatus": "Joined",
"StatusDate": ISODate("2018-02-28T23:00:00.0Z"),
"IsActive": NumberInt(1)
},
{
"CurrentStatus": "Joined",
"StatusDate": ISODate("2019-05-28T23:00:00.0Z"),
"IsActive": NumberInt(1)
}
],
...
I want that in the case of Joined the existing sub document should be updated.
Please help!!!
There is a solution with two Mongo calls; first tries to update an existing sub-document; if no sub-document is modified then one is inserted in the main document.
Here is a unit-test that proves the solution works (the main function is updateOrInsert):
require_once __DIR__ . '/MongoTestHelper.php';
use MongoDB\BSON\ObjectID;
use MongoDB\BSON\UTCDateTime;
use tests\Dudulina\MongoTestHelper;
class TmpTest extends \PHPUnit_Framework_TestCase
{
/** #var \MongoDB\Collection */
private $collection;
/** #var ObjectID */
private $_id;
public function test_subdocumentExists()
{
$statusDate = new UTCDateTime(strtotime("2018-02-28T23:00:00.0Z"));
$employeeStatus = "Joined";
/**
* Test data
*/
$this->collection->insertOne([
"_id" => $this->_id,
"EmployeeNumber" => "123456",
"Instructor" => 1,
"Department" => new ObjectID("5a8173e6d2ccda13240015a4"),
"FirstName" => "Faiza",
"MiddleName" => "Yousuf",
"Status" => [
[
"CurrentStatus" => $employeeStatus,
"StatusDate" => $statusDate,
"IsActive" => 1,
],
],
]);
/**
* the real test
*/
$this->updateOrInsert($employeeStatus, $statusDate, 0);
/**
* we verify the execution
*/
$document = $this->collection->findOne([
"_id" => $this->_id,
]);
$this->assertCount(1, $document['Status']);
$this->assertSame(0, $document['Status'][0]['IsActive']);
}
public function test_subdocumentDoesNotExists()
{
$statusDate = new UTCDateTime(strtotime("2018-02-28T23:00:00.0Z"));
$employeeStatus = "Joined";
/**
* we insert some test data
*/
$this->collection->insertOne([
"_id" => $this->_id,
"EmployeeNumber" => "123456",
"Instructor" => 1,
"Department" => new ObjectID("5a8173e6d2ccda13240015a4"),
"FirstName" => "Faiza",
"MiddleName" => "Yousuf",
"Status" => [
[
"CurrentStatus" => 'SomeOtherStatus',
"StatusDate" => new UTCDateTime(strtotime("2010-02-28T23:00:00.0Z")),
"IsActive" => 1,
],
],
]);
/**
* the real test
*/
$this->updateOrInsert($employeeStatus, $statusDate, 0);
/**
* we verify the execution
*/
$document = $this->collection->findOne([
"_id" => $this->_id,
]);
/**
* now there are 2 subdocuments inserted
*/
$this->assertCount(2, $document['Status']);
/**
* we verify the second document
*/
$secondSubDocument = $document['Status'][1];
$this->assertSame(0, $secondSubDocument['IsActive']);
$this->assertSame('Joined', $secondSubDocument['CurrentStatus']);
$this->assertTrue($statusDate->toDateTime()->getTimestamp() === $secondSubDocument['StatusDate']->toDateTime()->getTimestamp());
}
/**
* The function that you should do
*/
private function updateOrInsert($employeeStatus, $statusDate, $isActive)
{
$updateResult = $this->collection->updateOne([
"_id" => $this->_id,
"Status.CurrentStatus" => $employeeStatus,
"StatusDate" => ['$ne' => $statusDate],
], [
'$set' => [
'Status.$.StatusDate' => $statusDate,
'Status.$.IsActive' => $isActive,
],
], [
'upsert' => false,
]);
if ($updateResult->getModifiedCount() === 0) {
$this->collection->updateOne([
"_id" => $this->_id,
], [
'$addToSet' => [
'Status' => [
"CurrentStatus" => $employeeStatus,
"StatusDate" => $statusDate,
"IsActive" => $isActive,
],
],
], [
'upsert' => false,
]);
}
}
protected function setUp()
{
$this->collection = (new MongoTestHelper())->selectCollection('eventStore');
$this->collection->deleteMany([]);
$this->_id = new ObjectID('47ecbc36e8fb4b50662adcf9');
}
}

What should be the base URL for the images in magento?

After accessing the Rest API (http://192.168.1.180/magento/index.php/rest/V1/products/SKU) to get the product details, the response is something like this:
{
"attribute_code": "image",
"value": "/6/4/64275-152378-large.jpg"
},
{
"attribute_code": "small_image",
"value": "/6/4/64275-152378-large.jpg"
},
{
"attribute_code": "thumbnail",
"value": "/6/4/64275-152378-large.jpg"
}
What should be the base url for the jpg in the attribute_code keys ?
There is no need for modifying the API request.
Just add a prefix: http://magento.com/pub/media/catalog/product/
So the new URL according to my response will be:
Prefix: http://magento.com/pub/media/catalog/product/6/4/64275-152378-large.jpg
/** #return string */
function getMediaBaseUrl() {
/** #var \Magento\Framework\ObjectManagerInterface $om */
$om = \Magento\Framework\App\ObjectManager::getInstance();
/** #var \Magento\Store\Model\StoreManagerInterface $storeManager */
$storeManager = $om->get('Magento\Store\Model\StoreManagerInterface');
/** #var \Magento\Store\Api\Data\StoreInterface|\Magento\Store\Model\Store $currentStore */
$currentStore = $storeManager->getStore();
return $currentStore->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA);
}

How can I fetch just a document without his referenced documents

I have two Documents: Client and PV. Many PV are referenced in one Client. It's unidirectional.
Client
/**
* #MongoDB\Document
*/
class Client
{
/**
* #MongoDB\Id(strategy="auto")
*/
protected $id;
/**
* #MongoDB\String
*/
protected $name;
/**
* #MongoDB\ReferenceMany(targetDocument="PV", simple=true, cascade={"persist", "remove"})
*/
private $PV = array();
public function __construct()
{
$this->PV = new \Doctrine\Common\Collections\ArrayCollection();
}
}
PV
/**
* #MongoDB\Document
*/
class PV
{
/**
* #MongoDB\Id(strategy="auto")
*/
protected $id;
/**
* #MongoDB\String
*/
protected $name;
}
To fetch a Client Document I use:
$client = $this->get('doctrine_mongodb')
->getRepository('HubMainBundle:Client')
->findOneById($id);
And I get:
"53da113176a2955c6d8b4567": {
"id": "53da113176a2955c6d8b4567",
"name": "Test",
"_p_v": [
{
"id": "53da121276a2956c708b4568",
"name": "test pv"
},
{
"id": "53da4e2876a295b7088b4567",
"name": "pv 2"
}
]
}
But I want:
"53da113176a2955c6d8b4567": {
"id": "53da113176a2955c6d8b4567",
"name": "Test",
"_p_v" : [
"53da121276a2956c708b4568",
"53da4e2876a295b7088b4567"
]
}
So how can I fetch just the Parent Document with MongoIds for every PV referenced not the entires referenced PVs ? (In MongoDB when I do db.Client.find({name: 'Test'}) I got the MongoIds not the documents).
Is it related to the Doctrine MongoDB hydrator ?