Using column names when generating JSON in Propel - php

When using toJSON() on an ObjectCollection the names of the properties are always based on the PHP-names. For instance:
For the column type_name the JSON property becomes TypeName, etc.
Is there a way to make Propel use the name of the field/column instead?

If you don't mind using json_encode, try using the object's toArray() with arguments:
use Map\AuditTableMap as TableMap;
$something = new Something();
$something->setSomeColumnValue("value");
echo json_encode($something->toArray(SomethingMap::TYPE_FIELDNAME));
Output:
{"some_column_value": "value"}
In other words, use the argument <ObjectName>Map::TYPE_FIELDNAME to output an array with column names.
The docs are amazing, but they're quite confusing to navigate. I found the following comment from one of the generated models in my project. This is for version 2.0#dev, which I'm using; note that it may differ in your version. (I'd suggest looking at the docs for more formal guidance, but you can take a peek at your models too.)
/**
* Exports the object as an array.
*
* You can specify the key type of the array by passing one of the class
* type constants.
*
* #param string $keyType (optional) One of the class type constants TableMap::TYPE_PHPNAME, TableMap::TYPE_CAMELNAME,
* TableMap::TYPE_COLNAME, TableMap::TYPE_FIELDNAME, TableMap::TYPE_NUM.
* Defaults to TableMap::TYPE_PHPNAME.
* #param boolean $includeLazyLoadColumns (optional) Whether to include lazy loaded columns. Defaults to TRUE.
* #param array $alreadyDumpedObjects List of objects to skip to avoid recursion
* #param boolean $includeForeignObjects (optional) Whether to include hydrated related objects. Default to FALSE.
*
* #return array an associative array containing the field names (as keys) and field values
*/

If you only want to strictly use the toJSON call, then you'll have to do some post-processing manipulation of the string, because the only option allowed with the toJSON method is to include or not include lazy-loaded columns.
$something = new Something();
$something->setSomeColumnValue("value");
$json = $something->toJSON();
$tableMap = \Propel::getDatabaseMap()->getTableMap('Something');
$columnMaps = $tableMap->getColumns();
$phpNames = array();
$columnNames = array();
foreach ($columnMaps as $columnMap) {
$phpNames[] = '"' . $columnMap->getPhpName() . '"';
$columnNames[] = '"' . $columnMap->getColumnName() . '"';
}
$json = str_replace($phpNames, $columnNames, $json);
One caveat to this code is that if the value matches one of your column names exactly, it will be replaced. The only way to eliminate this is to json_decode the JSON object and only replace the keys, but if you don't want to use json_encode, I don't suppose you'd want to use json_decode.
#Cezille07's answer is the most correct in this case. My answer is mainly to show how TableMap/ColumnMaps can be used for post-processing, which is something I didn't know about when I started with Propel.

Related

explode column from database

Hi every I need to get product from the database and explode it in one line of code So I can get product data and get product_properties as an array together
$Product = Product::find(33);
$properties = explode(",", ($Product ->product_properties));
You could just explode product_properties to get an array or, you could also define an accessor on your Product model.
So in App\Product, add the following:
public function getPropertiesAttribute()
{
return explode(',', $this->product_properties);
}
That's it. Now, every time you retrieve your model, you also have access to its properties in an array format by doing the following:
$product = Product::find(33);
$product->properties; // will output product_properties in an array format
Edit: If you want this properties (computed) field to be included in its JSON representation, for example, you need to define it in the appends property in your model. For example.
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['properties'];
Here's a working example.
You can achieve it doing:
$properties = explode(",", (Product::find(33)->product_properties));
But this is poor in readability compared with your first approach.

How deserialize from array ids in jms?

I can not figure out how to deserialize a object from a array of IDs? Here is a simple example my array.
"roles":["1","2","3"]
But when trying I get the error that object expected-class AppBundle\Entity\Role... is There something for this? Thank you all for your attention!
The problem is the one mentioned by LBA in his answer. Basically in order for jms serializer to create an object, it need to be provided with an Array. The solution if you are ok with changing the structure of the api is also good.
I'm assuming your $role jms configuration looks something like this
/**
* #var ArrayCollection
*
* #JMS\Expose
* #JMS\Accessor(setter="setRoles")
* #JMS\SerializedName("roles")
* #JMS\Type("ArrayCollection<Role>")
* #JMS\Groups({"...."})
*
*/
private $roles;
In this case Jms will expect to get and array of arrays just like LBA mentioned. If instead you want he keep the current structure ("roles":["1","2","3"]) instead of "roles":["id": 1], ["id": 2], ["id": 3]], there is an alternative. You can extend the JsonDeserializationVisitor and this a very powerful tool in your arsenal to do almost anything you like with jms.
To do this first change your #Type like this(you will understand why latter)
* #JMS\Type("ArrayCollection<Role,Expand<id>>")
Now you use parameter overwrite, to extend JsonDeserializationVisitor, like this
<parameter key="jms_serializer.json_deserialization_visitor.class">MyBundle\Services\JsonDeserializationVisitor.php</parameter>
And then go and define the new Visitor something like this.
<?php
namespace MyBundle\Services\JmsSerializer;
use JMS\Serializer\Context;
use JMS\Serializer\JsonDeserializationVisitor as ParentJsonDeserializationVisitor;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
class JsonDeserializationVisitor extends ParentJsonDeserializationVisitor
{
/**
* #param PropertyMetadata $metadata
* #param mixed $data
* #param Context $context
*/
public function visitProperty(PropertyMetadata $metadata, $data, Context $context)
{
//This type is the information you put into the #Type annotation.
$type = $metadata->type;
$expand = null;
.......
/*Here you can extract the Expand<id> part that you added to the #Type config.
The Expand part will help you identify the fact that this property needs to be expanded and the id part will tell you how to expand id.
Based on if you do find this you will probably set a variable like $expand = $key, where $key is the "id" part.*/
......
if ($expand !== null) {
$expandedData = [];
foreach($data as $key->$value)
{
$expandedData[$key]=["$expand":$value];
}
parent::visitProperty($metadata, $expandedData, $context);
} else {
parent::visitProperty($metadata, $data, $context);
}
}
This is the basic stuff. Feel free to refactor the code however you like, this is just for fast proof of concept. Also Expand is just how i named this into the exemple. You can use it with whatever name you like. Sorry for not providing the code to extract this from $type but i don't remember the structure of $type right now. Also the structure will change if you do something like Expand<'id'> so play with it and see which fits best for you. Basically with this method you can extend and add features to any jms type you want. For instance you can add something like
#JMS\Type("string<translatable>")
And then extend JsonSerializationVisitor:visitString to call $translator->trans($data) on $data before returning it, and so you can translate a string before serializing it.
Something like this
<?php
namespace MyBundle\Service\JmsSerializer;
use JMS\Serializer\Context;
use JMS\Serializer\JsonSerializationVisitor as ParentJsonSerializationVisitor;
use Symfony\Component\Translation\Translator;
class JsonSerializationVisitor extends ParentJsonSerializationVisitor
{
/**
* #var Translator;
*/
private $translator;
const TRANSLATABLE = "translatable";
/**
* #param string $data
* #param array $type
* #param Context $context
* #return string
*/
public function visitString($data, array $type, Context $context)
{
$translatable = $this->getParameters(self::TRANSLATABLE, $type['params']);
if ($translatable) {
$data = (string)$this->translator->trans($data);
}
return parent::visitString($data, $type, $context);
}
.....................
Hope this helps. Let me know if you have any questions.
Alexandru Cosoi
It is deserializing your array, but it tries to deserialize it into an array of objects of type AppBundle\Entity\Role which seems not to be compatible with your values "1", "2" and so on and therefore throws an error as it cannot instansiate the objects.
You'll have to make sure that your content fits to the expected class.
If you can share your Class Definition for Role we might be able to help you how your array should look like (e.g. something like [["id": 1], ["id": 2], ["id": 3]] probably).

Create Associative array with ArrayCollection::map

TL;DR: Why does Doctrine's ArrayCollection only supports mapping an array without setting a key?
I want to create an associative array (key->value) from my Doctrine entities: i.e. customerId => CustomerName, userId => userName, etc. Creating an associative array isn't rocket science, so there are many other ways to achieve this.
However, I'm still wondering why ArrayCollection:map (or a similar method) doesn't have an option to do this. Creating an array with keys is support by it's constructor method and ArrayCollection::set(). You can even create an array like this:
$arraycollection = new ArrayCollection();
$arraycollection->add('John Doe');
$arraycollection->set('foo', 'bar');
$arraycollection->set(418, 'teapot');
But you can't set keys with ArrayCollection::map(). Why? Am I the first developer that is looking for a feature like this (not very likely) or am I missing an important principle that makes it unnecessary, impossible, undesireable or a bad practice?
I found this answer Adam Wathan's blog Customizing Keys When Mapping Collections:
This problem of wanting to customize keys during a map operation is
something I get asked about pretty regularly.
I think the reason it seems like a tricky problem is because in PHP,
we use the same data type to represent both a list and a dictionary.
He uses Laravel’s Collection library:
$emailLookup = $employees->reduce(function ($emailLookup, $employee) {
$emailLookup[$employee['email']] = $employee['name'];
return $emailLookup;
}, []);
That's exactly the solution I would like to use, but it isn't in Doctrine's ArrayCollection. A pull request to add a reduce() method has been closes because of backwards compatibility.
Thanks to this example, you can implement your own class, based on Doctrine's ArrayCollection:
use Doctrine\Common\Collections\ArrayCollection;
class ExtendedArrayCollection extends ArrayCollection
{
/**
* Reduce the collection into a single value.
*
* #param \Closure $func
* #param null $initialValue
* #return mixed
*/
public function reduce(\Closure $func, $initialValue = null)
{
return array_reduce($this->toArray(), $func, $initialValue);
}
}
This is how the ArrayCollection class is described in the source code:
An ArrayCollection is a Collection implementation that wraps a regular PHP array.
Its map() method is just a wrapper of array_map() that returns a new ArrayCollection object that wraps the PHP array returned by array_map().
Just to make everything clear, array_map() calls a function (its first argument) for each element of the array; the value returned by the function is stored in the resulting array.
For example:
$input = [ 'one' => 1, 'two' => 2, 'three' => 3, ];
$output = array_map(function ($x) { return $x * $x; }, $input);
print_r($output);
outputs:
Array
(
[one] => 1
[two] => 4
[three] => 9
)
array_map() can be invoked using one or more arrays (starting with its second argument). ArrayCollection::map() calls it using only one array (the one it wraps). When it's called with a single array, array_map() preserves its string keys (but it re-numbers the numeric keys).
Revision:
ArrayCollection::map() doesn't set keys or values. It applies a function to all the values stored in the collection and returns a new collection.
If you need to put a value at a specified key in an object of type ArrayCollection, you can use the regular PHP syntax to access array elements using square brackets.
The following code is equivalent with the code you posted in the question.
$arraycollection = new ArrayCollection();
$arraycollection[] = 'John Doe';
$arraycollection['foo'] = 'bar';
$arraycollection[418] = 'teapot';
The access using square brackets works because the ArrayCollection class implements the Collection interface that extends the ArrayAccess PHP interface.
But you can't set keys with ArrayCollection::map().
Map is not a function to convert from a collection into a map.
In general map function operates on values from the list and is independent from the data structure so the behavior of ArrayCollection:map is quite natural.

Laravel model with POINT/POLYGON etc. using DB::raw expressions

I have some models that use geospatial fields like POINT, POLYGON or MULTIPOLYGON. I would like to tell my model to process these attributes in a special way, for me to get the desired model attributes set.
Example:
Every regular Model::find() or other Eloquent method should apply some custom code before storing or after retrieving a database value.
$area->surface is a POLYGON field in MySQL, but in my model class I would like to handle $area->surfare as an array of points.
On SELECT I would therefore like to 1) fetch the value using a raw expression to get a text representation of the value, and 2) go through some custom PHP code to convert the WKT string into an array.
On INSERT/UPDATE I would like to take the attribute value (an array) and 1) convert it into a WKT string, whereafter 2) it's written to the databse using a DB raw statement that stores the value.
I'd like to set this on a field-basis, not as special get/set functions for each field, and not in the controllers - because I have many geosptial fields.
Is there a way to achieve this in Laravel?
(A more abstract version of the same question, is how I can create code that manipulates attribute values for the actual SQL queries, rather than just some value-based manipulation via mutators & accessors)
UPDATE:
Looking deeper into the Laravel Doc and API, I found that maybe the Eloquent::newQuery() method is what I need to manipulate? Would that be used for any query regardless if SELECT, INSERT or UPDATE?
We have now solved this generically for all models by extending our base model with the following functionaly:
We define an array of attributes that hold geometric data.
We decide on a per-model-basis if we want this to be auto-loaded as text.
We change the default query builder to select the geometry attributes as text from the database.
Here is an excerpt from the base model we now use:
/**
* The attributes that hold geometrical data.
*
* #var array
*/
protected $geometry = array();
/**
* Select geometrical attributes as text from database.
*
* #var bool
*/
protected $geometryAsText = false;
/**
* Get a new query builder for the model's table.
* Manipulate in case we need to convert geometrical fields to text.
*
* #param bool $excludeDeleted
* #return \Illuminate\Database\Eloquent\Builder
*/
public function newQuery($excludeDeleted = true)
{
if (!empty($this->geometry) && $this->geometryAsText === true)
{
$raw = '';
foreach ($this->geometry as $column)
{
$raw .= 'AsText(`' . $this->table . '`.`' . $column . '`) as `' . $column . '`, ';
}
$raw = substr($raw, 0, -2);
return parent::newQuery($excludeDeleted)->addSelect('*', DB::raw($raw));
}
return parent::newQuery($excludeDeleted);
}
If mutators and accessor does not fit your needs, you can manipulate those attributes with Model Events.
Then you can execute code when some of the Eloquent events are fired: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored.
For all who use postgres database, laravel-postgis can be use for geometric data

Can I use PHP reflection to extract function code?

I am using PHP ReflectionClass to extract as much information about a class as possible. Can I also use this to get return values from functions? Or does that not make sense since reflection only profiles what an object accepts?
You can not rely, if a function has a well defined return value you can simply extract from the source code. Imagine something like this:
return $this->isValid() ? $result : $this->createNullObject();
Thats hard (/impossible) to parse just to get the return value. You can use DocComments instead. #return is the usual tag for that use
/**
* MyMethod
*
* #return int
*/
Call getDocComment() on a ReflectionMethod-object and then parse the docComment.
for internal functions you could use
$reflect = new ReflectionExtension('standard');
echo "<pre>" . $reflect . "</pre>";

Categories