I want to develop an application that will use data coming from remote API calls. I'd like the data to be saved in a local database so that I do not use up the API's quotes and for easier subsequent retrieval.
I've already set up entity mappings. However, I'm not sure how I should approach the task of mapping the data coming from remote calls (I'm planning on using Guzzle HTTP client) on the entities and saving them in the database.
With input coming from users, I'd set up Type classes and use Symfony's Form Component.
In this case, however, my application will be sending HTTP requests and receiving data that should be mapped and saved.
Should I perhaps first collect the data I need in DataFixtures, and then populate my entites from those fixtures?
Another method I thought about was using the FormComponent with the omission of handleRequest.
I will also add that I'd like to be able to easily update my local data with the remote data as the remote will be updated on regular basis.
I guess I need a conceptual hint on how to approach this task.
Using Form component is a viable solution, additional bonus apart from mapping data to entities is validation. So, you could validate if data is correct before persisting your entities. You can use submit method directly:
public function newAction(Request $request)
{
$form = $this->createFormBuilder()
// ...
->getForm();
$form->submit(json_decode($yourData, true));
if ($form->isValid()) {
// perform some action...
return $this->redirectToRoute('task_success');
}
}
Another options is to use serializer's, deserialize method:
$nameConverter = new OrgPrefixNameConverter();
$normalizer = new ObjectNormalizer(null, $nameConverter);
$serializer = new Serializer(array($normalizer), array(new JsonEncoder()));
$obj = new Company();
$obj->name = 'Acme Inc.';
$obj->address = '123 Main Street, Big City';
$json = $serializer->serialize($obj);
// {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"}
$objCopy = $serializer->deserialize($json);
// Same data as $obj
Links:
serializer
form handling
Related
We're developing an API on top of Symfony 5 using Doctrine ODM and MongoDB as well as API Platform. Problem is trying to support PUT operations.
What we find is that if we PUT a JSON document with some fields not included, the object (eg $data) seems to have those fields filled in from the original document in the database.
In some cases, this means that the document passed in via the PUT request will pass validations such as NotBlank() when the document coming n over the wire is missing a required field, but the $data object being validated has that field filled in from the database prior to validation.
As an example, if the original JSON document in Mongo is
{
projectID: "ABCD",
projectName: "My Project",
cost: 100
}
And assuming projectName is a required field and we PUT the following document to our endpoint /v1/example-endpoint/ABCD
{
cost: 100
}
then in out custom data persister we have the following:
public function persist($data, array $context = [])
{
...do something here...
}
The $data object passed in to the persister will include the previous projectName "My Project" from the database.
Is this expected behavior and/or is there a way around this to validate the data sent over the wire as-is?
FWIW, we tried created a new Object of the class and setting the data from the PUT content, then creating a validator and validating the new object, but ran in to an issue where custom validation methods that had constructors were not being autowired when calling the validator manually. If we could successfully validate the new object that would work too.
$putData = json_decode($this->requestStack->getCurrentRequest()->getContent(), true);
$newProject = (new Project());
foreach ($putData as $property => $value) {
$newProject->setProperty($property, $value);
}
$validator = Validation::createValidatorBuilder()
->addMethodMapping('loadValidatorMetadata')
->getValidator();
$errors = $validator->validate($newProject);
I am evaluating the CQRS pattern and wonder what would be the best way to obtain an Entity created by a command in the same action so I can render it in the view.
The two options I can think of are.
1) Create an id in the controller and send it with the command to fetch the entity by finding it by id.
2) Create an instance of the entity and send it with the command so I have a reference to it after it's populated
Example code
public function createEntityAction(array $data) {
$eventDispatcher = $this->get('event_dispatcher');
$eventDispatcher->dispatch(
CreateEntityHandler::name, // Handler
new Entity($data) // Command
);
// Placeholder //
$entity = get-the-created-entity
// //
return $this->view($entity, Response::HTTP_OK);
}
Second option is not really an option. "Entity creation", which is in fact is a business operation, is a command handling.
Generally speaking, the one who sends a command, whose handler creates an entity, should send the entity id with it. In what way the identity is generated is just an implementation concern.
Usually, command handlers either do what they suppose to do and return nothing (or ACK) or throw (or NAK).
I see that there are a number of ways to load fixture data into a database. But after a functional test, what is the best/standard way to confirm what was written to the database was correct?
The phpunit package has a whole section for this, where you can load a dataset and then use things like assertTablesEqual() to compare a table's contents with the expected contents. But that doesn't seem to be usable under Symfony2, and I can't find any other standard method.
How do others solve this problem?
Symfony2 use doctrine ORM by default, or you can set other database gestion (MongoDB by exemple). Check the app\config\parameters.php file to set the database connection and the app\config\config.php to check/set the type of gestion. With a ORM, you do not need to check alot of stuff as the phpunit package, because it is already integrated into the protocole and much more. Check here for more details.
If you want to load datafixtures, you can export your actual database to save it, or either create a new one only for testing and switch databases in the app\config\parameters.php by create a new one like this app\config\parameters_dev.php. In this case, the website and your local version won't use the same database. You can also edit the app\config\parameters.php and prevent to upload it with the .gitgnore file.
Here is an example from a test set that includes database results. If you need to interact directly with the database in your test the entity manager can be made available to the test. For more information, see this bit of documentation. Note that results are more usually presented in a web page and read by the DOM crawler.
public function setUp()
{
self::bootKernel();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager()
;
$this->tool = static::$kernel->getContainer()
->get('truckee.toolbox')
;
$classes = array(
'Truckee\VolunteerBundle\DataFixtures\SampleData\LoadFocusSkillData',
'Truckee\VolunteerBundle\DataFixtures\SampleData\LoadMinimumData',
'Truckee\VolunteerBundle\DataFixtures\SampleData\LoadStaffUserGlenshire',
'Truckee\VolunteerBundle\DataFixtures\SampleData\LoadStaffUserMelanzane',
'Truckee\VolunteerBundle\DataFixtures\SampleData\LoadOpportunity',
'Truckee\VolunteerBundle\DataFixtures\SampleData\LoadVolunteer',
);
$this->loadFixtures($classes);
$this->client = $this->createClient();
$this->client->followRedirects();
}
public function testOutboxUser()
{
$crawler = $this->login('admin');
$link = $crawler->selectLink("Send alerts to organizations")->link();
$crawler = $this->client->click($link);
$outboxObj = $this->em->getRepository('TruckeeVolunteerBundle:AdminOutbox')->findAll();
$outbox = $outboxObj[0];
$recipient = $outbox->getRecipientId();
$type = $this->tool->getTypeFromId($recipient);
$this->assertEquals('staff', $type);
}
I am building a mobile app (iOS) and Symfony2 REST backend. On Symfony2, my routes are working correctly and I have tested them with AJAX and httpie, all CRUD operations, etc are fine. Now, I am trying to access the routes from the app. So far, I can access the routes and when I look into the Symfony2 Profiler, I can see entries in last 10 entries to verify that I am hitting the server with my POST and GET requests. Now, I have 2 questions and I would be glad if people can point me in the direction for ** Best Practices ** on how to proceed.
Problem 1: Although I am posting data which I can see coming in under "Request", when I try to create a record, it creates only NULL records, meaning the data is being lost. This is my controller for creating users for example:
public function postUserAction(Request $request)
{
$content = $this->get('request')->getContent();
$serializer = $this->get('jms_serializer');
$entity = $serializer->deserialize($content, 'Name\BundleName\Entity\User', 'json');
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return array(
'entity' => $entity,
);
}
When I look into the log, the only things that stand out are: Request Cookies (No cookies), Request Content: "Request content not available (it was retrieved as a resource)." This tells me the data was missing, how can I get this data and use it? Or what else could it be?
Problem 2: GET returns an empty JSON response with no data just the keys when I NSlog (echo it). My code looks like:
public function getUsersAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('NameBundle:User')->findAll();
return array(
'entities' => $entities,
);
}
From the log, it has the Request Cookies set: PHPSESSID => "1udududjjs83883jdlb4ho0j4" but again the Request Content says: "Request content not available (it was retrieved as a resource)." How can I make it return the data with the JSON? This works well in the browser AJAX and httpie tests.
Problem 3: Using AFNetworking, I have a symbolic constant which I set as the APIHost (IP Address) and APIPath was the folder. Now in my earlier version using native PHP, I constructed the actual code to be executed in index.php by sending the parameter in JSON so if I wanted a login, I sent something like todo:login but with Symfony2, I am not sure or know even the best practices for this case. Ideally, I would like to specify the server-side request in the JSON request and then find the correct route in Symfony2 but is this how to do it and if yes, can you please provide an example? The workaround is to specify hard coded paths in AFNetworking each time I need to make a request which I think tightly couples the code and I need to make changes in a lot of places anytime something changes on the server side. Thanks and sorry for the long question!
You expect the jmsserializer to do magic for you. But it won't, you have to configure it first. From you code I can see that you are using jmsserializer wrong.
In getUsersAction() you have to return a serialized response, but you are returning an array of objects. This would be the right way:
public function getUsersAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('NameBundle:User')->findAll();
$serializer = $container->get('jms_serializer');
return array(
'users' => $jsonContent = $serializer->serialize($entities, 'json');,
);
}
Your post action basically looks ok, however when the json does not contain every field of entity USER the deserialization will fail. You can configure the entity for serialization/deserialization using annotations.
http://jmsyst.com/libs/serializer/master/reference/annotations
I am not sure if I understood your last problem, but I think you have to hardcode the path in your app.
Symfony2 is great and absolutely useful when writing an API. But if you don't want to deal with serialization/deserialization you can give http://laravel.com/ a try. It is build on symfony and you can generate an api on the fly.
I’m trying to better understand what the best method would be to persist data between requests in this scenario (using Zend Framework):
Say I have an Events controller and the default (index) view displays any existing Announcements (if there are any), and a link to Add a new Announcement (Both Event and Announcement are arbitrary objects). I’m trying to retrieve the eventId so I can associate the new Announcement with it when saving it to the database. Compositionally, an Event consists of 0 to many Announcements. From my limited understanding of the Zend Framework, I see two main options.
Option one: Make the URL something like ‘/event/addAnnouncement/eventId/5’, which makes retrieving the eventId easy via route/path parameters.
Option two: In the indexAction of the controller, save the eventId to a session variable, which can then be retrieved in the addAnnouncementAction of the Event controller. This way the Add Announcement link would simply be ‘/event/addAnnouncement/’.
Can anyone shed some light on which of these two ways is better, or if there is another way I’m not aware of?
As always, any help is much appreciated. Thanks.
The question to ask yourself is, how long do you need to persist the data? If you only need to save the data to pass it to the next action you can use POST or GET, the GET would pass through the url and the POST would not(typically).
The example you presented would suggest that you need to persist the data just long enough to validate, filter and process the data. So you would likely be very satisfied passing the few pieces of data around as parameters(POST or GET). This would provide the temporary persistence you need and also provide the added benefit of the data expiring as soon as a request was made that did not pass the variables.
A quick example (assume your form passes data with the POST method):
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost()){
$data = $form->getValues();//filtered values from form
$model = new Appliction_Model_DbTable_MyTable();
$model->save($data);
//but you need to pass the users name from the form to another action
//there are many tools in ZF to do this with, this is just one example
return $this->getHelper('Redirector')->gotoSimple(
'action' => 'newaction',
array('name' => $data['name'])//passed data
);
}
}
if you need to persist data for a longer period of time then the $_SESSION may come in handy. In ZF you will typically use Zend_Session_Namespace() to manipulate session data.
It's easy to use Zend_Session_Namespace, here is an example of how I often use it.
class IndexController extends Zend_Controller_Action {
protected $_session;
public function init() {
//assign the session to the property and give the namespace a name.
$this->_session = new Zend_Session_Namespace('User');
}
public function indexAction() {
//using the previous example
$form = new Application_Form_MyForm();
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost()){
$data = $form->getValues();//filtered values from form
//this time we'll add the data to the session
$this->_session->userName = $data['user'];//assign a string to the session
//we can also assign all of the form data to one session variable as an array or object
$this->_session->formData = $data;
return $this->getHelper('Redirector')->gotoSimple('action'=>'next');
}
}
$this->view->form = $form;
}
public function nextAction() {
//retrieve session variables and assign them to the view for demonstration
$this->view->userData = $this->_session->formData;//an array of values from previous actions form
$this->view->userName = $this->_session->userName;//a string value
}
}
}
any data you need to persist in your application can sent to any action, controller or module. Just remember that if you resubmit that form the information saved to those particular session variables will be over written.
There is one more option in ZF that kind of falls between passing parameters around and storing data in sessions, Zend_Registry. It's use is very similar to Zend_Session_Namespace and is often used to save configuration data in the bootstrap (but can store almost anything you need to store) and is also used by a number of internal Zend classes most notably the flashmessenger action helper.
//Bootstrap.php
protected function _initRegistry() {
//make application.ini configuration available in registry
$config = new Zend_Config($this->getOptions());
//set data in registry
Zend_Registry::set('config', $config);
}
protected function _initView() {
//Initialize view
$view = new Zend_View();
//get data from registry
$view->doctype(Zend_Registry::get('config')->resources->view->doctype);
//...truncated...
//Return it, so that it can be stored by the bootstrap
return $view;
}
I hope this helps. Pleas check out these links if you have more questions:
The ZF Request Object
Zend_Session_Namespace
Zend_Registry
Option 1 is better, although in your example this is not a POST (but it could be done with a POST).
The problems with option 2 are:
If a user had multiple windows or tabs open at the same time, relating to different events, how would you track which event ID should be used?
If a user bookmarked the add event page and came back later, the session var may not be set
Option 2 is also a little more complicated to implement, and adds a reliance on sessions.