flush() doesn't update embed documents - mongodb

I have this classes:
class Country
{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
*/
protected $iso;
/**
* #MongoDB\EmbedOne(targetDocument="Localstring")
*/
protected $name;
public function __construct(){
$this->name = new Localstring();
}
}
class Localstring
{
/**
* #MongoDB\Id
*/
private $id;
/**
* #MongoDB\Hash
*/
private $location = array();
}
I want to update every country with a new translation:
$dm = $this->get('doctrine_mongodb')
->getManager();
foreach ($json as $iso => $name) {
$country = $dm->getRepository('ExampleCountryBundle:Country')->findOneByIso($iso);
$localstring_name = $country->getName();
$localstring_name->addTranslation('es_ES', $name);
$dm->flush();
}
If I print one object just before flushing it prints correctly:
Example\CountryBundle\Document\Country Object ( [id:protected] => 541fe9c678f965b321241121 [iso:protected] => AF [name:protected] => Example\CountryBundle\Document\Localstring Object ( [id:Example\CountryBundle\Document\Localstring:private] => 541fe9c678f965b321241122 [location:Example\CountryBundle\Document\Localstring:private] => Array ( [en_EN] => Afghanistan [es_ES] => Afganistán ) ) )
But on database it doesn't updates. I tried updating $iso and it works. Why this happens?

You forgot to persist your object. flush() just pushes into DB your changes that were registered by persist() (called with your object in argument). It needs to be here because you doesn't change your document. You just added the translation. This functionality covered by Translatable extension and doesn't tell to the Doctrine that your object was modified. And when Doctrine will prepare the changelist for query it will not find changes and will not create the query.
Your code should look like this:
$dm = $this->get('doctrine_mongodb')
->getManager();
foreach ($json as $iso => $name) {
$country = $dm->getRepository('ExampleCountryBundle:Country')->findOneByIso($iso);
$localstring_name = $country->getName();
$localstring_name->addTranslation('es_ES', $name);
$dm->persist($country);
}
$dm->flush();

you forgot the persist your object!
try this at the end of your foreach: $dm->persist($your_object);
and outside form foreach put the $dm->flush();

Related

automatching property_path in Symfony API

