Symfony calling functions between controllers - symfony

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 ]]

Related

how to create save actions in api-platform

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.

Need help extending a class in a Drupal 8 custom module

I'm using the elasticsearch connector module for Drupal 8 to manage indexes. https://www.drupal.org/project/elasticsearch_connector
One thing I need to happen on index creation time is to add some analyzers and filters to the elasticsearch index mapping.
I've tracked down the place that's happening here in the create() method for one of their classes:
modules\contrib\elasticsearch_connector\src\ElasticSearch\Parameters\Factory\IndexFactory.php
I have a custom module that I'm trying to extend that method and add to it but can't seem to get it to fire. I have the module enabled properly as it shows up on the extend page. So far my folder structure for my custom module looks like this:
modules\custom\elasticsearch_analyzer\src\elasticsearch_analyzer.php
<?php
namespace Drupal\elasticsearch_analyzer;
use Drupal\search_api\IndexInterface;
use Drupal\elasticsearch_connector\ElasticSearch\Parameters\Factory\IndexFactory;
class elasticsearch_analyzer extends IndexFactory {
/**
* {#inheritdoc}
*/
public static function create(IndexInterface $index) {
die('does this work?');
return [
'index' => IndexFactory::getIndexName($index),
'body' => [
'settings' => [
'number_of_shards' => $index->getOption('number_of_shards', 5),
'number_of_replicas' => $index->getOption('number_of_replicas', 1),
],
],
];
}
}
I've tried a bunch of different combinations surrounding the PSR-4 standard, but can't seem to get it to run. Is this how we'd normally go about extending things in Drupal 8? Should I just be putting this class in the .module file at the root of my module? Should I be registering this as a service?
Based on the tutorials I've read, I haven't been able to see any examples of extending contributed modules or core outside of creating block plugins or simple route pages. I thought now that we're out of the hook world I should be able to examine code within an existing class and override it using OOP inheritance. Or is that not the case?
You cannot just extend a class. How is the module supposed to call this new class? How to you communicate its existence to other modules so they are able to call it? Your class in isolation does not do anything.
I was reading through the source code of this module. This IndexFactory class is used in the SearchApiElasticsearchBackend class which is registered as a backend to the Search API module (i.e. the search api knows that this backend exists and calls it).
Sadly this IndexManager class is just hardcoded into the backend and there is no way to inject your own. The class has static methods only so they are called directly. Even if you would create your own search backend and extend SearchApiElasticsearchBackend you would have to replace IndexManager with YourCustomIndexManager everywhere.
You will have to change the create() method directly in module to do what you want.

Symfony (a bit more dynamic ?) routing

I am new to symfony. As an exersice I`m trying to make some basic cms.And I was wondering is this aproach of routing wrong:
/**
* #Route("/back-office/", name="back-office")
*/
public function indexAction(Request $request,$page="")
{
switch($page){
case "":
return $this->render('CmsBundle:BackOffice:index.html.twig');
break;
default:
return $this->render('CmsBundle:BackOffice:site-map.html.twig');
break;
}
}
This is my yaml confing:
back_office_pages:
path: /{page}
defaults: { _controller: CmsBundle:BackOffice:index}
By using this aproach I wont have to configure each route in the yaml file. Since routes may vary. But I am not quite sure this is the symfony way of doing things so I decided to ask for advice..
What I`m trying to achive:
Lets say we have a user that have less back-end programing expirience or not at all and he stumbuled upon the CMS. The goal is to add front end pages using some user interface. Then we store the pages(slug) in the database. In the index action we retrive this data. From the database we can also assing template to a page (we need the user to have at least some html+css+twig).
So what we do is get the pages that user added :
ex : Gallery, Contacts
we check the request url
and if the page requested is in the array from the database we return the template related to the page.
NOTE:
If you disagree with this method please do not bash me but eplain why is this wrong. Because as I said I am still new with the framework.
Try setting your routing to:
back_office_pages:
resource: "#CmsBundle/Controller/"
type: annotation
to set up Routing Annotations inside your CmsBundle.
Then, your action should be working using the url "/back-office/{page}"

