JMS Serializer Exclude Entity field in Symfony2 Controller - json

I have an Entity of mine which I would like to expose in a JSON API that I'm developing, the problem is that in this particular controller, there is just one field which I don't want to expose. Is there a way to exclude it from serialization from within the controller?
I know I can annotate my entity so the serializer just passes by that field, but what happens in all the other cases? This is really the exception.

You can assign each property to a group,
then define that group in a context when serializing
from the controller.
Your entity:
use JMS\Serializer\Annotations as Serializer;
class Comment
{
/** #Serializer\Groups({"main", "secondary"}) */
private $id;
/** #Serializer\Groups({"main", "secondary"}) */
private $title;
/** #Serializer\Groups({"main", "secondary"}) */
private $name;
/** #Serializer\Groups({"main"}) */
private $email;
/** #Serializer\Groups({"main", "secondary"}) */
private $message;
}
Then in your controller
use JMS\Serializer\SerializationContext;
$serializer->serialize(
new Comment(),
'json',
SerializationContext::create()->setGroups(array('secondary'))
);
In this example, the email field is excluded from the serialized data, but only for the group named secondary. You can of course call these groups whatever you like.

Related

Symfony, How to use DenyAccess with Doctrine object?

I want to control the same user access on some methods in my controller.
Currently, I use this :
$this->denyAccessUnlessGranted('ACCESS', $this->Player($toolRepository));
However I am forced to use this line and inject the ToolRepository into each method ...
What would be the easiest way to do it?
I saw that there was the IsGranted annotation but my subject needs to be a doctrine object to control access with my Vote.
/**
* #Route("/player")
*/
class PlayerController extends AbstractController
{
/**
* #Route("/", name="player")
* #throws Exception
*/
public function Player(ToolRepository $toolRepository): \App\Entity\Tool
{
$playerTool = 'TestTool2';
$tool = $toolRepository->findOneBy(array('libelle' => $playerTool));
if (!$tool) {
throw new Exception('Tool : ' . $playerTool . 'not found!');
}
return $tool;
}
/**
* #Route("/main", name="player")
* #IsGranted ("ACCESS", subject="tool")
* #throws Exception
*/
public function mainPlayer(PlayerRepository $playerRepository, ToolRepository $toolRepository): Response
{
$this->denyAccessUnlessGranted('ACCESS', $this->Player($toolRepository));
$players = $playerRepository->findAll();
return $this->render('player/player_mainpage.html.twig', ['players'=>$players]);
}
}
I think this ressource should answer you: Symfony voters.
You'll put your security logic in your custom voter which will be called in every function of your controller (or every methods where you want to control access) isGranted() function.
Calling your Player() function is a easier way to do this for beginner, you can keep like that but you shouldn't put it in Controller and use a Service instead .
Edit:
You may store your ToolRepository as Controller private property and inject it in a __construct() method so you don't have to inject ToolRepository in each method

How to get grid behavior columns dynamically?