I'm having a REST-API built in Symfony3.
As an example here are the API-fields of Price in a form, made with the FormBuilderInterface. The code-example below is of ApiBundle/Form/PriceType.php
class PriceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, array(
'description' => 'Name',
))
->add('price_category', EntityPublicKeyTextType::class, array(
'class' => 'MyCustomBundle:PriceCategory',
'property_path' => 'priceCategory',
))
The issue is about good response messages of fields which have e.g. a validation error. For default symfony-types (e.g. IntegerType, TextType) it can find the property_path automatically and hands me out an useful error message. Here is the API-response with two errors:
name can be resolved in a good way (because I see what field it is about,
for price_category it can't resolve it (second message).
{
"name": [
"This value is too long. It should have 50 characters or less."
],
"0": "This value should not be null."
}
To resolve the issue. I add 'property_path' => 'priceCategory' for the field price_category. The value of property_path is matching with BaseBundle/Entity/Price.php where the var protected $priceCategory; is defined.
After adding property_path the error message looks fine.
{
"name": [
"This value is too long. It should have 50 characters or less."
],
"price_category": [
"This value should not be null."
]
}
The class of price_category is EntityPublicKeyTextType which is abstracted from TextType (which can do errors just fine).
Therefore I have the following question: What do i have to add to my inherited class EntityPublicKeyTextType to avoid adding the property_path for all fields by hand?
Any hint to fix this is highly welcome
Best endo
EDIT:
EntityPublicKeyTextType:
class EntityPublicKeyTextType extends AbstractType
{
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new ObjectToPublicKeyTransformer(
$this->om,
$options['class'],
$options['public_key'],
$options['remove_whitespaces'],
$options['multiple'],
$options['string_separator'],
$options['extra_find_by']
);
$builder->addModelTransformer($transformer);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setRequired(array(
'class',
'public_key'
))
->setDefaults(array(
'multiple' => false,
'string_separator' => false,
'extra_find_by' => array(),
'remove_whitespaces' => true,
));
}
public function getParent()
{
return TextType::class;
}
public function getBlockPrefix()
{
return 'entity_public_key_text';
}
}
ObjectToPublicKeyTransformer:
class ObjectToPublicKeyTransformer implements DataTransformerInterface
{
/**
* #var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* #var ObjectManager
*/
private $om;
/**
* #var string
*/
private $class;
/**
* #var string|string[]
*/
private $publicKey;
/**
* #var bool
*/
private $removeWhitespaces;
/**
* #var boolean
*/
private $multiple;
/**
* #var boolean|string
*/
private $stringSeparator;
/**
* #var array
*/
private $extraFindBy;
public function __construct(
ObjectManager $om,
string $class,
$publicKey,
bool $removeWhitespaces,
bool $multiple = false,
$stringSeparator = false,
array $extraFindBy = array(),
PropertyAccessorInterface $propertyAccessor = null
) {
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
$this->om = $om;
$classMetadata = $om->getClassMetadata($class);
$this->class = $classMetadata->getName();
$this->publicKey = $publicKey;
$this->stringSeparator = $stringSeparator;
$this->multiple = $multiple;
$this->extraFindBy = $extraFindBy;
$this->removeWhitespaces = $removeWhitespaces;
}
/**
* Transforms an object / Collection of objects to a publicKey string / array of publicKey strings.
*
* #param object|Collection $object
* #return string|array
*/
public function transform($object)
{
if (null == $object) {
return null;
}
if (is_array($this->publicKey)) {
$publicKey = $this->publicKey[0];
} else {
$publicKey = $this->publicKey;
}
if ($this->multiple) {
if ($object instanceof Collection) {
$values = array();
foreach ($object as $objectItem) {
$values[] = (string)$this->propertyAccessor->getValue($objectItem, $publicKey);
}
if ($this->stringSeparator) {
return implode($this->stringSeparator, $values);
}
return $values;
}
} else {
return (string)$this->propertyAccessor->getValue($object, $publicKey);
}
}
/**
* Transforms an publicKey string / array of public key strings to an object / Collection of objects.
*
* #param string|array $value
* #return object|Collection
*
* #throws TransformationFailedException if object is not found.
*/
public function reverseTransform($value)
{
if (null === $value) {
return $this->multiple ? new ArrayCollection() : null;
}
if (is_array($this->publicKey)) {
$publicKeys = $this->publicKey;
} else {
$publicKeys = array($this->publicKey);
}
if ($this->multiple) {
if ($this->stringSeparator) {
$value = explode($this->stringSeparator, $value);
}
if (is_array($value)) {
$objects = new ArrayCollection();
foreach ($value as $valueItem) {
foreach ($publicKeys as $publicKey) {
$object = $this->findObject($valueItem, $publicKey);
if ($object instanceof $this->class) {
$objects->add($object);
break;
}
}
}
return $objects;
}
}
foreach ($publicKeys as $publicKey) {
$object = $this->findObject($value, $publicKey);
if ($object instanceof $this->class) {
return $object;
}
}
return $this->multiple ? new ArrayCollection() : null;
}
private function findObject($value, $publicKey)
{
if ($this->removeWhitespaces) {
$value = str_replace(' ', '', $value);
}
$findBy = array_merge([$publicKey => $value], $this->extraFindBy);
$object = $this->om->getRepository($this->class)->findOneBy($findBy);
return $object;
}
}
It would be useful if you also provide your Price model/entity class. It seems that you are using camel case for the property name in your model (priceCategory) and then you use snake case in your form (price_category).
If you use the same convention for the model and the form, the validation errors will automatically map to the correct property.
The explanation is that Symfony's mappers can still map your fields by transforming snake to camel case and vice versa, that's why your form is still working and submitting values even without using the property_path option. But the problem is that the validator does not do this mapping and cannot match the correct property (price_category -> priceCategory).