Symfony2 output any HTML controller as JSON

I have a website completed that was created in Symfony2 and I now want a lot of the features of the site to now be made available in a mobile app.
My idea is by appending a simple URL variable then it will output all the variables of the relevant page request in JSON.
So if I connect to
www.domain.com/profile/john-smith
It returns the HTML page as now.
But if I go to
www.domain.com/profile/john-smith?app
Then it returns a JSON object of name, age and other profile info.
My app code then receives the JSON and processes.
I can't see any security issues as it's just really the variables presented in JSON and no HTML.
By doing the above I can create all the app code and simply make calls to the same URL as a web page, which would return the variables in JSON and save the need for any more server-side work.
The question is: How would I do this without modifying every controller?
I can't imagine an event listener would do it? Maybe I could intercept the Response object and strip out all the HTML?
Any ideas as to the best-practice way to do this? It should be pretty easy to code, but I'm trying to get my head around the design of it.
There is a correct way to configure the routes for this task
article_show:
path: /articles/{culture}/{year}/{title}.{_format}
defaults: { _controller: AcmeDemoBundle:Article:show, _format: html }
requirements:
culture: en|fr
_format: html|rss
year: \d+
However, this would still require you to edit every Controller with additional control structures to handle that output.
To solve that problem, you can do two things.
Create json templates for each template you have, then replace html in template.html.twig with template.'.$format.'.twig. (Be careful to ensure users can't pass a parameter without validation in the url, this would be a major security risk).
Create your own abstract controller class and override the render method to check the requested format and provide output based on that.
class MyAbstractController extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{
public function render($view, array $parameters = array(), Response $response = null)
{
if($this->getRequest()->getRequestFormat() == 'json')
{
return new Response(json_encode($parameters));
}
else
{
parent::render($view, $parameters, $response);
}
}
}
NOTE The above code is a prototype, don't expect it to work out of the box.
I personally would deem the second method more correct, because there is no duplication of code, and less security concern.

forward request to controller action

i'd like to forward a request to a controller to some specific action without redirecting to another url.
so, for example, the following url:
/home/peter
should internally become
/home/people/peter
i know i could catch the action name ('peter') in the 'init' function, then do a Controller::redirect, but i'd like to stay on the same url, just have the request internally forwarded to the 'people' action.
2 things to note:
'peter' is a dynamic string, so i can't just hardcode a route for this
any other action of the controller shall still work, so only a couple of strings should get forwared (eg. 'peter', 'ann' get forwarded to the 'people' action, whereas 'otherAction' is still callable)
i already found the 'handleAction' and 'handleRequest' methods in the Controller class, but just can't imagine how make use of them for my task.
SilverStripe version: 3.0.3
I suggest the use of static $url_handlers described here, that allows you to define specific routes to actions. Your use case could be achieved by using a catch-all url-handler.
But therefore we have to pay attention to the order of each route. The catch-all route has to be defined at the end. All other actions need a seperate url handler before:
public static $url_handlers = array(
'otherAction' => 'otherAction',
// ...
'people/$Name' => 'people',
'$Name' => 'people' // catch-all
);
This should fit to your explanation. The last route catches all dynamic routes which weren't handled before.
But as Ingo mentioned, both routes would deliver the same content and that could be bad for SEO. You could target the second route people/$Name to a different action which does a 301 redirect to the url with $Name only. I would recommend this if you already have public content and you want to replace the existing URLs with the short version.
I hope this solves your problem (temporarily).
First of all, I assume you have good reasons for making duplicate URLs (bad for SEO) :)
I've tried around a bit with Director::direct(), which would be the cleanest, but didn't get anywhere. Director::test() resets most request state, so doesn't qualify either.
Calling handleRequest() or handleAction() on a new controller instance is tricky, because the Director has built up a lot of state by that time already (for example pushed to the controller stack).
So, unfortunately the SilverStripe routing isn't that flexible, anything you do will go deep into system internals, and potentially break with the next release.

Resources