I am aware that ODM purpose is mapping, but I'm also curious if I can save a JSON Object without mapping it to any class. Or with mapping it to a class but having only one object value $object.
I've managed to do this when ever I would have an array of objects, for instance:
[
{
"id":28,
"Title":"Sweden"
},
{
"id":56,
"Title":"USA"
},
{
"id":89,
"Title":"England"
}
]
I've managed to save that array of objects without mapping the id, Title, and other fields which are not there.
/**
* #MongoDB\Field(name="object", type="hash")
*/
protected $object = array();
My question is if I can do the same thing only for the JSON Objects, not array objects. For instance, I'd like to save this object without mapping every single key:
{
"id":28,
"Title":"Sweden"
},
{
"id":56,
"Location":"New York"
},
{
"id":89,
"Something": {
"test": "test
}
}
ODM provides you with RawType which just saves whatever is given:
/**
* #MongoDB\Field(name="object", type="raw")
*/
protected $object;
I'm not sure though if there's a mistake in your desired stored code as there are 3 objects and that you won't have as you can't map 3 objects to 1 property.
Related
is it possible to implement a custom hydration and persistence in Doctrine 2 on a per entity basis?
Doctrine 2 has some major limitations regarding value objects (e.g. collections and ids). I wonder if it would be possible to use custom mechanisms (or implementations) for the mapping from object properties to the database (loading and persistence).
I know there are some possibilities to "solve" this problem but I like none of them:
Fake entities require proper handling in the entity which leaks the persistence layer into the domain objects
real entities require a lot more work in persistence (more repositories and more complex handling)
Embaddables have the mentioned limitations
Custom DBAL types with serialization makes querying for certain values impossible or at least extremely slow
I know there are the lifecycle events in doctrine which may be usable. I could't find out if the postLoad event carries an already constructed entity object (with all the VOs)? Becuase in that case it would be useless to me.
best regards,
spigandromeda
Yes, you can register new hydrators in your config/packages/doctrine.yaml like this:
doctrine:
dbal: ...
orm:
hydrators:
CustomEntityHydrator: 'App\ORM\Hydrator\CustomEntityHydrator'
...
mapping: ...
...
You can then use it in your queries like this:
public function findCustomEntities(): array
{
return $this->createQueryBuilder('c')
...your query logic...
->getResult('CustomEntityHydrator');
}
Note, that you can only specify which hydrator you want to use for the root entity. If you fetch associated entities you might end up with a more complicated setup that is hard to debug.
Instead you could consider dealing with value objects (VOs) only in the interface of your entity. In other words, the fields are scalar values, but your method arguments and return values are VOs.
Here is an example with an entity that has a id of type Uuid, a location (some numeric identifier), status (e.g. ternary true/false/null). These are only there to showcase how to deal with different type of value objects:
/**
* #ORM\Entity()
*/
class CustomEntity
{
/**
* #ORM\Id()
* #ORM\Column(type="string", length=64)
*/
private string $id;
/**
* #ORM\Column(type="int")
*/
private int $location;
/**
* #ORM\Column(type="bool, nullable=true)
*/
private bool $status;
private function __construct(Uuid $id, Location $location, Status $status)
{
$this->id = (string) $id;
$this->location = $location->getValue();
$this->status = $status->get();
}
public static function new(Location $location, Status $status): self
{
return new self(Uuid::v4(), $location, $status);
}
public function getId(): Uuid
{
return Uuid::fromString($this->id);
}
public function getLocation(): Location
{
return new Location($this->location);
}
public function activate(): void
{
$this->status = true;
}
public function deactivate(): void
{
$this->status = false;
}
public function isActive(): bool
{
$this->status === true;
}
public function isInactive(): bool
{
$this->status === false;
}
public function isUninitialized(): bool
{
$this->status === null;
}
public function getStatus(): Status
{
if ($this->status === null) {
return new NullStatus();
}
if ($this->status === true) {
return new ActiveStatus();
}
return new InactiveStatus();
}
}
As you can see, you could replace new() with a public constructor. It would work similar with setters. I sometimes even use (private) setters for this in the constructor. In case of the status you don't even need setters if you instead use multiple methods that set the value internally. Similarly you might want to return scalar values instead of a VO in some cases (or the other way around as shown with the status getter and issers).
The point is, your entity looks from the outside as if it would use your VOs, but internally it already switches to a representation that works better with Doctrine ORM. You could even mix this with using VOs and custom types, e.g. for the UUID. You just have to be careful, when your VO needs more info for being constructed than you want to store in the database, e.g. if the numeric location in our example would also use a locale during creation, then we would need to store this (which makes sense as it seems to be related to the numeric id) or we have to hardcode it in the entity or add an abstraction above, that has access to the locale, in which case your entity would likely not return a Location or at least not a LocalizedLocation.
You might also want to consider not having a VO for each and every property in your entity. While it definitely can be helpful, e.g. to wrap an Email into a custom VO to ensure validity instead of just type hinting for string, it might be less useful for something as generic as a (user's) name, which should be very lenient with which strings it accepts as there are a wide variety of names. Using the approach above you can easily introduce a VO later, by adding a new getter for the VO, changing new() or any other method that mutates your property and then not having to change anything in the data model below (unless there is a more drastic change to how the value is represented).
I have a nested Json like this:
string strJson = "{
"Header":
{"Version":"V0.00.01","ID":"1000","Name":"SetEnvValues"}
,
"Data":
{"Temp":0.00,"rH":0.00,"CO2":0.00,"O2":0.00 }
}";
Now I want to test if there is an Element "rH" existent. For example, if I only have in the Data Objekt one value "Temp", how can I test which values are existent? Is this possible without exception handling?
{
"Header":
{"Version":"V0.00.01","ID":"1000","Name":"SetEnvValues"}
,
"Data":
{"Temp":0.00 }
}
I tried it with count, but it seems this wont work for nested objects:
jsonReceivedEnvValues = json::parse(strJson);
int count = jsonReceivedEnvValues["Data"].count("Bla");
This returns always one, I think because it only tests the "Data" Object, and not the deeper nested ones.
It can be done with jsonReceivedEnvValues["Data"].count("Bla"). Make sure that you don't accidentally create the object you're looking for; for instance, jsonReceivedEnvValues["Data"]["Bla"].is_null(); creates an object ["Bla"] in ["Data"]
I have a already json11 object build:
Json my_json = Json::object {
{ "key1", "value1" },
{ "key2", false },
{ "key3", Json::array { 1, 2, 3 } },
};
And I want to add a new value to key3 array like this:
my_json["keys3"].push_back(4);
How I can achieve that? I can't see anything to modify objects (all operator to access values are const!)
Unfortunately it seems you cannot modify directly an instance of Json.
It's an opaque wrapper around a JsonValue that is inaccessible.
Anyway, note that a Json::object is a std::map<std::string, Json>. You can create a copy of your original Json::object as it follows:
Json::object json_obj = my_json.object_items();
Then the key keys3 contains a Json::array, that is nothing more than a std::vector<Json>.
You can modify it as it follows:
json_obj["keys3"].push_back(4);
Finally you must create a new Json from your Json::object and that's all:
Json another_json = json_obj;
Quite expensive an operation.
I suspect the right way is to create your objects step by step and at the very end of your process create an instance of a Json.
I found next issues on github about this question:
[https://github.com/dropbox/json11/issues/20]: more o less the same that skypjack explain
The Json type is immutable, but the Json::object type is just a
std::map, so your code would work if the first line created a
Json::object instead. You can use that map to build whatever data you
want, then wrap it in as Json(data) when you're done modifying it. You
can also extract the map from a Json using object_items(), copy it,
mutate it, and use it to create a new Json, similar to a builder
pattern.
[https://github.com/dropbox/json11/issues/75]: This one is very interesting because explain why it's not possible to modify a json
The Json type is intended to be an immutable value type, which has a
number of advantages including thread safety and the ability to share
data across copies. If you want a mutable array you can use a
Json::array (which is just a typedef for a vector) and mutate it
freely before putting it into a Json object.
If you are using json11 you can do it like this:
Json json = Json::object
{
{
"num_neurons_in_each_layer", Json::array{ 1000, 1000, 10, 10 }
},
{
"non_editable_data",
Json::object
{
{"train_error", -1.0 },
{"validation_error", -1.0 }
}
}
};
Json* p_error = const_cast<Json*>(&json["non_editable_data"].
object_items().find("validation_error")->second);
*p_error = Json(2.0); //"validation_error" has been modified to 2.0
p_error = nullptr;
delete p_error;
Doctrine2 has a built-in type 'array' that I find useful for my project. It perfectly works with arrays of scalar types. But now I want to use an array of objects. Something like this:
/**
* #ORM\Entity
*/
class MyEntity {
/**
* #var MyEntityParameter[] array of MyEntityParameter instances
*
* #ORM\Column(name="parameters", type="array", nullable=true)
*
*/
private $parameters;
}
Where MyEntityParameter is a class that can be serialized. I also use it in the Symfony's Form Builder.
My plan works perfectly, except that when the field in the MyEntityParameter instance gets changed, Doctrine doesn't detect it and thus doesn't update the record. If I delete or add array elements, Doctrine detects that. I realize that this happens because class instance object id doesn't change when I change its field, but then how can I make it so that Doctrine detects this change?
I found a working solution for me. I don't think it's that elegant, but in case if there are no good ways to solve this problem it can work for me and for others.
First of all, I decided not to keep objects in the array, but instead keep arrays. I, however, still want to use MyEntityParameter class in the Symfony's form builder. In this case, the idea is to disable mapping for our field:
In the form builder we do the following:
// Acme/Bundle/DemoBundle/Form/Type/MyEntityType.php
// ...
class MyEntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('parameters', 'collection', array(
'mapped' => false, // do not map this field
'type' => new MyEntityParameterType(),
// ... other options ...
));
}
}
In the child type (MyEntityParameterType) we set 'data_class' to MyEntityParameter (I don't show the code, as it's not related to the problem).
Now all we need is to manually fill and process the data for this not-mapped field.
In the controller:
public function editAction($id, Request $request)
{
// ...
// $object is an instance of MyEntity
$form = $this->createForm(new MyEntityType(), $object);
$parameters = $object->getParameters();
if ($parameters) {
foreach ($parameters as $key => $parameter)
{
$form->get('parameters')->add($key, new MyEntityParameterType(),
array(
// here I assume that the constructor of MyEntityParameter
// accepts the field data in an array format
'data' => new MyEntityParameter($parameter),
)
);
}
}
if ($request->isMethod('POST')) {
$form->submit($request);
$parameters = array();
foreach ($form->get('parameters')->all() as $parameter) {
// here first getData() gives MyEntityParameter instance
// and the second getData() is just a method of MyEntityParameter
// that returns all the fields in an array format
$parameters[] = $parameter->getData()->getData();
}
$object->setParameters($parameters);
// if the parameters were changed in the form,
// this change will be detected by UnitOfWork
}
// ...
}
I remember coming across this issue before, and a workaround that worked for me was to set a new object so Doctrine would recognise the entity property has been modified, then set the object that has the changes you want persisted.
$parameters = $entity->getParameters();
$parameters->foo = "bar";
$entity->setParameters(new MyEntityParameter());
$entity->setParameters($parameters);
$em->persist($entity);
If it's an array of MyEntityParameter instances then the following code might work.
$parameters = $entity->getParameters();
$parameters[3]->foo = "bar"; // Just an example
$entity->setParameters(array(new MyEntityParameter()));
$entity->setParameters($parameters);
$em->persist($entity);
In Symfony 2 I generate a Bundle for storing any type of document into database, but I need the BLOB column type.
Tnx to this question I add the class BlobType into Doctrine DBAL, but for use the new column type I had to change
Doctrine\DBAL\Types\Type
[...]
const BLOB = 'blob';
[...]
private static $_typesMap = array(
[...],
self::BLOB => 'Doctrine\DBAL\Types\BlobType',
);
Doctrine\DBAL\Platforms\MySqlPlatform (maybe it was better if I had changed Doctrine\DBAL\Platforms\AbstractPlatform)
[...]
protected function initializeDoctrineTypeMappings()
{
$this->doctrineTypeMapping = array(
[...],
'blob' => 'blob',
);
}
[...]
/**
* Obtain DBMS specific SQL to be used to create time fields in statements
* like CREATE TABLE.
*
* #param array $fieldDeclaration
* #return string
*/
public function getBlobTypeDeclarationSQL(array $fieldDeclaration)
{
return 'BLOB';
}
Now I don't have mouch time for a 'pretty solution', but in future I would like to restore the Doctrine classes and be able to assign the new column type into Symfony 2 bootstrap.
I think I should edit my app/bootstrap.php.cache but I don't have idea how to intervene.
this worked for me:
create your blobtype (See https://gist.github.com/525030/38a0dd6a70e58f39e964ec53c746457dd37a5f58)
add this to your Bundle initialization (/src/YOURDOMAIN/YOURBUNDLE/YOURDOMAINYOUBUNDLE.php)
class YourBundle extends Bundle
{
public function boot()
{
$em = $this->container->get('doctrine.orm.entity_manager');
Type::addType('blob', 'YOURDOMAIN\YOURBUNDLE\YOURTYPEDIRECTORY\BlobType');
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('blob','blob');
}
}
Small improvement for registration blob type in XXXBundle::boot(), but can be necessary during unittests.
class XXXBundle extends Bundle
{
public function boot()
{
// Add blob type
if(!Type::hasType('blob')) {
Type::addType('blob', '{CLASS_PATH}\\Blob');
}
// Add blob type to current connection.
// Notice: during tests there can be multiple connections to db so
// it will be needed to add 'blob' to all new connections if not defined.
$em = $this->container->get('doctrine.orm.entity_manager');
if (!$em->getConnection()->getDatabasePlatform()->hasDoctrineTypeMappingFor('blob')) {
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('blob','blob');
}
}
I just found this gist:
https://gist.github.com/525030/38a0dd6a70e58f39e964ec53c746457dd37a5f58
app/bootstrap.php:
<?php
// ...
$em = Doctrine\ORM\EntityManager::create($conn, $config, $evm);
// types registration
Doctrine\DBAL\Types\Type::addType('blob', 'Doctrine\DBAL\Types\Blob');
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('BLOB', 'blob');
BTW bootstrap.cache.php is auto-generated AFAIK.. So changes there would be overwritten.