TYPO3 Gridelements. Render assets from child

I'm creating a content Element with gridelments where i need to render some Data from the childs directly. There is no problem for fields like "bodytext" or "header". But the assets gives me only a counter, not a reference or path.
So the big question: How can i render the assets images from the childs?
I can share a VH I just did
<?php
namespace GeorgRinger\Theme\ViewHelpers;
use TYPO3\CMS\Core\Database\DatabaseConnection;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3\CMS\Frontend\Resource\FileCollector;
class FalViewHelper extends AbstractViewHelper
{
/**
* #var boolean
*/
protected $escapeOutput = FALSE;
/**
* #param string $table
* #param string $field
* #param string $id
* #param string $as
* #return string
*/
public function render($table, $field, $id, $as = 'references')
{
$row = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', $table, 'uid=' . (int)$id);
if (!$row) {
return '';
}
$fileCollector = GeneralUtility::makeInstance(FileCollector::class);
$fileCollector->addFilesFromRelation($table, $field, $row);
$this->templateVariableContainer->add($as, $fileCollector->getFiles());
$output = $this->renderChildren();
$this->templateVariableContainer->remove($as);
return $output;
}
/**
* #return DatabaseConnection
*/
protected function getDatabaseConnection()
{
return $GLOBALS['TYPO3_DB'];
}
}
Of course you need to adopt the namspace.
Usage would be
<theme:fal table="tt_content" field="assets" id=" => the id <= ">
<f:debug>{references}</f:debug>
</theme:fal>

Symfony 2 collection with image - editing

I have little problem with my collection form type.
I have relation 1 to many ( product and productImages )
I think that the problem is in my productImages Entity ( if it is possible to solve )
My productImages:
class produktImage
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #Assert\File(maxSize="6000000")
*/
public $file;
/**
* #ORM\ManyToOne(targetEntity="product", inversedBy="tags")
*/
protected $product;
and the most importatnt method:
public function upload()
{
$this->name = trim(date("dmyGis").rand(1,99999999).'_'.$this->file->getClientOriginalName());
$this->file->move($this->getUploadRootDir(), $this->name);
$move = new resizeImg(1280, 3000, $this->name, $this->getUploadRootDir(), null);
$move->setThumbial(550, 2000, $this->getUploadRootDir().'../small/', null);
$move->setThumbial(200, 2000, $this->getUploadRootDir().'../thumb/', null);
$this->file = null;
}
Form productImage has file field only.
My product entity ( the most importatnt methods )
public function addPhotosum($photos)
{
$photos->setProdukt($this);
$photos->upload($this);
$this->photos->add($photos);
return $this;
}
public function removePhotosum($photos)
{
$this->photos->removeElement($photos);
$photos->removeImg();
}
Ok, but where is my problem.
When I try to add or remove file everything is ok.
If I try to edit file nothing happen. Name and file don't change.
I think it is because Product don't see changes in name ( name is stored in db only ) but I don't know how can I tell him "change name and file when file is diffrent".
Can I use preUpdate or something else?
Someone had similar problem?
Just call the upload() method in your edit action of your controller ..
example :
public function editAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('YourBundle:YourEntity')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Your entity.');
}
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$entity->upload()
$entity->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('your_destination_route', array('id' => $id)));
}
return array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
);
}
I hope it will work for you ..

Annotations Namespace not loaded DoctrineMongoODMModule for Zend Framework 2

