I have two entities, MediaObject and Book. MediaObject is a generic entity for managing files and includes fields like size, mimeType, and filePath. Book has fields like title, author, and also includes a link to an associated MediaObject for its cover image file.
How can I POST a Book entity with its associated MediaObject cover image with API-Platform? I'd like to do this as one atomic operation. I don't want books saved without a cover image and I don't want orphan cover images. So I don't want to POST a MediaObject cover image and then use the ID I get back when POSTing a new Book. (or vice-versa)
https://api-platform.com/docs/core/file-upload/
class MediaObject
{
...
public $filePath;
...
}
class Book
{
...
public $coverImage; // i.e. mediaObjectId; associated MediaObject to an image file
...
}
The documentation has the option "deserialize"= false. This means that deserialization will not occur for this operation. Therefore, you must write the entire deserialization process yourself to the handler controller. You must also write fields for the swagger documentation.
For example:
<?php
declare(strict_types=1);
namespace App\Entity;
// more use...
/**
* #ApiResource(
* iri="http://schema.org/MediaObject",
* normalizationContext={
* "groups" = {"media:read"}
* },
* collectionOperations={
* "post" = {
* "controller" = MediaHandler::class,
* "deserialize" = false,
* "access_control" = "is_granted('ROLE_USER')",
* "validation_groups" = {"Default", "media:collection:post"},
* "openapi_context" = {
* "requestBody" = {
* "content" = {
* "multipart/form-data" = {
* "schema" = {
* "type" = "object",
* "properties" = {
* "file" = {
* "type" = "string",
* "format" = "binary"
* },
* "name" = {
* "type" = "string"
* }
* }
* }
* }
* }
* }
* }
* },
* "get"
* },
* itemOperations={
* "get"
* }
* )
* #Vich\Uploadable
* #ORM\Entity(repositoryClass="App\Repository\MediaRepository")
*/
class Media
{
/**
* #ApiProperty(iri="http://schema.org/contentUrl")
* #Groups({"media:read"})
*/
public $contentUrl;
/**
* #Assert\NotNull(groups={"media:collection:post"})
* #Vich\UploadableField(mapping="media", fileNameProperty="filePath")
* #Assert\File(
* maxSize="2M",
* mimeTypes={
* "application/pdf",
* "application/x-pdf",
* "image/jpeg",
* "image/jpg",
* "image/png"
* },
* groups={"media:collection:post"}
* )
*/
public $file;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=512)
*/
private $filePath;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
//...
}
Controller handler example:
<?php
declare(strict_types=1);
namespace App\Controller\Api;
// use ...
class MediaHandler extends AbstractController
{
/**
* #var EntityManagerInterface
*/
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function __invoke(Request $request): Media
{
$uploadedFile = $request->files->get('file');
if (!$uploadedFile) {
throw new BadRequestHttpException('"file" is required');
}
$mediaObject = new Media();
$mediaObject->file = $uploadedFile;
$mediaObject->setName($request->request->get('name'));
return $mediaObject;
}
}
If the "Book" exists. And you want to add Book toMediaObject, you can set the iri string and parse it in the controller-handler:
//...
public function __construct(EntityManagerInterface $entityManager, IriConverterInterface $iriConverter)
{
$this->entityManager = $entityManager;
$this->iriConverter = $iriConverter;
}
public function __invoke(Request $request): Media
{
$uploadedFile = $request->files->get('file');
if (!$uploadedFile) {
throw new BadRequestHttpException('"file" is required');
}
$iriBook = $request->request->get('book');
$book = null;
if ($iriBook) {
/**
* #var Book $book
*/
$book = $this->iriConverter->getItemFromIri($iriBook);
}
$mediaObject = new Media();
$mediaObject->file = $uploadedFile;
$mediaObject->setBook($book);
return $mediaObject;
}
//..
If this is your case, then no further action (DataPersist) is required.
Next your need to go https://api-platform.com/docs/core/data-persisters/ and make DataPesist handler
Example:
<?php
declare(strict_types=1);
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\Entity\Media;
use App\ExtendTrait\ContextAwareDataTrait;
use Doctrine\ORM\EntityManagerInterface;
class MediaObjectDataPersister implements ContextAwareDataPersisterInterface
{
use ContextAwareDataTrait;
/**
* #var EntityManagerInterface
*/
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* {#inheritdoc}
*/
public function supports($data, array $context = []): bool
{
return $this->isCollection('post', $context) && $data instanceof Media;
}
/**
* {#inheritdoc}
*
* #param $data Media
*
* #throws \Exception
*/
public function persist($data, array $context = []): void
{
$book = new Book();
$book->setName($data->getName());
// begin transaction and persist and flush $book and $data
}
/**
* {#inheritdoc}
*/
public function remove($data, array $context = []): void
{
// todo remove book
}
}
P.S. I don't test this code. I writing idea ;)
P.S.S. $this->isCollection() it function from my trait, may be need you it:
<?php
declare(strict_types=1);
namespace App\ExtendTrait;
/**
* Trait ContextAwareDataTrait.
*
* Helps confirm the operation name
*/
trait ContextAwareDataTrait
{
public function isItem(string $operationName, array $context, string $resourceClass = null): bool
{
if ($resourceClass && ($context['resource_class'] ?? false) !== $resourceClass) {
return false;
}
return ($context['item_operation_name'] ?? null) === $operationName;
}
public function isCollection(string $operationName, array $context, string $resourceClass = null): bool
{
if ($resourceClass && ($context['resource_class'] ?? false) !== $resourceClass) {
return false;
}
return ($context['collection_operation_name'] ?? null) === $operationName;
}
}
Related
After opening and exiting the reserved shopping cart, I switch between different accounts in the browser, and the additional_options field in the quote_item_options table is lost, resulting in incomplete display of product specifications
How to modify the core file to add the additional_options field
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Checkout\Model;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Quote\Api\Data\CartInterface;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\QuoteIdMaskFactory;
use Psr\Log\LoggerInterface;
/**
* Represents the session data for the checkout process
*
* #api
* #SuppressWarnings(PHPMD.CouplingBetweenObjects)
* #SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* #SuppressWarnings(PHPMD.TooManyFields)
* #since 100.0.2
*/
class Session extends \Magento\Framework\Session\SessionManager
{
const CHECKOUT_STATE_BEGIN = 'begin';
/**
* Quote instance
*
* #var Quote
*/
protected $_quote;
/**
* Customer Data Object
*
* #var CustomerInterface|null
*/
protected $_customer;
/**
* Whether load only active quote
*
* #var bool
*/
protected $_loadInactive = false;
/**
* A flag to track when the quote is being loaded and attached to the session object.
*
* Used in trigger_recollect infinite loop detection.
*
* #var bool
*/
private $isLoading = false;
/**
* Loaded order instance
*
* #var \Magento\Sales\Model\Order
*/
protected $_order;
/**
* #var \Magento\Sales\Model\OrderFactory
*/
protected $_orderFactory;
/**
* #var \Magento\Customer\Model\Session
*/
protected $_customerSession;
/**
* #var \Magento\Quote\Api\CartRepositoryInterface
*/
protected $quoteRepository;
/**
* #var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress
*/
protected $_remoteAddress;
/**
* #var \Magento\Framework\Event\ManagerInterface
*/
protected $_eventManager;
/**
* #var \Magento\Store\Model\StoreManagerInterface
*/
protected $_storeManager;
/**
* #var \Magento\Customer\Api\CustomerRepositoryInterface
*/
protected $customerRepository;
/**
* #param QuoteIdMaskFactory
*/
protected $quoteIdMaskFactory;
/**
* #param bool
*/
protected $isQuoteMasked;
/**
* #var \Magento\Quote\Model\QuoteFactory
*/
protected $quoteFactory;
/**
* #var LoggerInterface|null
*/
private $logger;
/**
* #param \Magento\Framework\App\Request\Http $request
* #param \Magento\Framework\Session\SidResolverInterface $sidResolver
* #param \Magento\Framework\Session\Config\ConfigInterface $sessionConfig
* #param \Magento\Framework\Session\SaveHandlerInterface $saveHandler
* #param \Magento\Framework\Session\ValidatorInterface $validator
* #param \Magento\Framework\Session\StorageInterface $storage
* #param \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager
* #param \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory
* #param \Magento\Framework\App\State $appState
* #param \Magento\Sales\Model\OrderFactory $orderFactory
* #param \Magento\Customer\Model\Session $customerSession
* #param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
* #param \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress
* #param \Magento\Framework\Event\ManagerInterface $eventManager
* #param \Magento\Store\Model\StoreManagerInterface $storeManager
* #param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository
* #param QuoteIdMaskFactory $quoteIdMaskFactory
* #param \Magento\Quote\Model\QuoteFactory $quoteFactory
* #param LoggerInterface|null $logger
* #throws \Magento\Framework\Exception\SessionException
* #SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\App\Request\Http $request,
\Magento\Framework\Session\SidResolverInterface $sidResolver,
\Magento\Framework\Session\Config\ConfigInterface $sessionConfig,
\Magento\Framework\Session\SaveHandlerInterface $saveHandler,
\Magento\Framework\Session\ValidatorInterface $validator,
\Magento\Framework\Session\StorageInterface $storage,
\Magento\Framework\Stdlib\CookieManagerInterface $cookieManager,
\Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory,
\Magento\Framework\App\State $appState,
\Magento\Sales\Model\OrderFactory $orderFactory,
\Magento\Customer\Model\Session $customerSession,
\Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
\Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress,
\Magento\Framework\Event\ManagerInterface $eventManager,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
QuoteIdMaskFactory $quoteIdMaskFactory,
\Magento\Quote\Model\QuoteFactory $quoteFactory,
LoggerInterface $logger = null
) {
$this->_orderFactory = $orderFactory;
$this->_customerSession = $customerSession;
$this->quoteRepository = $quoteRepository;
$this->_remoteAddress = $remoteAddress;
$this->_eventManager = $eventManager;
$this->_storeManager = $storeManager;
$this->customerRepository = $customerRepository;
$this->quoteIdMaskFactory = $quoteIdMaskFactory;
$this->quoteFactory = $quoteFactory;
parent::__construct(
$request,
$sidResolver,
$sessionConfig,
$saveHandler,
$validator,
$storage,
$cookieManager,
$cookieMetadataFactory,
$appState
);
$this->logger = $logger ?: ObjectManager::getInstance()
->get(LoggerInterface::class);
}
/**
* Set customer data.
*
* #param CustomerInterface|null $customer
* #return \Magento\Checkout\Model\Session
* #codeCoverageIgnore
*/
public function setCustomerData($customer)
{
$this->_customer = $customer;
return $this;
}
/**
* Check whether current session has quote
*
* #return bool
* #codeCoverageIgnore
*/
public function hasQuote()
{
return isset($this->_quote);
}
/**
* Set quote to be loaded even if inactive
*
* #param bool $load
* #return $this
* #codeCoverageIgnore
*/
public function setLoadInactive($load = true)
{
$this->_loadInactive = $load;
return $this;
}
/**
* Get checkout quote instance by current session
*
* #return Quote
* #throws \Magento\Framework\Exception\LocalizedException
* #throws NoSuchEntityException
* #SuppressWarnings(PHPMD.CyclomaticComplexity)
* #SuppressWarnings(PHPMD.NPathComplexity)
*/
public function getQuote()
{
$this->_eventManager->dispatch('custom_quote_process', ['checkout_session' => $this]);
if ($this->_quote === null) {
if ($this->isLoading) {
throw new \LogicException("Infinite loop detected, review the trace for the looping path");
}
$this->isLoading = true;
$quote = $this->quoteFactory->create();
if ($this->getQuoteId()) {
try {
if ($this->_loadInactive) {
$quote = $this->quoteRepository->get($this->getQuoteId());
} else {
$quote = $this->quoteRepository->getActive($this->getQuoteId());
}
$customerId = $this->_customer
? $this->_customer->getId()
: $this->_customerSession->getCustomerId();
if ($quote->getData('customer_id') && (int)$quote->getData('customer_id') !== (int)$customerId) {
$quote = $this->quoteFactory->create();
$this->setQuoteId(null);
}
/**
* If current currency code of quote is not equal current currency code of store,
* need recalculate totals of quote. It is possible if customer use currency switcher or
* store switcher.
*/
if ($quote->getQuoteCurrencyCode() != $this->_storeManager->getStore()->getCurrentCurrencyCode()) {
$quote->setStore($this->_storeManager->getStore());
$this->quoteRepository->save($quote->collectTotals());
/*
* We mast to create new quote object, because collectTotals()
* can to create links with other objects.
*/
$quote = $this->quoteRepository->get($this->getQuoteId());
}
if ($quote->getTotalsCollectedFlag() === false) {
$quote->collectTotals();
}
} catch (NoSuchEntityException $e) {
$this->setQuoteId(null);
}
}
if (!$this->getQuoteId()) {
if ($this->_customerSession->isLoggedIn() || $this->_customer) {
$quoteByCustomer = $this->getQuoteByCustomer();
if ($quoteByCustomer !== null) {
$this->setQuoteId($quoteByCustomer->getId());
$quote = $quoteByCustomer;
}
} else {
$quote->setIsCheckoutCart(true);
$quote->setCustomerIsGuest(1);
$this->_eventManager->dispatch('checkout_quote_init', ['quote' => $quote]);
}
}
if ($this->_customer) {
$quote->setCustomer($this->_customer);
} elseif ($this->_customerSession->isLoggedIn()) {
$quote->setCustomer($this->customerRepository->getById($this->_customerSession->getCustomerId()));
}
$quote->setStore($this->_storeManager->getStore());
$this->_quote = $quote;
$this->isLoading = false;
}
if (!$this->isQuoteMasked() && !$this->_customerSession->isLoggedIn() && $this->getQuoteId()) {
$quoteId = $this->getQuoteId();
/** #var $quoteIdMask \Magento\Quote\Model\QuoteIdMask */
$quoteIdMask = $this->quoteIdMaskFactory->create()->load($quoteId, 'quote_id');
if ($quoteIdMask->getMaskedId() === null) {
$quoteIdMask->setQuoteId($quoteId)->save();
}
$this->setIsQuoteMasked(true);
}
$remoteAddress = $this->_remoteAddress->getRemoteAddress();
if ($remoteAddress) {
$this->_quote->setRemoteIp($remoteAddress);
$xForwardIp = $this->request->getServer('HTTP_X_FORWARDED_FOR');
$this->_quote->setXForwardedFor($xForwardIp);
}
return $this->_quote;
}
/**
* Return the quote's key
*
* #return string
* #codeCoverageIgnore
*/
protected function _getQuoteIdKey()
{
return 'quote_id_' . $this->_storeManager->getStore()->getWebsiteId();
}
/**
* Set the current session's quote id
*
* #param int $quoteId
* #return void
* #codeCoverageIgnore
*/
public function setQuoteId($quoteId)
{
$this->storage->setData($this->_getQuoteIdKey(), $quoteId);
}
/**
* Return the current quote's ID
*
* #return int
* #codeCoverageIgnore
*/
public function getQuoteId()
{
return $this->getData($this->_getQuoteIdKey());
}
/**
* Load data for customer quote and merge with current quote
*
* #return $this
*/
public function loadCustomerQuote()
{
if (!$this->_customerSession->getCustomerId()) {
return $this;
}
$this->_eventManager->dispatch('load_customer_quote_before', ['checkout_session' => $this]);
try {
$customerQuote = $this->quoteRepository->getForCustomer($this->_customerSession->getCustomerId());
} catch (NoSuchEntityException $e) {
$customerQuote = $this->quoteFactory->create();
}
$customerQuote->setStoreId($this->_storeManager->getStore()->getId());
if ($customerQuote->getId() && $this->getQuoteId() != $customerQuote->getId()) {
if ($this->getQuoteId()) {
$quote = $this->getQuote();
$quote->setCustomerIsGuest(0);
$this->quoteRepository->save(
$customerQuote->merge($quote)->collectTotals()
);
$newQuote = $this->quoteRepository->get($customerQuote->getId());
$this->quoteRepository->save(
$newQuote->collectTotals()
);
$customerQuote = $newQuote;
}
$this->setQuoteId($customerQuote->getId());
if ($this->_quote) {
$this->quoteRepository->delete($this->_quote);
}
$this->_quote = $customerQuote;
} else {
$this->getQuote()->getBillingAddress();
$this->getQuote()->getShippingAddress();
$this->getQuote()->setCustomer($this->_customerSession->getCustomerDataObject())
->setCustomerIsGuest(0)
->setTotalsCollectedFlag(false)
->collectTotals();
$this->quoteRepository->save($this->getQuote());
}
return $this;
}
/**
* Associate data to a specified step of the checkout process
*
* #param string $step
* #param array|string $data
* #param bool|string|null $value
* #return $this
*/
public function setStepData($step, $data, $value = null)
{
$steps = $this->getSteps();
if ($value === null) {
if (is_array($data)) {
$steps[$step] = $data;
}
} else {
if (!isset($steps[$step])) {
$steps[$step] = [];
}
if (is_string($data)) {
$steps[$step][$data] = $value;
}
}
$this->setSteps($steps);
return $this;
}
/**
* Return the data associated to a specified step
*
* #param string|null $step
* #param string|null $data
* #return array|string|bool
*/
public function getStepData($step = null, $data = null)
{
$steps = $this->getSteps();
if ($step === null) {
return $steps;
}
if (!isset($steps[$step])) {
return false;
}
if ($data === null) {
return $steps[$step];
}
if (!is_string($data) || !isset($steps[$step][$data])) {
return false;
}
return $steps[$step][$data];
}
/**
* Destroy/end a session and unset all data associated with it
*
* #return $this
*/
public function clearQuote()
{
$this->_eventManager->dispatch('checkout_quote_destroy', ['quote' => $this->getQuote()]);
$this->_quote = null;
$this->setQuoteId(null);
$this->setLastSuccessQuoteId(null);
return $this;
}
/**
* Unset all session data and quote
*
* #return $this
*/
public function clearStorage()
{
parent::clearStorage();
$this->_quote = null;
return $this;
}
/**
* Clear misc checkout parameters
*
* #return void
*/
public function clearHelperData()
{
$this->setRedirectUrl(null)->setLastOrderId(null)->setLastRealOrderId(null)->setAdditionalMessages(null);
}
/**
* Revert the state of the checkout to the beginning
*
* #return $this
* #codeCoverageIgnore
*/
public function resetCheckout()
{
$this->setCheckoutState(self::CHECKOUT_STATE_BEGIN);
return $this;
}
/**
* Replace the quote in the session with a specified object
*
* #param Quote $quote
* #return $this
*/
public function replaceQuote($quote)
{
$this->_quote = $quote;
$this->setQuoteId($quote->getId());
return $this;
}
/**
* Get order instance based on last order ID
*
* #return \Magento\Sales\Model\Order
*/
public function getLastRealOrder()
{
$orderId = $this->getLastRealOrderId();
if ($this->_order !== null && $orderId == $this->_order->getIncrementId()) {
return $this->_order;
}
$this->_order = $this->_orderFactory->create();
if ($orderId) {
$this->_order->loadByIncrementId($orderId);
}
return $this->_order;
}
/**
* Restore last active quote
*
* #return bool True if quote restored successfully, false otherwise
*/
public function restoreQuote()
{
/** #var \Magento\Sales\Model\Order $order */
$order = $this->getLastRealOrder();
if ($order->getId()) {
try {
$quote = $this->quoteRepository->get($order->getQuoteId());
$quote->setIsActive(1)->setReservedOrderId(null);
$this->quoteRepository->save($quote);
$this->replaceQuote($quote)->unsLastRealOrderId();
$this->_eventManager->dispatch('restore_quote', ['order' => $order, 'quote' => $quote]);
return true;
} catch (NoSuchEntityException $e) {
$this->logger->critical($e);
}
}
return false;
}
/**
* Flag whether or not the quote uses a masked quote id
*
* #param bool $isQuoteMasked
* #return void
* #codeCoverageIgnore
*/
protected function setIsQuoteMasked($isQuoteMasked)
{
$this->isQuoteMasked = $isQuoteMasked;
}
/**
* Return if the quote has a masked quote id
*
* #return bool|null
* #codeCoverageIgnore
*/
protected function isQuoteMasked()
{
return $this->isQuoteMasked;
}
/**
* Returns quote for customer if there is any
*/
private function getQuoteByCustomer(): ?CartInterface
{
$customerId = $this->_customer
? $this->_customer->getId()
: $this->_customerSession->getCustomerId();
try {
$quote = $this->quoteRepository->getActiveForCustomer($customerId);
} catch (NoSuchEntityException $e) {
$quote = null;
}
return $quote;
}
}
I am using TYPO3 and I have in my extension my own model, controller and repository file. In the Backend I created a record which worked without any issues. But when I try to load the FE I get the following error in DataMapper.php
PHP Warning: class_parents(): object or string expected in /home/app/vendor/typo3/cms/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php line 284
00284: if ($propertyData['type'] === 'DateTime' || in_array('DateTime', class_parents($propertyData['type']))) {
I debugged it and found out that $propertyName contains the correct property name 'street' but $propertyData is an empty array.
My model looks like this:
<?php
namespace Snowflake\Htwapartmentexchange\Domain\Model;
use Snowflake\ApiRepository\Helpers\DateTime;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Apartment extends AbstractEntity
{
protected $apartmentType = '';
protected $street = '';
protected $zip = '';
protected $city = '';
protected $price = '';
protected $countRooms = '';
protected $area = '';
protected $availableDate = '';
protected $description = '';
protected $firstname = '';
protected $lastname = '';
protected $mobile = '';
protected $mail = '';
protected $pictures = null;
/**
* Apartment constructor.
* #param $apartmentType
* #param $street
* #param $zip
* #param $city
* #param $price
* #param $countRooms
* #param $area
* #param $availableDate
* #param $description
* #param $firstname
* #param $lastname
* #param $mobile
* #param $mail
* #param $pictures
*/
public function __construct($apartmentType, $street, $zip, $city, $price, $countRooms, $area, $availableDate, $description, $firstname, $lastname, $mobile, $mail, $pictures)
{
$this->apartmentType = $apartmentType;
$this->street = $street;
$this->zip = $zip;
$this->city = $city;
$this->price = $price;
$this->countRooms = $countRooms;
$this->area = $area;
$this->availableDate = $availableDate;
$this->description = $description;
$this->firstname = $firstname;
$this->lastname = $lastname;
$this->mobile = $mobile;
$this->mail = $mail;
$this->pictures = $pictures;
}
/**
* #return mixed
*/
public function getApartmentType()
{
return $this->apartmentType;
}
/**
* #param mixed $apartmentType
*/
public function setApartmentType($apartmentType)
{
$this->apartmentType = $apartmentType;
}
/**
* #return mixed
*/
public function getStreet()
{
return $this->street;
}
/**
* #param mixed $street
*/
public function setStreet($street)
{
$this->street = $street;
}
/**
* #return mixed
*/
public function getZip()
{
return $this->zip;
}
/**
* #param mixed $zip
*/
public function setZip($zip)
{
$this->zip = $zip;
}
/**
* #return mixed
*/
public function getCity()
{
return $this->city;
}
/**
* #param mixed $city
*/
public function setCity($city)
{
$this->city = $city;
}
/**
* #return mixed
*/
public function getPrice()
{
return $this->price;
}
/**
* #param mixed $price
*/
public function setPrice($price)
{
$this->price = $price;
}
/**
* #return mixed
*/
public function getCountRooms()
{
return $this->countRooms;
}
/**
* #param mixed $countRooms
*/
public function setCountRooms($countRooms)
{
$this->countRooms = $countRooms;
}
/**
* #return mixed
*/
public function getArea()
{
return $this->area;
}
/**
* #param mixed $area
*/
public function setArea($area)
{
$this->area = $area;
}
/**
* #return DateTime
*/
public function getAvailableDate()
{
return $this->availableDate;
}
/**
* #param DateTime $availableDate
*/
public function setAvailableDate($availableDate)
{
$this->availableDate = $availableDate;
}
/**
* #return mixed
*/
public function getDescription()
{
return $this->description;
}
/**
* #param mixed $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* #return mixed
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* #param mixed $firstname
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
}
/**
* #return mixed
*/
public function getLastname()
{
return $this->lastname;
}
/**
* #param mixed $lastname
*/
public function setLastname($lastname)
{
$this->lastname = $lastname;
}
/**
* #return mixed
*/
public function getMobile()
{
return $this->mobile;
}
/**
* #param mixed $mobile
*/
public function setMobile($mobile)
{
$this->mobile = $mobile;
}
/**
* #return mixed
*/
public function getMail()
{
return $this->mail;
}
/**
* #param mixed $mail
*/
public function setMail($mail)
{
$this->mail = $mail;
}
/**
* #return mixed
*/
public function getPictures()
{
return $this->pictures;
}
/**
* #param mixed $pictures
*/
public function setPictures($pictures)
{
$this->pictures = $pictures;
}
}
Can you tell me what is wrong?
Found a solution.
The problem was that I didn't use PHPDoc comments on my model.
So I changed every property from this
protected $street = '';
to this
/**
* #var string
*/
protected $street = '';
class_parents(new $propertyData['type'])))
you should use new keyword for object
in line 284...
I need to hyrdate multiple objests in one form. Here is what I use:
Product Form - I have a form where I call three fieldsets
Product Fieldset
Promotion Fieldset
Category Fieldset
I have Models for all the necessary tables, here is an example for the product model:
class Product implements ProductInterface
{
/**
* #var int
*/
protected $Id;
/**
* #var string
*/
protected $Title;
/**
* #var float
*/
protected $Price;
/**
* #var string
*/
protected $Description;
/**
* #var string
*/
protected $Url;
/**
* #var \DateTime
*/
protected $DateAdded;
/**
* #var string
*/
protected $Image;
/**
* #var int
*/
protected $Status;
/**
* #return int
*/
public function getId()
{
return $this->Id;
}
/**
* #param int $Id
*/
public function setId($Id)
{
$this->Id = $Id;
}
/**
* #return string
*/
public function getTitle()
{
return $this->Title;
}
/**
* #param string $Title
*/
public function setTitle($Title)
{
$this->Title = $Title;
}
/**
* #return float
*/
public function getPrice()
{
return $this->Price;
}
/**
* #param float $Price
*/
public function setPrice($Price)
{
$this->Price = $Price;
}
/**
* #return string
*/
public function getDescription()
{
return $this->Description;
}
/**
* #param string $Description
*/
public function setDescription($Description)
{
$this->Description = $Description;
}
/**
* #return string
*/
public function getUrl()
{
return $this->Url;
}
/**
* #param string $Url
*/
public function setUrl($Url)
{
$this->Url = $Url;
}
/**
* #return \DateTime
*/
public function getDateAdded()
{
return $this->DateAdded;
}
/**
* #param \DateTime $DateAdded
*/
public function setDateAdded($DateAdded)
{
$this->DateAdded = $DateAdded;
}
/**
* #return string
*/
public function getImage()
{
return $this->Image;
}
/**
* #param string $Image
*/
public function setImage($Image)
{
$this->Image = $Image;
}
/**
* #return int
*/
public function getStatus()
{
return $this->Status;
}
/**
* #param int $Status
*/
public function setStatus($Status)
{
$this->Status = $Status;
}
In my controllers I want to bind the data to my view so I can edit them.
try {
$aProduct = $this->productService->findProduct($iId);
} catch (\Exception $ex) {
// ...
}
$form = new ProductForm();
$form->bind($aProduct);
In the first place I need to select all the necessary information from the DB. I join three tables product, promotion and category tables. I must return the data to my controller as objects and bind them in my form to be able to edit on the view page.
Please give me some ideas how to accomplish this so I can continue with my development. I am stuck.
I will appreciate all the links which can help me or give me any ideas/examples from the real life.
public function findProduct($Id)
{
$iId = (int) $Id;
$sql = new Sql($this->dbAdapter);
$select = $sql->select('product');
$select->join('promotion', 'promotion.ProductId = product.Id', array('Discount', 'StartDate', 'EndDate', 'PromotionDescription' => 'Description', 'PromotionStatus', 'Type'), 'left');
$select->join('producttocategory', 'producttocategory.ProductId = product.Id', array('CategoryId'), 'left');
$select->join('category', 'category.Id = producttocategory.CategoryId', array('ParentId', 'Title', 'Description', 'Url', 'DateAdded', 'Image', 'Status'), 'left');
$where = new Where();
$where->equalTo('product.Id', $iId);
$select->where($where);
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$resultSet = new HydratingResultSet($this->hydrator, $this->productPrototype);
return $resultSet->initialize($result);
}
throw new \Exception("Could not find row $Id");
}
I need to hydrate the result and return an object which I will use in the controller to bind the form.
You can to fill entities from a database manually.
If you want to fill automatically need to create a map between a database and entities. I made a library for making a map between DB and entities use annotations in entities https://github.com/newage/annotations.
Next step.
When you get different data from tables. Example:
SELECT
table1.id AS table1.id,
table1.title AS table1.title,
table2.id AS table2.id,
table2.alias AS table2.alias
FROM table1
JOIN table2 ON table1.id = table2.id
Need do foreach by rows and set data to entities comparing row with table name and Entity from a generated map.
Auto generating tree of entities from DB is my next project.
But it's do not finished. https://github.com/newage/zf2-simple-orm.
I'm trying to update my Recipe Entity that has a file field, in particular an image.
THIS IS MY ENTITY
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Article
*
* #ORM\Table()
* #ORM\HasLifecycleCallbacks
* #ORM\Entity
*/
class Article
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="titolo", type="string", length=255)
*/
private $titolo;
/**
* #var string
*
* #ORM\Column(name="autore", type="string", length=255)
*/
private $autore;
/**
* #var string
*
* #ORM\Column(name="testo", type="text")
*/
private $testo;
/**
* #var string
*
* #ORM\Column(name="categoria", type="string", length=100)
*/
private $categoria;
/**
* #var string $image
* #Assert\File( maxSize = "1024k", mimeTypesMessage = "Perfavore inserisci un'immagine valida!")
* #ORM\Column(name="image", type="string", length=255, nullable=true)
*/
private $image;
/**
* #var date
*
* #ORM\Column(name="data", type="date")
*/
public $data;
/**
* #var integer
*
* #ORM\Column(name="rate", type="integer",nullable=true)
*/
private $rate;
/**
* #ORM\OneToMany(targetEntity="CommentoArticle", mappedBy="article")
*/
protected $commentoarticle;
public function __construct()
{
$this->commentoarticle = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set titolo
*
* #param string $titolo
*
* #return Article
*/
public function setTitolo($titolo)
{
$this->titolo = $titolo;
return $this;
}
/**
* Get titolo
*
* #return string
*/
public function getTitolo()
{
return $this->titolo;
}
/**
* Set autore
*
* #param string $autore
*
* #return Article
*/
public function setAutore($autore)
{
$this->autore = $autore;
return $this;
}
/**
* Get autore
*
* #return string
*/
public function getAutore()
{
return $this->autore;
}
/**
* Set testo
*
* #param string $testo
*
* #return Article
*/
public function setTesto($testo)
{
$this->testo = $testo;
return $this;
}
/**
* Get testo
*
* #return string
*/
public function getTesto()
{
return $this->testo;
}
/**
* Set image
*
* #param string $image
*/
public function setImage($image)
{
$this->image = $image;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
public function getFullImagePath() {
return null === $this->image ? null : $this->getUploadRootDir(). $this->image;
}
protected function getUploadRootDir() {
// the absolute directory path where uploaded documents should be saved
return $this->getTmpUploadRootDir().$this->getId()."/";
}
protected function getTmpUploadRootDir() {
// the absolute directory path where uploaded documents should be saved
return __DIR__ . '/../../../web/imgArticoli/';
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function uploadImage() {
// the file property can be empty if the field is not required
if (null === $this->image) {
return;
}
if(!$this->id){
$this->image->move($this->getTmpUploadRootDir(), $this->image->getClientOriginalName());
}else{
$this->image->move($this->getUploadRootDir(), $this->image->getClientOriginalName());
}
$this->setImage($this->image->getClientOriginalName());
}
/**
* #ORM\PostPersist()
*/
public function moveImage()
{
if (null === $this->image) {
return;
}
if(!is_dir($this->getUploadRootDir())){
mkdir($this->getUploadRootDir());
}
copy($this->getTmpUploadRootDir().$this->image, $this->getFullImagePath());
unlink($this->getTmpUploadRootDir().$this->image);
}
/**
* Set data
*
* #param \DateTime $data
*
* #return Article
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Get data
*
* #return \DateTime
*/
public function getData()
{
return $this->data;
}
/**
* Set categoria
*
* #param string $categoria
*
* #return Article
*/
public function setCategoria($categoria)
{
$this->categoria = $categoria;
return $this;
}
/**
* Get categoria
*
* #return string
*/
public function getCategoria()
{
return $this->categoria;
}
/**
* Add commentoarticle
*
* #param \AppBundle\Entity\CommentoArticle $commentoarticle
*
* #return Article
*/
public function addCommentoArticle(\AppBundle\Entity\CommentoArticle $commentoarticle)
{
$this->commentoarticle[] = $commentoarticle;
return $this;
}
/**
* Remove commentoarticle
*
* #param \AppBundle\Entity\CommentoArticle $commentoarticle
*/
public function removeCommentoArticle(\AppBundle\Entity\CommentoArticle $commentoarticle)
{
$this->commentoarticle->removeElement($commentoarticle);
}
/**
* Get commentoarticle
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCommentoArticle()
{
return $this->commentoarticle;
}
/**
* Set rate
*
* #param integer $rate
*
* #return Article
*/
public function setRate($rate)
{
$this->rate = $rate;
return $this;
}
/**
* Get rate
*
* #return integer
*/
public function getRate()
{
return $this->rate;
}
}
In the controller i have the update action
THIS IS THE CONTROLLER ACTION
public function update_ricettaAction(Request $request, $id)
{
//$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Non puoi accedere a questa pagina!');
$em = $this->getDoctrine()->getManager();
$recipe = $em->getRepository('AppBundle:Recipe')->find($id);
$form = $this->createForm(new RecipeType($recipe), $recipe);
$form->handleRequest($request);
if ($form->isValid())
{
$em = $this->getDoctrine()->getManager();
try
{
$em->persist($recipe);
$em->flush();
return $this->redirect($this->generateUrl('successricettaupdate'));
} catch (\Exception $e)
{
$form->addError(new FormError('errore nel database: ' . $e->getMessage()));
}
if ($form->isValid())
{
$var = $recipe;
$em->persist($var);
$em->flush();
return $this->redirect($this->generateUrl('successricettaupdate'));
} else
{
}
}
return $this->render('administration/update_ricetta.html.twig', array(
'recipe' => $recipe,
'form' => $form->createView()));
}
When i submit the form, to update all, some, or just one field of the entity, i get the error:
Error: Call to a member function move() on a non-object
I don't know what can it be...
Any suggestion?
SOLVED
I solved my own problem, and this is the solution if can help anyone:
In the Entity, i modified this:
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function uploadImage() {
// the file property can be empty if the field is not required
if (null === $this->image) {
return;
}
if(!$this->id){
$this->image->move($this->getTmpUploadRootDir(), $this->image->getClientOriginalName());
}else{
$this->image->move($this->getUploadRootDir(), $this->image->getClientOriginalName());
}
$this->setImage($this->image->getClientOriginalName());
}
in to this:
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function uploadImage() {
// the file property can be empty if the field is not required
if (null === $this->image) {
return;
}
if(!$this->id){
$this->image->move($this->getTmpUploadRootDir(), $this->image->getClientOriginalName());
}else{
return null;
}
}
Now I don't get any error, and the updating works!
I'm trying to create symfony2 application which will access mongoDB collection with the following structure:
{
"_id": ObjectId("5239c1c0359bf908058a5071"),
"param2": "test",
"param3": {
"subparam31": 0,
"subparam32": 0,
"subparam33": 0
},
"param4": 1
}
In symfony 2 I create a .yml, and php class. I correctly map only "_id", "param2" and "param4", but not "subparam31", "subparam32" and "subparam33" of "param3".
I use next file structure for mapping:
Params.mongodb.yml:
Acme\StoreBundle\Document\Params:
db: test
type: document
fields:
id:
id: true
param2:
type: string
param3:
type: mixed
subparam31:
type: float
subparam32:
type: float
subparam33:
type: float
param4:
type: float
Params.php
<?php
namespace Acme\StoreBundle\Document;
class Params
{
protected $param2;
protected $param4;
protected $param3;
protected $subparam31;
protected $subparam32;
protected $subparam33;
}
?>
Where I'm wrong? How to get and set values of subparams?
For accessing param2, param4 and id I use following code in controller which works:
$parameter = $this->get('doctrine_mongodb')
->getRepository('AcmeStoreBundle:Params')
->find($id);
$parameter2 = $parameter->getParam2();
$parameter4 = $parameter->getParam4();
if (!$format) {
throw $this->createNotFoundException('Not found parameter with id -> '.$id);
}
return array(
"parameter2" => $parameter2,
"parameter4" => $parameter4
);
I hope I was clear enough.
Thanks in advance.
I found the solution! In addition to mapping in yml, appropriate annotation in php classes also should be defined.
Here is the content the necessary files:
Params.mongodb.yml
Acme\StoreBundle\Document\Params:
db: test
type: document
embedOne:
param3:
targetDocument: Param3
fields:
id:
id: true
param2:
type: string
param4:
type: float
Param3.mongodb.yml
Acme\StoreBundle\Document\Param3:
db: test
type: embeddedDocument
fields:
subparam31:
type: float
subparam32:
type: float
subparam33:
type: float
Params.php
<?php
namespace Acme\StoreBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations;
/**
* #Doctrine\ODM\MongoDB\Mapping\Annotations\Document
*/
class Params
{
/**
* #Doctrine\ODM\MongoDB\Mapping\Annotations\Id
*/
protected $id;
/**
* #Doctrine\ODM\MongoDB\Mapping\Annotations\String
*/
protected $param2;
/**
* #Doctrine\ODM\MongoDB\Mapping\Annotations\Float
*/
protected $param4;
/**
* #Doctrine\ODM\MongoDB\Mapping\Annotations\EmbedOne(targetDocument="Param3")
*/
protected $param3;
public function __construct($subparam31 = NULL, $subparam32 = NULL, $subparam33 = NULL)
{
$param3 = new Param3($subparam31, $subparam32, $subparam33);
$this->setParam3($param3);
}
/**
* Get id
*
* #return id $id
*/
public function getId()
{
return $this->id;
}
/**
* Set param2
*
* #param string $param2
* #return self
*/
public function setParam2($param2)
{
$this->param2 = $param2;
return $this;
}
/**
* Get param2
*
* #return string $param2
*/
public function getParam2()
{
return $this->param2;
}
/**
* Set param4
*
* #param float $param4
* #return self
*/
public function setParam4($param4)
{
$this->param4 = $param4;
return $this;
}
/**
* Get param4
*
* #return float $param4
*/
public function getParam4()
{
return $this->param4;
}
/**
* Set param3
*
* #param Acme\StoreBundle\Document\Param3 $param3
* #return self
*/
public function setParam3(\Acme\StoreBundle\Document\Param3 $param3)
{
$this->param3 = $param3;
return $this;
}
/**
* Get param3
*
* #return Acme\StoreBundle\Document\Param3 $param3
*/
public function getParam3($toArray = false)
{
if ($toArray) {
if ($this->param3) {
return $this->param3->toArray();
}
}
return $this->param3;
}
public function toArray()
{
return array(
'param3' => $this->getParam3(true)
);
}
}
Param3.php
<?php
namespace Acme\StoreBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations;
/**
* #Doctrine\ODM\MongoDB\Mapping\Annotations\EmbeddedDocument
*/
class Param3
{
/**
* #Doctrine\ODM\MongoDB\Mapping\Annotations\Float
*/
protected $subparam31;
/**
* #Doctrine\ODM\MongoDB\Mapping\Annotations\Float
*/
protected $subparam32;
/**
* #Doctrine\ODM\MongoDB\Mapping\Annotations\Float
*/
protected $subparam33;
public function __construct($subparam31 = NULL, $subparam32 = NULL, $subparam33 = NULL)
{
$this->subparam31 = $subparam31;
$this->subparam32 = $subparam32;
$this->subparam33 = $subparam33;
}
/**
* Set subparam31
*
* #param float $subparam31
* #return self
*/
public function setSubparam31($subparam31)
{
$this->subparam31 = $subparam31;
return $this;
}
/**
* Get subparam31
*
* #return float $subparam31
*/
public function getSubparam31()
{
return $this->subparam31;
}
/**
* Set subparam32
*
* #param float $subparam32
* #return self
*/
public function setSubparam32($subparam32)
{
$this->subparam32 = $subparam32;
return $subparam32;
}
/**
* Get subparam32
*
* #return float $subparam32
*/
public function getSubparam32()
{
return $this->subparam32;
}
/**
* Set subparam33
*
* #param float $subparam33
* #return self
*/
public function setSubparam33($subparam33)
{
$this->subparam33 = $subparam33;
return $this;
}
/**
* Get subparam33
*
* #return float $subparam33
*/
public function getSubparam33()
{
return $this->subparam33;
}
public function toArray()
{
return array(
'subparam31' => $this->getSubparam3(),
'subparam32' => $this->getSubparam32(),
'subparam33' => $this->getSubparam33()
);
}
}
This question helped me.
I think what you're looking for is an EmbeddedDocument.
Define a separate document for param3 (that includes subparam31, subparam32, and subparam33) set it as the targetDocument in params. So Params.mongodb.yml would look something like:
db: test
type: document
embedOne:
params3:
targetDocument: params3Class
fields:
id:
id: true
param2:
type: string
param4:
type: float