Does Phalcon support content negotiation out-of-the-box or is there some easy-to-implement solution? I'm scouring the 'nets and not seeing it.
Thanks!
Short answer is no and thank god for that, or we'd have another 100 bugs for a non-major component :)
You can easily plug an existing library, like Negotiation, into DI and use it later globally throughout the app.
$di->setShared('negotiator', function(){
return new \Negotiation\Negotiator();
});
$bestHeader = $di->getShared('negotiator')->getBest('en; q=0.1, fr; q=0.4, fu; q=0.9, de; q=0.2');
Keep in mind that with the default server config (.htaccess / Nginx) from examples static files will be served as is, without interception by Phalcon. So, to server files from the server it would be best to create a separate controller / action to handle that rather than making all request go through your app.
Edit:
If it's simply about enabling your app sending either xml or json based on the common distinction (header, param, method), then you can easily accomplish it without external frameworks. There are many strategies, the simplest would be to intercept Dispatcher::dispatch(), decide in there what content to return and configure the view and response accordingly – Phalcon will do the rest.
/**
* All controllers must extend the base class and actions must set result to `$this->responseContent` property,
* that value will be later converted to the appropriate form.
*/
abstract class AbstractController extends \Phalcon\Mvc\Controller
{
/**
* Response content in a common format that can be converted to either json or xml.
*
* #var array
*/
public $responseContent;
}
/**
* New dispatcher checks if the last dispatched controller has `$responseContent` property it will convert it
* to the right format, disable the view and direcly return the result.
*/
class Dispatcher extends \Phalcon\Mvc\Dispatcher
{
/**
* #inheritdoc
*/
public function dispatch()
{
$result = parent::dispatch();
$headerAccept = $this->request->getHeader('Accept');
$headerContentType = $this->request->getHeader('Content-Type');
$lastController = $this->getLastController();
// If controller is an "alien" or the response content is not provided, just return the original result.
if (!$lastController instanceof AbstractController || !isset($lastController->responseContent)) {
return $result;
}
// Decide what content format has been requested and prepare the response.
if ($headerAccept === 'application/json' && $headerContentType === 'application/json') {
$response = json_encode($lastController->responseContent);
$contentType = 'application/json';
} else {
$response = your_xml_convertion_method_call($lastController->responseContent);
$contentType = 'application/xml';
}
// Disable the view – we are not rendering anything (unless you've already disabled it globally).
$view->disable();
// Prepare the actual response object.
$response = $lastController->response
->setContent($response)
->setContentType($contentType);
// The returned value must also be set explicitly.
$this->setReturnedValue($response);
return $result;
}
}
// In your configuration you must insert the right dispatcher into DI.
$di->setShared('dispatcher', function(){
return new \The\Above\Dispatcher();
});
Just thought that you can probably achieve the same using dispatch loop events. The solution in theory might look more elegant but I never attempted this, so you might want to try this yourself.
Related
I’m in TYPO3 11.5. In my sitepackage extension I need to access the TYPO3 request object in either Configuration/TCA/Overrides/tt_content.php or a custom class method. But I don’t know how to instantiate the PSR-7 request object nor is the old style $GLOBALS['TYPO3_REQUEST'] available in either of the two files (I get the warning that it’s undefined).
Questions:
How can I instantiate the PSR-7 request object in either the TCA override or my custom class?
If that’s not possible, why can’t I get access to $GLOBALS['TYPO3_REQUEST']?
Background:
In Configuration/TCA/Overrides/tt_content.php of my sitepackage extension I define several custom content elements. To make it a little easier I write their configuration (CType, showItem, flexform, …) into an array and then iterate through it, calling a custom class method. This class method calls the ExtensionManagementUtility methods addTcaSelectItem and addPiFlexFormValue for each content element.
That all works. But now I want to add a condition based on the site object (my sitepackage should run on two different sites) and I try to get the site information using the TYPO3 request object. The documentation on the TYPO3 request object tells me that I need to pass it as an argument to a user function – but it does not tell me how to instantiate it.
Code snippets:
My code snippets show different unsuccessful attempts (v1, v2) of accessing the request object in tt_content.php and in my class method.
Snippet of Configuration/TCA/Overrides/tt_content.php:
// ... defining associative array $ce with CType names as key ...
// then calling my class method on each $ce element:
$addCEObj = new \Vendor\Sitepackage\Utility\AddContentElement;
$addAfter = 'textmedia';
foreach ($ce as $CType => $tcaConf) {
// $tcaConf contains 'showItem', 'colOverrides', 'flexform' and the localised title
$tcaConf['CType'] = $CType;
$tcaConf['addAfter'] = $addAfter;
/* (v1a) */ $addCEObj->tca($tcaConf, $request);
/* (v1b) */ $addCEObj->tca($tcaConf, $GLOBALS['TYPO3_REQUEST']);
/* (v2) */ $addCEObj->tca($tcaConf);
$addAfter = $CType;
}
Snippet of Classes/Utility/AddContentElement:
class AddContentElement
{
/**
* used for v2
*/
public function __construct()
{
$this->request = $GLOBALS['TYPO3_REQUEST'];
}
/* (v1) */ public static function tca(array $tcaConf, ServerRequestInterface $request)
/* (v2) */ public function tca(array $tcaConf)
{
/* (v1) */ $site = $request->getAttribute('site');
/* (v2) */ $site = $this->$request->getAttribute('site');
$siteId = $site->getIdentifier();
// ... adding the content element to the TCA based on site identifier using:
ExtensionManagementUtility::addTcaSelectItem(...);
ExtensionManagementUtility::addPiFlexFormValue(...);
}
}
TCA is generated once then and cached. accessing the request. is a bad idea in this context. there might not even a request if the first instance which needs TCA is CLI command.
so therefore i would strongly suggest not to use this king of thing. but rather
use TSconfig to disable the content elements you don' want on a certain site.
https://docs.typo3.org/m/typo3/reference-tsconfig/main/en-us/PageTsconfig/TceForm.html#removeitems
I'm very new to Laravel and I was given a Laravel project, where I need to add some new features. The person, who has previously worked on that project hadn't left even a single comment in the code and now I must make my own scenarios about the features.
I have a controller, defined with some functions (dashboard, show_project, save_project etc.) and in one of my function, I need to use the result of calling other function.
In the concrete example, the call is made from "http://127.0.0.1:8000/username/project_slug" - there is a button "Save" and post function, called on onClick event. The function, whose output I need is normally called on "http://127.0.0.1:8000/username/project_slug/svg", which returns a view.
For better understanding, there's an example of the flow:
The user wants to save his/her project (an UML diagram) but in order to have a thumbnail, a function which generates a view (SVG format) will be called and the idea is, to take the HTML content of the page, which is on "http://127.0.0.1:8000/username/project_slug/svg" and to pass it to another API in order an image to be generated.
So far, I tried with cURL, file_get_contents, file_get_html, render methods but when I return the output, the server just keeps waiting and shows no error messages.
//The both functions are in ProjectController.php
/**
* A function, for saving the json file, where the whole of the diagram
* components are described. From the frontend we receive the project_id and
* the project_data(the json content).
*/
public function save_project(Request $request) {
$input = $request->only(['project_id', 'project_data']);
/*
Here we need to call the other function, to render the HTML content
and to pass it to the other API. Then we save the result with the
other information.
*/
/*
What I've tried?
$new_link = 'http://' . $_SERVER['HTTP_HOST'] . "/$username"
."/$project_slug" . "/svg";
$contents = file_get_contents($new_link);
return $contents;
*/
//In the same way with cURL.
$project = Project::where('user_id',session('userid'))
->where('id',$input['project_id'])->first();
$project->project_data = json_encode($input['project_data']);
if($project->save()) {
return ["status"=>"saved"];
}
else {
return ["status"=>"error"];
}
}
/**
* A function, which takes the the Json content (project_data) from the
* database and passes it to the view, where the Json is transformed in HTML
* tags.
*/
public function generate_svg(Request $request,$username,$project_slug) {
if(session('username')!=$username) {
return redirect("/");
}
$userid = session('userid');
$project = Project::where([
'user_id' => $userid,
'slug' => $project_slug,
])->first();
if(!is_null($project)) {
return view('svg',compact('project'));
}
}
I've read about some possible ways, including Guzzle request but maybe I haven't understood correctly the idea:
If I need to make a Guzzle request from my controller to the other function inside my controller, do I need an API configuration?
What I mean? Example:
Before saving the project, the user is on this URL address "http://127.0.0.1:8000/hristo/16test". Inside the controller, I have in session variables the token, the username(hristo) and i can get the project_name(16test) from the URL but after passing this URL to the generate_svg function, there is no indication of error or success.
So I'm missing some kind of token information?
If you just need the response of the other function you can just use
$response = $this->generate_svg($request, $username, $project_slug);
If you'll need to use this function from a different controller you can use this
app('App\Http\Controllers\UsernameController')->generate_svg($request, $username, $project_slug);
I'm building a RESTFUL API with FOSRest Bundle and return data with custom headers like this:
class InvestorController extends AbstractFOSRestController
{
/**
* Retrieve a list of investors
*
* #Rest\Get("/{page}", defaults={"page"=1}, requirements={"page"="\d+"})
*
* #param Integer $page
* #param Request $request
* #param InvestorRepository $investorRepository
*
* #return Response
*/
public function getInvestorsAction($page, Request $request, InvestorRepository $investorRepository)
{
$data = $investorRepository->getInvestorsList($page);
$data = $this->getUser()->getId();
$view = $this->view($data, 200)
->setHeader('Access-Control-Allow-Origin', 'http://localhost:3000') // Remove this bit in PROD
->setHeader('Access-Control-Allow-Credentials', 'true'); // Remove this bit in PROD
return $this->handleView($view);
}
}
Now I would like to pass these headers for all of the responses in this Controller. I would like to avoid having to send them manually each time. Is there a way to set custom headers for all of the controller's responses automatically, say, in the constructor or in somewhere else?
Thank you
There is multiple ways how to do this:
First one that comes to my mind would be to configure your webserver to attach these headers to every response it sends based on URL.
Second one would be to utilize Symfony internal event system and catch kernel.response and attach the headers there. You would need to filter the response based on where is coming from -
https://symfony.com/doc/current/reference/events.html#kernel-response
The other thing would be to create your own custom handler which calls these setHeader() methods instead of doing it inside Controller. To provide this handler you can simply override handleView() method -
https://symfony.com/doc/master/bundles/FOSRestBundle/2-the-view-layer.html#custom-handler
.. Couldn't think of a descriptive enough title. What I'm asking for is how do I do this?
I want the following 2 API calls
GET /api/users/2/duels - returns all of the duels for user 2
GET /api/users/2 - returns the profile for user 2
Since PHP doesn't support method overloading, it isn't clear to me how to make this work.
Currently I have the function
function get($id, $action){
//returns data based on action and id
}
And I can't just make
function get($id){
//returns profile based on id
}
because of said reasons.
Any help is greatly appreciated!!!
You can use the #url phpdoc decorator to tell restler of any special invocation schemes that doesn't match the direct class->method mapping.
/**
* #url GET /api/users/:userId/duels
*/
public function getDuels($userId)
{
}
.. should probably work.
One approach is handling both cases in same function with a conditional block as shown below
function get($id, $action=null){
if(is_null($action)){
//handle it as just $id case
}else{
//handle it as $id and $action case
}
}
if you are running restler 3 and above you have to disable smart routing
/**
* #smart-auto-routing false
*/
function get($id, $action=null){
if(is_null($action)){
//handle it as just $id case
}else{
//handle it as $id and $action case
}
}
Another approach is to have multiple functions since index also maps to root, you have few options, you can name your functions as get, index, getIndex
function get($id, $action){
//returns data based on action and id
}
function index($id){
//returns profile based on id
}
If you are using Restler 2 or turning smart routing off, order of the functions is important to fight ambiguity
If you are running out of options for function names you can use #url mapping as #fiskfisk suggested but the route should only include from method level as the class route is always prepended unless you turn it off using $r->addAPIClass('MyClass','');
function get($id){
//returns data based on action and id
}
/**
* #url GET :id/duels
*/
function duels($id)
{
}
HTH
I'm guessing its just not done since I can't find any reference to this anywhere, even people asking without response. Although I'm hoping Symfony calls it something else.
How can I get Symfony to auto render views/{controller}/{action}.html.php
No, it's not possible in standard edition of Symfony. However, routing is just one of its components. So if you really want, you can create your own component and use it instead.
You have two options:
1) Use SensioFrameworkExtraBundle - it allows to use #Template annotation. (It's included in SE).
2) Write your own method. I found it annoying to write #Template and any annotations in controllers every time so I added this method in the base controller (it's only an example, examine before using it in production):
public function view(array $parameters = array(), Response $response = null, $extension = '')
{
$extension = !empty($extension) ? $extension : $this->templateExtension;
$view = ViewTemplateResolver::resolve($this->get('request')->get('_controller'), get_called_class());
return $this->render($view . '.' . $extension, $parameters, $response);
}
class ViewTemplateResolver
{
public static function resolve($controller, $class)
{
$action = preg_replace('/(.*?:|Action$)/', '', $controller);
if (preg_match('~(\w+)\\\\(\w+Bundle).*?(\w+(?=Controller$))~', $class, $name)) {
return implode(':', array($name[1] . $name[2], $name[3], $action));
}
}
}
Now in the controller we can do: return $this->view();
When you do routing with annotations instead of yaml, you can add a #Template() to your action method and it's rendering the default template as requested by you.
To do this, change your routing to annotations:
AcmeDemoBundle:
resource: "#PAcmeDemoBundle/Controller/"
type: annotation
prefix: /
Within your controllers, add this for each action:
/**
* #Route("/index", name="demo_index")
* #Template()
*/
Actually I don't know if there is a way to get this behaviour when not using annotations. But as there seems to be logic for this, there might be one.
#meze's answers are both more desirable than out of the box behaviour.
However I think the SensioFrameworkExtraBundle he pointed me to has given me the clue that I needed to achieve this without replacing my own route.
That is to hook into the kernel view event.
Its purpose is specifically stated as:
The purpose of the event is to allow some other return value to be converted into a Response.
I'm assuming then that it can be used to convert a null return from the controller action to a response.