I've loaded up the Doctrine MongoODM Module for zf2. I have got the document manager inside my controller, and all was going well until I tried to persist a document. It fails with this error:
"[Semantical Error] The annotation "#Document" in class SdsCore\Document\User was never imported."
It seems to fail on this line of DocParser.php
if ('\\' !== $name[0] && !$this->classExists($name)) {
It fails because $name = 'Document', and the imported annotation class is 'Doctrine\ODM\MongoDB\Mapping\Annotations\Doctrine'
Here is my document class:
namespace SdsCore\Document;
/** #Document */
class User
{
/**
* #Id(strategy="UUID")
*/
private $id;
/**
* #Field(type="string")
*/
private $name;
/**
* #Field(type="string")
*/
private $firstname;
public function get($property)
{
$method = 'get'.ucfirst($property);
if (method_exists($this, $method))
{
return $this->$method();
} else {
$propertyName = $property;
return $this->$propertyName;
}
}
public function set($property, $value)
{
$method = 'set'.ucfirst($property);
if (method_exists($this, $method))
{
$this->$method($value);
} else {
$propertyName = $property;
$this->$propertyName = $value;
}
}
}
Here is my action controller:
public function indexAction()
{
$dm = $this->documentManager;
$user = new User();
$user->set('name', 'testname');
$user->set('firstname', 'testfirstname');
$dm->persist($user);
$dm->flush;
return new ViewModel();
}
I didn't yet work on the DoctrineMongoODMModule, but I'll get to it next week. Anyway, you are still using the "old way" of loading annotations. Most of the doctrine projects are now using Doctrine\Common\Annotations\AnnotationReader, while your #AnnotationName tells me that you were using the Doctrine\Common\Annotations\SimpeAnnotationReader. You can read more about it at the Doctrine\Common documentation
So here's how to fix your document:
<?php
namespace SdsCore\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
/** #ODM\Document */
class User
{
/**
* #ODM\Id(strategy="UUID")
*/
private $id;
/**
* #ODM\Field(type="string")
*/
private $name;
/**
* #ODM\Field(type="string")
*/
private $firstname;
/* etc */
}

Updating single database cell in Zend Framework Model

Simple one hopefully, is there a specific way i should be updating a single database value using a model in Zend Framework.
I currently do this:
class Model_MyModel extends Zend_Db_Table_Abstract
{
$_name = 'table';
public function updateSetting($id,$status)
{
$data = array(
'status' => $status
);
$this->update($data, 'id = '.$id);
}
}
$update = new Model_MyModel();
$update->updateSetting(10,1);
Obviously i could pass in another argument as the column to update. I just wondered if there was a more "magic" way i should be doing this?
You could write a simple property overloader for this:
class Model_MyModel extends Zend_Db_Table_Abstract
{
protected $_name = 'table';
/**
* Should be a Zend_Db_Table_Row instance
*
* #var Zend_Db_Table_Row
*/
protected $_currentRow = null;
/**
* Property overloader
*
* For more information on this see
* http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members
*
* #param string $key
* #param string $value
* #return void
*/
public function __set($key, $value)
{
$row = $this->getCurrentRow();
if (null !== $row)
{
$row->$key = $value;
}
else
{
throw new Exception('Cannot update a column on a non existent row!');
}
}
/**
* Save current row
*
* #return Model_MyModel
*/
public function saveCurrentRow()
{
$row = $this->getCurrentRow();
if (null !== $row)
{
$row->save();
}
else
{
throw new Exception('Cannot save a non existent row!');
}
}
/**
* Set current row
*
* #param Zend_Db_Table_Row $row
* #return Model_MyModel
*/
public function setCurrentRow(Zend_Db_Table_Row $row)
{
$this->_currentRow = $row;
return $this;
}
/**
* Get current row
*
* #return Zend_Db_Table_Row
*/
public function getCurrentRow()
{
return $this->_currentRow;
}
}
You could then do stuff like this:
$model = new Model_MyModel();
$model->status = 'foo';
$model->somecolumn = 'bar'
$model->saveCurrentRow();
Although this approach would require the least editing in your code, an even better approach would be seperating your models from your database tables and use a Data Mapper Pattern as described in the Quickstart.