I'm making a plugin system and I'm dynamically adding behaviors with relation to Order model. For example:
class OrderBehavior extends Behavior
{
public function getOrderTrackNumber()
{
return $this->owner->hasOne(TrackNumber::class, ['order_id' => 'id']);
}
}
At runtime I don't know which plugins (and therefore which behaviors) are activated.
How can I get all relation properties (in example orderTrackNumber) dynamically for render in GridView columns?
You may use getBehaviors() to get all active behaviors attached to model. At this point you should probably implement some interface for behaviors which may add new relations, so they will be able to provide list of defined relations - it may save you performance hell (browsing all behavior's methods and searching relations definitions may be slow and unreliable). For example:
interface BehaviorWithRelationsInterface {
/**
* #return string[]
*/
public function getRelationsNames(): array;
}
Then in model:
/**
* #return string[]
*/
public function getAllRelationsNames(): array {
$relations = [];
foreach ($this->getBehaviors() as $behavior) {
if ($behavior instanceof BehaviorWithRelationsInterface) {
$relations = array_merge($relations, $behavior->getRelationsNames());
}
}
// add relations defined directly in model
$relations[] = 'user';
return $relations;
}
If you don't miss anything, getAllRelationsNames() should return names of all relations defined in model.
there's no built in method in yii2 that returns all relations of a model,
you can either check your attached behaviours via behaviours() method,
or, if you need a complete list, check via Reflection whether your getters and attached behaviors return an object that implements relation interface.

Yii2 field accessed only via magic method

/**
* This is the model class for table "hashtag".
*
* #property string $text
*
* #property TweetHashtag[] $tweetHashtags
* #property Tweet[] $tweets
*/
class Hashtag extends ActiveRecord
{
.........
public function getTweetHashtags()
{
return $this->hasMany(TweetHashtag::className(), ['hashtag_text' => 'text']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getTweets()
{
return $this->hasMany(Tweet::className(), ['id' => 'tweet_id'])->viaTable('tweet_hashtag', ['hashtag_text' => 'text']);
}
}
When I do in some component
$hashtags = Hashtag::find()
->with('tweets')
->where(['text' => $hashtagText])
->all();
foreach($hashtags as $hashtag)
{
print_r($hashtag->tweets);
}
It`s working but why tweets - field accessed only via magic method and how can i fix it? And tweetHashtags working well.
Class Tweet have same relationship but public function getHashtags() working without this problem.
Your question is not clear. Each method on a Component class that start with get (like getName) can be accessed with property form (e.g. name). On special case, relations of Yii's ActiveRecord, if you access to relation by property form, you get results. In fact $this->tweets is a shorthand for $this->getTweets()->all().
P.S: On Yii2 Document http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#accessing-relational-data:
Note: While this concept looks similar to the object property feature,
there is an important difference. For normal object properties the
property value is of the same type as the defining getter method. A
relation method however returns an yii\db\ActiveQuery instance, while
accessing a relation property will either return a yii\db\ActiveRecord
instance or an array of these.
$customer->orders; // is an array of `Order` objects
$customer->getOrders(); // returns an ActiveQuery instance
This is useful for creating customized queries, which is described in the next section.

Symfony2 custom json serialization

I am wondering how I can personify the default JSON serialization with JMS on entities.
I have an object A that have a OneToMany relation with an object B.
I expose the object B and attrA from A object.
But instead of having the basic JSON
{'attrA' : 'foo', 'Bs' : {{'attrB' : 'bar'}, {'attrB' : 'baz'}, ...} }
I want to have
{'attrA' : 'foo', 'Bs' : {'bar', 'baz', ...}}
Is it possible to do that?
A way to do this, is to play with serializer's annotations
I assume your value objects looks like this
class Foo
{
/**
* #var string
*/
private $attrA;
/**
* #var ArrayCollection<Bar>
*/
private $bs;
}
class Bar
{
/**
* #var string
*/
private $attrB;
}
Import annotations
First, if you didn't already, you need to import the annotations at the top of your files
use JMS\Serializer\Annotation as Serializer;
Set up a custom getter
By default, JMS serializer gets the property value through reflection. My thoughts go on creating a custom accessor for the serialization.
Note: I assume your bs property is an ArrayCollection. If not, you can use array_map
// Foo.php
/**
* #Serializer\Accessor(getter="getBarArray")
*/
private $bs;
/**
* #return ArrayCollection<string>
*/
public function getBarArray()
{
return $this->getBs()->map(function (Bar $bar) {
return $bar->getAttrB();
});
}
Setting up a custom Accessor(getter) will force the serializer to use the getter instead of the reflection. This way, you can tweak any property value to get it in the format you want.
Result
Serialization of these value objects would result in
{'attrA' : 'foo', 'Bs' : ['bar', 'baz', ...]}

PhpStorm cannot autocomplete model attributes

simply I want PhpStorm autocomplete my model's attributes when I use find(), findAll(), findByAttributes() etc...
I have a model like:
/**
* member model parameters:
* #property integer $id
* #property integer $city_id
* #property string $e_mail
*/
class Member extends CActiveRecord
{
/**
* #static
* #param string $className
* #return Member
*/
public static function model($className = __CLASS__)
{
return parent::model($className);
}
...
When I use active record methods like:
$member = Member::model()->findByAttributes(array('e_mail'=>'Foo Bar'));
and try to autocomplete when I wrote this:
$member->
It only gives me CActiveRecord's parameters and methods in the list.
I tried to change
/**
* Finds a single active record that has the specified attribute values.
* See {#link find()} for detailed explanation about $condition and $params.
* #param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
* An attribute value can be an array which will be used to generate an IN condition.
* #param mixed $condition query condition or criteria.
* #param array $params parameters to be bound to an SQL statement.
* #return CActiveRecord the record found. Null if none is found.
*/
public function findByAttributes($attributes,$condition='',$params=array())
{...
this method's return param from CActiveRecord to Member, self, parent, $this, child etc...
Autocomplete only worked when it was "Member". But this method is used for all models not just the Member model so this is not a solution.
If anyone knows the solution (preferably without changing the framework core methods) I will be glad.
NOTE: All of my awesome Yii code is freely available on bitbucket in the repositories here and here. If you hate Yii's verbosity, check out my Pii class. I think you'll totally dig it.
I've run into this and what I do is augment the phpdoc for the static model() method. This corrects the issue and the autocomplete works fine.
For instance:
class MyModel extends CActiveRecord {
/**
* #static
* #param string $className
* #return MyModel|CActiveRecord
*/
public static function model($className=__CLASS__) {
.... yada yada yada ...
}
}
Note the pipe and additional class added to the "#return". This tells PhpStorm to also include that class in the auto-complete lookups.
One additional note, if you're using namespaces, you may need a slash in front of some class names. Just depends on your project and includes.
=============== UPDATE: 2013-08-05 ===============
With PhpStorm v6 and up, it looks like you can use:
*
* #return $this
*
And also get proper auto-completion.
On a side note, the whole static "model()" method thing is antiquated (like Yii) and I have a base model class that I use now for all projects. It contains the static model method; which is then no longer required in each of my subclasses. Here's an example...
<?php
namespace My\Awesome\Name\Space;
/**
* MyBaseModel
* etc.etc.
*/
class MyBaseModel extends \CActiveRecord
{
/**
* Returns the static model of the specified AR class.
*
* #param string $className
*
* #return $this
*/
public static function model( $className = null )
{
return parent::model( $className ? : \get_called_class() );
}
//code code code code
}
/**
* MySubModel
*/
class MySubModel extends MyBaseModel
{
/**
* {#InheritDoc}
*/
public function tableName()
{
return 'my_sub_table';
}
}
$_models = MySubModel::model()->findAll( 'xyz = :xyz', array( ':xyz' => 'abc' ) );
if ( !empty( $_models ) )
{
foreach ( $_models as $_model )
{
// Do awesome stuff...
}
}
Autocomplete works fine for all the subclasses...
Just thought I'd update this and let y'all know.
You can use phpdoc #method. You can use this approach for frequently-used models or you can create new template for code generator.
/**
* #method Member findByPk($pk,$condition='',$params=array())
*/
class Member extends CActiveRecord
{