I'm trying to implement in my symfony project some api. Currently the project have many controller with standard crud, based on html table, form/validator etc.
I'm looking to the api-platform project that seam to make very easy the construction of standard rest api, and for the GET part it fit my necessities.
But for the POST/PUT/DELETE part it seam a very basic persist action on an entity, and suddenly in my project, i need to do many more actions after the persist of the entity.
I've red the docs and I'm really confused on how to do that...
I see two possibilities:
Using the event system, subscribing for the POST_WRITE for every entities
Creating a custom action for every create/update/delete actions of an entity
In both the case, I would have a really high number of single actions or event subscriber in the project (30/40), and it's really unconfortable to maintain. Also I probably have to replicate the same code that I already have in the controller, to maintain the old form system until is all rewitten in an API format.
Any suggestion on how to approach this problem?
There isn't a way to use the same controller actions, like in the FOSRestBundle, so that I can receive the data, do the various validation/persist/extra actions, and then return a result that is managed by the api-platform events?
Any way to manually call some part of the api-platform, like the deserialization/serialization, the filter and pagination from a standard controller action?
Thanks to all
Cheers
Daniele
Forgive me if I do not completely understand your question but if you already have the functionality written in the controller and you want to access the same actions via an api then maybe you can set multiple routes on each action and depending on how the action was called you can respond differently. For example:
/**
* #Route("/api/v1/tester", name="api_tester")
* #Route("/tester", name="tester")
*/
public function testerAction( Request $request )
{
$route = $request->attributes->get('_route');
if( $route == "api_tester" )
#..do things the api way
response = array( "success" => 1, "data" => $return_string );
return new Response( json_encode( $response ) );
} else { //non-api
$this->render('tester/basic.html.twig', array();
}
}
You can evaluate which route was used and in various sections of your actions you can handle things differently based on if the action was called via the api or from a normal request.
Related
If I'm receiving JSON data in a request (say an API type interface), what's the Symfony recommended way of populating an entity. It seems to me the options are:
Use the form component – passing the decoded JSON as an array to the submit() method.
Use the Serializer to deserialize.
It seems to me the issue with using the serializer is that you need to manually do data transformation (and validation, though simple).
Using the form component feels kind of hacky and a also uses a lot of functionality that isn't touched/needed.
Are there other options (built into Symfony or other bundles/packages)? Or is one of these the recommended way?
(I realize this is at least partially an opinion based question...)
As you've mentioned - this is a pretty opinionated issue to deal with. The options you've been considering are the two common ways of handling it:
Just go with the Form component - need to create a FormType, adds some performance overhead (not significant in most cases). As a bonus - it gives you all the Form perks like not allowing extra fields, ability to use Form events etc.
Use Serializer + Validator - a "skinny" option in terms of components employed, a bit more verbose, doesn't come with Form perks
I'd say that there is really nothing wrong with using forms to handle deserialization and validation in one go.
Have a look at the sample action code below. Note that it is using the FOSRestBundle View class to handle responses. It simply takes in a json encoded entity data and either creates a new entity based on it or updates an existing one.
public function sampleAction(SampleEntity $sampleEntity, Request $request) {
//Is it a new or existing entity?
$statusCode = $sampleEntity->getId() ? 200 : 201;
//Load our form with the entity provided by the route loader
$form = $this->createForm(SampleEntityType::class, $sampleEntity);
//Decode the actual input and make Form component to populate an entity for us
$formData = json_decode($request->getContent(), true);
$form->submit($formData);
//Validation is as simple as this
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($sampleEntity);
$em->flush();
return View::create($form, $statusCode);
}
return View::create($form->getErrors(true, false), 400);
}
I'm in searching of the best way of removing business logic from controller and correct usage of model(and maybe services).
Some details below.
Actually, my project is more complicated, but as example I will use Simple Blog application.
I have created my application (Simple Blog) in next steps:
created bundle
generated entities(Topic, Post, Comment)
generated controller for each entity, using doctrine:generate:crud
installed FOSUserBundle and generated User entity
So, I have all needed methods and forms in my controllers. But now I have some troubles:
Admin need to be able see all topics and posts, when simple User can only see
topic and posts where he is owner.
Currently there are indexAction, that return findAll common for any user. As solution, I can check in action, if ROLE_USER or ADMIN and return find result for each condition. But this variant keep some logic at action.
I also can generate action for each role, but what happened if roles amount will increase?
What is the best way to solve this problem with result for each role?
I need to edit some parameters before saving.
For example, I have some scheduler, where I create date in some steps, using features of DateTime.
Before saving I need to do some calculations with date.
I can do it in controller using service or simple $request->params edit.
What is the best way to edit some $request parameters before saving?
My questions I have marked with bold.
Thanks a lot for any help!
What I would do is to create a query which fetches the topics. Afterwards I would have a method argument which specifies if the query should select only the topics for a certain user or all topics. Something like this should do the work in your TopicRepository:
public function findTopics($userId = false)
{
$query = $this->createQueryBuilder('topic');
if($userId) {
$query->join('topic.user', 'user')
->where('user.id = :user_id')
->setParameter(':user_id', $userId)
;
}
return $query->getQuery()->getResult();
}
So, whenever you need to get the topics only by a user, you would pass a $userId to the method and it would return the results only for that user. In your controller you'd have something similar to this code (Symfony 2.6+):
$authorizationChecker = $this->get('security.authorization_checker');
if($authorizationChecker->isGranted('ROLE_ADMIN')){
$results = $this->get('doctrine.orm.entity_manager')->getRepository('TopicRepository')->findTopics();
} else {
$results = $this->get('doctrine.orm.entity_manager')->getRepository('TopicRepository')->findTopics($this->getUser()->getId());
}
You can try using Doctrine Events and create a PreUpdate depending on your case. See the documentation for more information. If you have a TopicFormType, you could also try the form events.
You are not supposed to "edit" a $request, which is why you can't directly do that. You can, however, retrieve a value, save it as a $variable and then do whatever you want with it. You can always create a new Request if you really need it. Could you be more specific what you want to do here and why is this necessary?
Can anyone give me direction on how to accomplish cross controller variable exchange and/or function calls?
I'm new to Symfony and I have a reasonably complex practice sample site which has two controllers - PageController and BlogController.
PageController has actions to generate my home, about and contact page. The home page simply has a list of blogs.
The BlogController has all the CRUD related functions - create, delete etc
My issue is that I want to call my BlogController:createAction function from the PageController so I can present a blog create form above the blog listings on the homepage OR just pass the variable containing the new blog form data.
In addition, I need to find a solution which will allow the form to submit and the listings to refresh via AJAX.
Although #Tom Toms answer is correct, I would recommend another approach in order to minimize dependencies:
Just create a route for your Blog function and then use $this->redirectToRoute in your PageController thereby the action will be simply redirected to the assigned route, so you have no forwarding (although I think that the forwad action will create nothing more then a redirect) or service implementation with injections just a simple redirect
Either using forward method directy
$response = $this->forward('AcmeDemoBundle:Blog:myFunction', array(
'name' => $name,
));
http://symfony.com/doc/current/book/controller.html#forwarding-to-another-controller
Or alternatively, you could define the blog controller as a service.
service.yml
services:
your_service:
class: Acme\DemoBundle\Controller\BlogController
Then you can use
$service = $this->get('your_service');
$service->myFunction();
You could also look into setting up a form factory service (bit more advanced)
UPDATE:
Following your post I implemented the a service but got the following error:
Error: Call to a member function get() on a non-object
This got fixed when I added the following line to the service declaration:
calls:
- [ setContainer, [ #service_container ]]
A project I am working on contains a search feature. I would like users to have the ability to bookmark a page along with their query so they can bookmark their search page.
In the traditional implementation, I simply used GET parameters in the URL.
However, with symfony I am having a hard time understanding what is best to use for this functionality? is it just a matter of preference?
Should I just create a controller such as:
search/{query}/{page}
and when a user clicks on an option
description/{id}
or should I implement the traditional get variable in the URL:
Note the URL's on Stack Overflow, for example. When you search for something and you navigate the pages, the URL is something like:
search?q="nav&page=2 But then when you click on a post the URL becomes something like questions/19157969/expanded-navigation-by-default, so it is kind of a mix of the two.
Couldn't SO use something like search/search-term-here/2 for the same functionality? is there any benefit for picking GET over the alternative, using Symfony?
I appreciate any suggestions! many thanks in advance!
It is a matter of preference. Some say that the URL is more important than the query string for SEO purposes. Also, /search/{query}/{page} is better looking in general, but that's just my opinion.
Search terms can get very long. Perhaps your searching mechanism is fairly simple, but personally I wouldn't want to make that assumption, especially if you plan on using the same feature for something in future you haven't designed the scale of yet.
Using a typical GET string is safe enough in my opinion, the controller should know what variables it needs, and GET queries generally don't (shouldn't) have an impact on routing.
However if you want to achieve pretty search URLs which can always be revisited, you could try storing each search in a database.
An example (untested):
class SearchController extends Controller
{
/**
* #Route("/search" name="search")
* #Method("GET")
*/
public function searchAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$search = new Search();
$search->setQuery($request->query);
// The query field would be of an object type in this example,
// but you could store the data any way you want really.
// Add any additional information about the search/request to the entity.
$em->persist($search);
$em->flush();
$this->redirect($this->generateUrl('search_results', array(
'search_id' => $search->getId()
)));
}
/**
* #Route("/search/{search_id}", name="search_results")
* #Method("GET")
* #Template()
*/
public function resultsAction($search_id)
{
$em = $this->getDoctrine()->getManager();
if(! $search = $em->getRepository('AcmePostBundle:Search')) {
throw $this->createNotFoundException();
}
$query = $search->getQuery(); // This will be a ParameterBag
// Run your search filters
return array(
// Your search results
);
}
}
In this example the request is to an action with a GET string as normal (could be POST if you prefer), which saves the search data and then redirects to an action which gets the search data from the database, does its filtering and show the results.
I can't see any real performance loss in a technique like this, the database queries to insert and select the search should be relatively small, but I can see the table in which you store them getting very full very quickly.
Then again it is always useful (occasionally) to draw reports on popular search terms.
I've a form for creating a new Customer. A customer may have a mobile number. Mobile number should be persisted without + or 00 prefix that user can type. This can be accomplished easly with:
$customer->setMobile(preg_replace("/^(\+|00)/", '', $customer->getMobile()));
Which is the best place to put this code?
Inside a CustomerController prior to call entity manager and persist the entity. Is this really a matter of a controller in MVC pattern?
Using a SanitizeCustomerSubscriber and listening to FormEvents:POST_BIND event
Using a CustomerSanitizer service
Any other idea? Of course i'm speaking of data manipulation in general, mobile number is just an example: fields to be sanitized could be more than just one.
You should do this in the PRE_BIND event, where you can access the submitted data before it is being processed.
$builder->addEventListener(FormEvents::PRE_BIND, function (FormEvent $event) {
$data = $event->getData();
if (isset($data['mobile'])) {
$data['mobile'] = preg_replace("/^(\+|00)/", '', $data['mobile']);
}
$event->setData($data);
});
For the record, as of Symfony 2.3, this event is called PRE_SUBMIT.
I'd put this into the Customer setMobile() method — the closer to the data itself, the better. This way the mobile number will be sanitized no matter what controllers or forms are used to set it.