I need some help with SF 2.7 serializer
I have made an API with get Json Data like this :
{
"dateDebut":"2017-02-16",
"dateFin":"2018-02-16",
"caMoisTotalHorsSessions":"5.2",
"caMoisClients":"5.3",
"caMoisGarantie":"5.4",
"caMoisHuile":"5.5" }
I tried many way in order to deserialze into my object Class where dateDebut and dateFin are attending to be Datetime object and not string
try {
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$callback = function ($date) {
return new \DateTime($date);
};
$normalizer->setCallbacks(array(
'dateDebut' => $callback,
'dateFin' => $callback, ));
$serializer = new Serializer(array($normalizer), array($encoder));
$entity = $serializer->deserialize($request->getContent(), $class, $format);
} catch (RuntimeException $e) {
return new JsonResponse(
['code' => Response::HTTP_BAD_REQUEST, 'message' => $this->trans('api.message.data_error')],
Response::HTTP_BAD_REQUEST);
}
But callbacks are never used :/ Could anyone help me please ?
Aim is to transform date string into Datetime object automatically before flush the object in database.
Thanks a lot
What you are trying to do is denormalization. The normalizer callbacks are for normalization. I think it's pretty confusing. It's strange that they would offer setting callback for just one direction.
I tested some code doing what I think you want to do. You need a custom normalizer class. The class is not so complicated, it can extend from the GetSetNormalizer or the ObjectNormalizer. You just want to create the \DateTime inside here, and you might add some validation for the date time.
class BoardNormalizer extends GetSetMethodNormalizer
{
public function denormalize($data, $class, $format = null, array $context = array())
{
if (isset($data['created'])) {
$data['created'] = new \DateTime($data['created']);
}
return parent::denormalize($data, $class, $format, $context);
}
}
I tested it with this code:
$json = json_encode([
'created' => '2017-02-20T05:49:51-0500'
]);
$encoder = new JsonEncoder();
$normalizer = new MyCustomNormalizer();
$serializer = new Serializer([$normalizer], [$encoder]);
$entity = $serializer->deserialize($json, MyCustomClass::class, 'json');
And it produced my custom class where the created property was a \DateTime object.
Aim is to transform date string into Datetime object automatically before flush the object in database.
Something like this? Using setters/getters ? I'm using the following code in entity
private $created;
public function setCreated($created)
{
if (!($created instanceof \DateTime)) {
$created = date_create($created);
}
$this->created = $created;
return $this;
}
Related
Using symfony/serializer version 4. The data I am getting back JSON from an API that looks like this
{
"name": "Steves Book Shop",
"book_1": "Lord of the Rings",
"book_2": "The Hobbit",
"book_[n]": "there can be any number of books"
}
I want to deserialize into the following models
class BookShop
{
protected $name;
/** #var Book[] */
protected $books;
public function getBooks(): array
{
return $this->books;
}
public function setBooks(array $books)
{
$this->books = $books;
}
public function addBook(Book $book)
{
$this->books[] = $book;
}
// ... other code removed to save space
}
class Book
{
protected $title;
// ... other code removed to save space
}
When using "cleaner" JSON below, everything works as expected and I get a BookShop with and array of Books returned.
{
"name": "Steves Book Shop",
"books": [
{ "title": "Lord of the Rings" },
{ "title": "The Hobbit" }
]
}
What would be a clean way to denormalize the original JSON which instead has the annoying book_1, book_2 etc.
I have been experimenting with a custom denormalizer (DenormalizerInterface) and my solution is looking much more difficult than you would expect.
Here is the solution I ended up with, I am not completely convinced this is the best way, but its working for the moment. Any feedback welcome:
class StrangeDataDenormalizer extends Symfony\Component\Serializer\Normalizer\ObjectNormalizer
{
protected function isStrangeDataInterface(string $type): bool
{
try {
$reflection = new \ReflectionClass($type);
return $reflection->implementsInterface(StrangeDataInterface::class);
} catch (\ReflectionException $e) { // $type is not always a valid class name, might have extract junk like `[]`
return false;
}
}
public function denormalize($data, $class, $format = null, array $context = array())
{
$normalizedData = $this->prepareForDenormalization($data);
$normalizedData = $class::prepareStrangeData($normalizedData);
return parent::denormalize($normalizedData, $class, $format, $context);
}
public function supportsDenormalization($data, $type, $format = null)
{
return $this->isStrangeDataInterface($type);
}
}
interface StrangeDataInterface
{
public static function prepareStrangeData($data): array;
}
class BookShop implements StrangeDataInterface
{
public static function prepareStrangeData($data): array
{
$preparedData = [];
foreach ($data as $key => $value) {
if (preg_match('~^book_[0-9]+$~', $key)) {
$preparedData['books'][] = ['title' => $value];
} else {
$preparedData[$key] = $value;
}
}
return $preparedData;
}
// .... other code hidden
}
function makeSerializer(): Symfony\Component\Serializer\Serializer
{
$extractor = new ReflectionExtractor();
$nameConverter = new CamelCaseToSnakeCaseNameConverter();
$arrayDenormalizer = new ArrayDenormalizer(); // seems to help respect the 'adder' typehints in the model. eg `addEmployee(Employee $employee)`
$strangeDataDenormalizer = new StrangeDataDenormalizer(
null,
$nameConverter,
null,
$extractor
);
$objectNormalizer = new ObjectNormalizer(
null,
$nameConverter,
null,
$extractor
);
$encoder = new JsonEncoder();
$serializer = new Symfony\Component\Serializer\Serializer(
[
$strangeDataDenormalizer,
$objectNormalizer,
$arrayDenormalizer,
],
[$encoder]
);
return $serializer;
}
You should use ArrayCollection for those books - assuming that those are just another entity on your application
I'm new to yii2. I want to write afterSave() method after insert new record. My scenario is: when the user add a damage must be send email to him.
When user add damage, does not enter the serial number. I should read his name and serial damage but I don't know how to passing serial from Controller to model.
Damage Model:
public function getUser()
{
return $this->hasOne(User::className(), ['id' => 'user_id']);
}
public function afterSave($insert, $changedAttributes)
{
parent::afterSave($insert, $changedAttributes);
$name = $this->user->name;
$serial = ?
$to = $this->user->email;
$subject = 'new damage';
$body = 'mr'.$name.'your damage with serial number'.$serial.'is registered';
if ($insert) {
App::sendMail($to, $subject, $body);
}
}
DamageController:
public function actionAddDamage($serial){
$model = new Damage();
$gaurantee = Gaurantee::find()->where(['serial' => $serial])->andWhere(['user_id' => Yii::$app->user->id])->one();
if (!$gaurantee)
throw new ForbiddenHttpException('You don't access');
$model->gr_id = $gaurantee->id;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('addDamage',
['model' => $model,
'servers' => $servers,
'gaurantee' => $gaurantee,
]);
}
public function init(){
$this->on(self::EVENT_AFTER_INSERT, [$this, 'sendMail']);
}
public function sendMail($event) {
}
First that comes to mind is to add attribute in damage model.
//Damage model
public $serial;
Then in your controller you can set value for this:
public function actionAddDamage($serial){
$model = new Damage();
$gaurantee = Gaurantee::find()->where(['serial' => $serial])->andWhere(['user_id' => Yii::$app->user->id])->one();
if (!$gaurantee)
throw new ForbiddenHttpException("you don't access");
$model->serial = $serial;
.....
In your afterSave() method you will have your serial as $this->serial.
2nd way is to get it from guaranty table since you have $this->gr_id , but this will generate 1 more db request.
You can create a property $serial in your Damage Model and assign the serial value to the $model->serial property in your Controller. For Example:
class Damage extends yii\db\ActiveRecord
{
public $serial = null;
public function afterSave($insert, $changeAttributes) {
// ...
var_dump($this->serial);
}
}
And in the controller you can assign $serial to the model:
public function actionAddDamage($serial) {
$model = new Damage();
$model->serial = $serial;
// ...
}
I am using Doctrine 2 and Zend framework since a few days.
I am generating my entities across yaml files.
Now I met an issue to convert my entities Doctrine into Json format (in order to use it through AJAX).
Here is the code used :
$doctrineobject = $this->entityManager->getRepository('\Entity\MasterProduct')->find($this->_request->id);
$serializer = new \Symfony\Component\Serializer\Serializer(array(new Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer()), array('json' => new Symfony\Component\Serializer\Encoder\JsonEncoder()));
$reports = $serializer->serialize($doctrineobject, 'json');
below is the return I get :
Fatal error: Maximum function nesting level of '100' reached, aborting! in /Users/Sites/library/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php on line 185
the issue seems to be the same than here :
http://comments.gmane.org/gmane.comp.php.symfony.symfony2/2659
but there is not proper solution proposed.
Any idea how can I do it ?
Cheers
I solved the same problem by writing my own GetSetNormalizer my class. Defined static variable in a class for branching
class LimitedRecursiveGetSetMethodNormalizer extends GetSetMethodNormalizer
{
public static $limit=2;
/**
* {#inheritdoc}
*/
public function normalize($object, $format = null)
{
$reflectionObject = new \ReflectionObject($object);
$reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC);
$attributes = array();
foreach ($reflectionMethods as $method) {
if ($this->isGetMethod($method)) {
$attributeName = strtolower(substr($method->name, 3));
$attributeValue = $method->invoke($object);
if (null !== $attributeValue && !is_scalar($attributeValue) && LimitedRecursiveGetSetMethodNormalizer::$limit>0) {
LimitedRecursiveGetSetMethodNormalizer::$limit--;
$attributeValue = $this->serializer->normalize($attributeValue, $format);
LimitedRecursiveGetSetMethodNormalizer::$limit++;
}
$attributes[$attributeName] = $attributeValue;
}
}
return $attributes;
}
/**
* Checks if a method's name is get.* and can be called without parameters.
*
* #param ReflectionMethod $method the method to check
* #return Boolean whether the method is a getter.
*/
private function isGetMethod(\ReflectionMethod $method)
{
return (
0 === strpos($method->name, 'get') &&
3 < strlen($method->name) &&
0 === $method->getNumberOfRequiredParameters()
);
}
}
And usage
LimitedRecursiveGetSetMethodNormalizer::$limit=3;
$serializer = new Serializer(array(new LimitedRecursiveGetSetMethodNormalizer()), array('json' => new
JsonEncoder()));
$response =new Response($serializer->serialize($YOUR_OBJECT,'json'));
JMSSerializerBundle seems to handle circular references fine.
what is the professional way insert record in database.
i am using laravel 5.2.
i'm new in laravel.
class students extends Controller
{
public function index()
{
$insertData = array(
"name" => Input::get("name"),
"detail" => Input::get("detail"),
"token_key" => Input::get("_token")
);
return view('student');
}
public function fees()
{
$record = array(
"p_name" => Input::get("name"),
"p_fees" => Input::get("fees"),
"p_detail" => Input::get("detail")
);
return view('fee');
}
}
stander able way?
You should use mass assignment. Fill $fillable array inside your model and use this:
Model::create($insertData);
public function store_student(Request $request)
{
$student = new Student;
$student->name = $request->name;
$student->detail = $request->details
$student->save();
return view('student');
}
public function store_fee(Request $request)
{
$fee = new Fee;
$fee->p_name = $request->name;
$fee->p_fee = $request->fees;
$fee->p_detail = $request->details
$fee->save();
return view('fee');
}
I suggest you to read this from Laravel official guide.
However you can do it like this:
DB::table('tablename')->insert($insertData);
I have a lumen application where I need to store incoming JSON Request. If I write a code like this:
public function store(Request $request)
{
if ($request->isJson())
{
$data = $request->all();
$transaction = new Transaction();
if (array_key_exists('amount', $data))
$transaction->amount = $data['amount'];
if (array_key_exists('typology', $data))
$transaction->typology = $data['typology'];
$result = $transaction->isValid();
if($result === TRUE )
{
$transaction->save();
return $this->response->created();
}
return $this->response->errorBadRequest($result);
}
return $this->response->errorBadRequest();
}
It works perfectly. But use Request in that mode is boring because I have to check every input field to insert them to my model. Is there a fast way to send request to model?
You can do mass assignment to Eloquent models, but you need to first set the fields on your model that you want to allow to be mass assignable. In your model, set your $fillable array:
class Transaction extends Model {
protected $fillable = ['amount', 'typology'];
}
This will allow the amount and typology to be mass assignable. What this means is that you can assign them through the methods that accept arrays (such as the constructor, or the fill() method).
An example using the constructor:
$data = $request->all();
$transaction = new Transaction($data);
$result = $transaction->isValid();
An example using fill():
$data = $request->all();
$transaction = new Transaction();
$transaction->fill($data);
$result = $transaction->isValid();
You can either use fill method or the constructor. First you must include all mass assignable properties in fillable property of your model
Method 1 (Use constructor)
$transaction = new Transaction($request->all());
Method 2 (Use fill method)
$transaction = new Transaction();
$transaction->fill($request->all());
Create your TransactionRequest with rules extends FormRequest
public function store(TransactionRequest $request)
{
$transaction = new Transaction($request->validated());
$transaction->save();
}