I'm new to OOP and MVC with PHP, and I'm currently learning by making my own custom MVC app for testing purposes. I’m not using Symfony yet, but I’ve integrated Twig and I have a problem with it when I call a 404 error outside a controller, in the two following cases :
When requested page doesn't exist on server (like http://localhost/test/)
When requested URL doesn't match with an existing controller and method (like http://localhost/test/task/)
I've got the following error :
Fatal error: Uncaught Error: Class 'Twig\Loader\FilesystemLoader' not found in /Applications/XAMPP/xamppfiles/htdocs/librairies/Renderer.php:32
Stack trace: #0 /Applications/XAMPP/xamppfiles/htdocs/librairies/Renderer.php(21): Renderer::loadTwig()
#1 /Applications/XAMPP/xamppfiles/htdocs/librairies/Http.php(26): Renderer::render('errors/404', Array)
#2 /Applications/XAMPP/xamppfiles/htdocs/librairies/Application.php(35): Http::error404()
#3 /Applications/XAMPP/xamppfiles/htdocs/index.php(9): Application::process()
#4 {main} thrown in /Applications/XAMPP/xamppfiles/htdocs/librairies/Renderer.php on line 32
However, if I call the right controller with an existing method, everything works fine (like http://localhost/article/show/123).
If I call 404 error inside a controller method (e.g. if the $_GET ID of an article does not exist in the DB), everything works fine too and my 404 error template is correctly rendered.
How I call a 404 error ?
I use a static method Http::error404() that render my 404 error Twig template.
class Http
{
/**
* Display a 404 error - page not found
*
* #return void
*/
public static function error404(): void
{
header('HTTP/1.1 404 Not Found');
$pageTitle = "Erreur 404 - Page non-trouvée";
Renderer::render('errors/404', compact('pageTitle'));
exit;
}
}
Application class
My App use a mini-router named Application. It checks if the controller and the called method exists. If not, call 404 error with Http::error404(), and it is in this case that the above fatal error appears.
class Application
{
/**
* This is the app process, called in index.php file.
* Use controllers and methods directly in URL.
* URL are rewritten by .htaccess file at app root.
*
* Usage : https://example.com/controller/task/{optional_parameter}
* Ex : https://example.com/article/show/145
*
* #return void
*/
public static function process()
{
// By default, call homepage
$controllerName = 'ArticleController';
$task = 'home';
if (!empty($_GET['controller'])) {
$controllerName = ucfirst($_GET['controller'] . 'controller');
}
if (!empty($_GET['task'])) {
$task = $_GET['task'];
}
$controllerPath = "\controllers\\" . $controllerName;
// Check if this controller & method exists
if (!method_exists($controllerPath, $task)) {
Http::error404(); // <-- Here, i'm not in a controller (line 35)
}
$controller = new $controllerPath();
$controller->$task();
}
}
There is a strange thing here : if I define any controller before calling 404 error, everything works as I would like and my 404 error template is correctly rendered. But it’s not a clean and elegant solution...
// Check if this controller & method exists
if (!method_exists($controllerPath, $task)) {
new \Controllers\ArticleController(); // <-- Not clean, but solve my fatal error
Http::error404();
}
The same thing happens in my 404.php file, called by the server and declared in the .htaccess when a file is called and it doesn’t exist. My 404 PHP file just contains few lines :
require_once 'librairies/autoload.php';
Http::error404();
If I define any controller, it's working perfectly and fatal error disappears.
require_once 'librairies/autoload.php';
new Controllers\ArticleController();
Http::error404();
Render Class
This is my rendering class. The fatal error appears in this file, as shown in line 32 below, when Http class with error404 method render errors/404 Twig template.
require_once 'librairies/autoload.php';
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFunction;
class Renderer
{
/**
* Print a HTML template with $var injection
*
* #param string $path
* #param array $var
*/
public static function render(string $path, $var = [])
{
$template = self::loadTwig()->load("$path.html.twig");
echo $template->display($var);
}
/**
* Load Twig
*
* #return Environment
*/
public static function loadTwig()
{
$loader = new FilesystemLoader('templates'); // <-- Line 32 is here !
$twig = new Environment($loader, [
'auto_reload' => true,
'autoescape' => 'html'
]);
$twig->addFunction(
new TwigFunction('notificationDisplay', function() { return Notification::display(); })
);
return $twig;
}
}
I have only added the code that I think is related to my issue. Do not hesitate to ask if there are any points that need to be clarified.
I’ve been looking for a solution to this error for seven days and haven’t found a solution. Your help is my last resort. Thank you very much!
I removed all the many unnecessary require_once autoloader in app files, and kept only the one in index.php, as indicated by #NicoHaase.
From that point, Twig is no longer found and the same error message appears on all requests !
The solution was easy to find : in my index.php file, I was using the wrong autoloader. I was calling my custom autoloader instead of the one from Composer...
I just change :
require_once 'librairies/autoload.php';
\Application::process();
To :
require_once 'vendor/autoload.php';
\Application::process();
And everything works fine now ! Thanks to #NicoHaase and #DarkBee for putting me on the right track !
Related
I have a controller has an action that looks something like this:
/**
* #Route("/my_route_path", name="my_route_name")
*/
public function doSomethingAction(Request $request)
{
$myPath = $request->getScheme().'://'.$request->getHttpHost().''.$request->getBasePath();
$data = file_get_contents($myPath. '/data_folder/data.json');
return $this->render('#Entry/my_template.html.twig', array(
'data' => json_decode($data, true)
));
}
And I create a functional test for this controller like this:
/** #test */
public function doSomething_should_success()
{
$client = static::createClient();
$crawler = $client->request('GET', '/my_route_path');
$this->assertEquals(200, $client->getResponse()->getStatusCode());
}
But I can't run the functional test I still get : Failed asserting that 500 is identical to 200
So, after I checked the test.log file I find this error : file_get_contents(http://localhost/data_folder/data.json) : failed to open stream
As now the problem is comming from $request->getBasePath() because always contain empty string but the expected behaviour is return PATH_TO_MY_PROJECT_FOLDER\web in my case must return projects\web_apps\MY_PROJECT_FOLDER_NAME\web
So, the simplified question: why the request object always contain an empty basePath string in the unit test but it works very well on the browser.
The Request object helps you handle the request of a client, that is something like GET /my_route_path plus lots of headers and a server that is directed at.
The web server passes those information on to php and symfony, and symfony will turn this into a Request object. Symfony has usually one entry point, which is public/index.php (symfony 4) or web/app.php (symfony 3) which is assumed to be / or possibly /basePath/ (the basepath will be communicated by the web server and handled by Symfony).
Symfony will generate a Request object, where the basepath is essentially abstracted away, and whenever you generate a url (via Controller::generateUrl) the base path is taken into account. that's why the basepath is important for Requests.
This is actually described pretty well in the comments of the Request's functions:
getBasePath vs getPathInfo.
However, this only concerns the public facing URLs and doesn't have anything to do with how you structure your project and where that project is located, because that's completely irrelevant to the Request (separation of concerns and stuff).
So I guess, you are actually looking for the root directory of your project.
To find the location of your project dir, there is the very base version, where you directly use the PHP magic var __DIR__ which contains the directory the current script file is in, and you can navigate from there. since controllers are usually located such that their path is projectdir/src/Controller/TheController.php a __DIR__.'/../.. would give you the projectdir. However, that's not really clean. The better version:
Depending on the symfony version you're using, you should retrieve the project dir via the ParameterBagInterface (symfony 4)
function doSomethingAction(ParameterBagInterface $params) {
$projectDir = $params->get('kernel.project_dir');
}
or via the container (symfony 3) see also: new in symfony 3.3: A simpler way to get the project root directory
function doSomethingAction() {
$projectDir = $this->getParameter('kernel.project_dir');
}
In my case I had to inyect RequestStack $stackand access the main request, after that my "BasePath" has value. This is because I where in a subrequest and I had to access to the top level of the request.
This post helped me to understood: Symfony2 - get main request's current route in twig partial/subrequest
/**
* #Route("/myroute", name="myroute")
*/
public function myroute(RequestStack $stack)
{
$request = $stack->getMainRequest();
$route = $request->getPathInfo();
}
Working on a REST API for PDF processor using Mpdf(and tfox symfony bundle) on Symfony 3 Framework. I created two GET requests, one with no parameters for testing, and one with the parameter(URL of the HTML file) I want to read and then convert into PDF.
The Generic GET function:
/**
*
* #Rest\Get("/create")
*/
public function createPDFAction(){
$mpdfService = $this->get('tfox.mpdfport');
$html = "<h1> Hello </h1>";
$mpdf = $mpdfService->getMpdf();
$mpdf->WriteHTML($html);
$mpdf->Output();
exit;
}
The Second GET function with parameter:
/**
* #param $htmlSource
* #Rest\Get("/create/{htmlSource}")
*/
public function createPDFFromSourceAction($htmlSource){
$mpdfService = $this->get('tfox.mpdfport');
$html = file_get_contents($htmlSource);
$mpdf = $mpdfService->getMpdf();
$mpdf->WriteHTML($html);
$mpdf->Output();
exit;
}
The problem is, when I call the second function using browser or Postman the first function is always returned instead and I get the PDF with "Hello", if I remove the first GET function, I get error "no route found for GET/create"
I investigated:
The PDF URL is correct, I manually inserted it in first function and worked
No syntax error, I copied the same function without the parameters and worked
The Calls I do are:
http://localhost:8000/create This one works
http://localhost:8000/create?htmlSource=PATH-TO-FILE-LOCALLY This one doesnot work
If I put the PATH-TO-FILE-LOCALLY in function 1 manually it works fine
So I have 2 Questions:
Since I am new to REST and LAMP, should I use GET or others ? My goal is to read the HTML form that the user will fill into a variable and pass it to Mpdf which will convert it into PDF and return that PDF for viewing or download
Why only the first GET function is being read ?
Notes: I am developing on Linux, with PHPStorm, PHP 7, Symfony 3, localhost, the html file I am testing with is on my local machine
Side point: In case this is resolved, I am supposed to upload this to my clients server (which is Apache) - do you have any guides on how to do that and what should be the URLs changed to ?
Thank you all in advance
Updates:
I have changed the functionality to POST methods and it now works fine:
/**
* #Rest\Post("/mPDF/")
*/
public function createPDFAction(Request $request){
$source = $request->get('source');
if($source == ""){
return new View('No Data found', Response::HTTP_NO_CONTENT);
}
$mpdfService = $this->get('tfox.mpdfport');
$html = file_get_contents($source);
$mpdf = $mpdfService->getMpdf();
$mpdf->WriteHTML($html);
$mpdf->Output();
exit;
}
After publishing to Apache production server and some configuration tweaks the site is now live ! - but now I am facing a new issue which I will post a new question for with all the config info I have - basically POST method is returning {
"error": {
"code": 405,
"message": "Method Not Allowed"
}
}
http://localhost:8000/create?htmlSource=PATH-TO-FILE-LOCALLY
("/create/{htmlSource}")
These paths do not match.
First path consists of domain name, and route create, while second path has route "create" + slash + wildcard.
Query parameters are not defined within routing annotation. Instead, access them inside controller, using
public function createPDFFromSourceAction(Request $request)
{
$htmlSource = $request->query->get('htmlSource'); // query string parameter
$somethingElse = $request->request->get('somethingElse'); //POST request parameter
...
}
Symfony will pass Request object inside the controller for you.
As for your other question, GET requests are usually used for things that do not change the state of the application, and POST/PUT/PATCH/DELETE requests change the state. Since you are uploading something, use POST request.
For your 'side note' you should ask another question instead.
I am encountering a very strange thing while doing testing with Laravel 4. It looks like a bug but there's probably a logical explanation.
I have replicated the "bug" in a clean Laravel install and here's my code:
My resource controller /app/controllers/TestController.php:
(Created with php artisan controller:make TestController)
class TestController extends \BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
return Response::json(array());
}
// The end of the file is unchanged
In my app/routes.php:
Route::get('/', function()
{
return View::make('hello');
});
// Note the composed part of the URL.
// My problem isn't present if I just use `myapi` or `path` as a url
Route::resource('myapi/path', 'TestController');
Added in /app/test/ExampleTest.php:
public function testTest()
{
$res = $this->call('GET', 'myapi/path');
// Until here everything works right
$this->assertEquals(json_decode($res->getContent()), array());
// Now I call the same URL a second time
$res = $this->call('GET', 'myapi/path');
$this->assertEquals(json_decode($res->getContent()), array());
}
Now I run phpunit and here's what I get:
There was 1 error:
1) ExampleTest::testTest
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:
/home/me/Web/laraveltest/bootstrap/compiled.php:5531
/home/me/Web/laraveltest/bootstrap/compiled.php:4848
/home/me/Web/laraveltest/bootstrap/compiled.php:4836
/home/me/Web/laraveltest/bootstrap/compiled.php:4828
/home/me/Web/laraveltest/bootstrap/compiled.php:721
/home/me/Web/laraveltest/bootstrap/compiled.php:702
/home/me/Web/laraveltest/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php:81
/home/me/Web/laraveltest/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php:332
/home/me/Web/laraveltest/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php:51
/home/me/Web/laraveltest/app/tests/ExampleTest.php:25
In my other project I get a slightly different backtrace, but I have the impression that's the same problem: (but I have no idea of why the other is compiled and this one not)
2) UnitModelTest::testOther
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php:148
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1049
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1017
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:996
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:775
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:745
/home/me/Web/my-project/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php:81
/home/me/Web/my-project/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php:327
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php:51
/home/me/Web/my-project/app/tests/UnitModelTest.php:32
In both case the line given in the trace for the test file corresponds to the second call of the test.
As I noted in the comments of the routes.php file, if I use a simple url with no slash, the test passes without problem.
I have no problem when I use the api from the browser.
I found many topics related to the NotFoundHttpException on StackOverflow, but none looks like mine. This one is specifically present when testing and only trigger an error at the second call.
So what am I doing wrong here ? Or is it really a bug ?
The problem is that calls made with the same client will use the provided URI relatively. That means what you actually call is:
myapi/path
myapi/myapi/path
You can fix this if you add a preface the urls with a / to make them absolute to the root of the application.
public function testTest()
{
$res = $this->call('GET', '/myapi/path');
// Until here everything works right
$this->assertEquals(json_decode($res->getContent()), array());
// Now I call the same URL a second time
$res = $this->call('GET', '/myapi/path');
$this->assertEquals(json_decode($res->getContent()), array());
}
If you experience other issues like that it often helps to call
$this->refreshApplication();
(This would also create a new client and therefore solve this issue as well)
In my case this error happened because i change public directory name to public_html my solution was put this in the \App\Providers\AppServiceProvider register method.
public function register()
{
// ...
$this->app->bind('path.public', function() {
return base_path('public_html');
});
}
In Kohana 2 you can override the default error page by creating a custom kohana_error_page.php file and putting it in your view directory. However, this overrides ALL errors not just 404's. In the case of 500 errors, I'd still like to get the friendly orange kohana error page.
Does anyone have experience in doing this?
You can do this in KO2 quite easily with hooks. If you look at events you'll find system.404 which you'll need to replace using something like:
<?php defined('SYSPATH') or die('No direct script access.');
// Replace the default kohana 404
Event::replace('system.404', array('Kohana', 'show_404'),
array('hook_404', 'show'));
class hook_404 {
public function show()
{
// first param is URI of page, second param is template to use
Kohana::show_404(FALSE, 'custom_404');
}
}
Save this in the hooks directory in your app folder (or in a module). Don't forget to enable hooks in your config:
$config['enable_hooks'] = TRUE;
And add your custom 404 view: views/custom_404.php.
Note: these won't display if you have $config['display_errors'] set to FALSE in your config.php (which it probably should be if you're IN_PRODUCTION right?). For that you need to output something and die eg. replace Kohana::show_404 with the following:
require Kohana::find_file('views', 'custom_404');
die();
I have not tested this!! So backup your code before you do this.
If I'm not mistaken, Kohana2 has hard-coded exception handling and there is no pretty way to add new exceptions. To work around that, you will have to make change in the core.
In the file system/i18n/en_US/errors.php
add new entry:
E_PAGE_ACCESS_DENIED => array( 1, 'Access Denied', ' -- message text-- ')
In the file system/i18n/en_US/core.php
add new entry:
'page_access_denied' => 'You are not permitted to access %s .'
In the system/core/Koahana.php:
near the top of Kohana::setup() method, add new constant:
define('E_PAGE_ACCESS_DENIED', 69);
register the the event for your custom error ( somewhere near the end of same Kohana::setup() you will see registration for 404th error) :
Event::add('system.403', array('Kohana', 'show_403'));
next, find the location for Kohana::show_404() and create you own method:
public static function show_403($page = FALSE, $template = FALSE)
{
throw new Kohana_403_Exception($page, $template);
}
scroll down to the bottom of the file .. there you will find the class definition for the Error_404_Exception ... make one for 403. Don't forget to:
define new variable protected $template = 'file_name_for_template';
protected $code = E_PAGE_ACCESS_DENIED;
Exception::__construct(Kohana::lang('core.page_access_denied', $page));
header('HTTP/1.1 403 Forbidden');
the template file will have to be located at system/views/
Now you should be able to call Event::run('system.403'); from anywhere in the application.
I am trying to use a SOAP Client-Server in my computer and it doesn't look like it is going to work, I am getting this error Error Fetching Http Headers when I try to run my SOAP Client.
I have been looking and the solution that I have encountred is to increase the default_socket_timeout from 60 to 120 seconds and it doesn't work for me, also I have seen another solution that is putting the vhost in my apache KeepAlive Off and that didn't work.
The WSDL is working fine because I try to use it in another computer and it work.
I am running PHP Version 5.3.5-1ubuntu7.4 in Linux Mint using Zend Framework, I hope some of you can help me fix this thank you.
I'm sorry but I don't know what you are using to set up your SOAP service.....
If you can give more information about your SOAP service (poss Zend_Soap given the Zend Framework tag) etc that would be great.
Also, as a quick alternative, you say you've looked at the WSDL on another computer, perhaps try the application in an alternative environment to ensure it's not an environment issue.
May be a simple issue with your client-server code.
UPDATE: Ok so I realised the example I mentioned yesterday wasn't fully implemented so I've hacked something together quickly that you can try to see if it works in your environment.
The code is a mix of something I found here (an example of Zend_Soap_Server) and something from another SO question here (an example of a basic SOAP service test).
I've tested it at my end using ZF 1.11 and the example I'm outlining uses the default Application path you get with a new ZF project (e.g models are in directory application/models so the model shown is headed up Application_Model_Classname).
If it works, you can tweak accordingly....if it doesn't work we can try something else.
Start by creating a new SOAP controller and set the class up like this:
<?php
class SoapController extends Zend_Controller_Action
{
public function init()
{
ini_set("soap.wsdl_cache_enabled", "0"); //disable WSDL caching
$this->_helper->layout()->disableLayout(); //disable the layout
$this->_helper->viewRenderer->setNoRender(); //disable the view
}
public function indexAction ()
{
if (isset($_GET['wsdl'])) {
//return the WSDL
$this->handleWSDL();
} else {
//handle SOAP request
$this->handleSOAP();
}
}
private function handleWSDL ()
{
$strategy = new Zend_Soap_Wsdl_Strategy_AnyType();
$autodiscover = new Zend_Soap_AutoDiscover();
$autodiscover->setComplexTypeStrategy($strategy);
$autodiscover->setClass('Application_Model_SoapService');
$autodiscover->handle();
}
private function handleSOAP ()
{
$server = new Zend_Soap_Server(null,
array('uri' => "http://YOURDOMAIN/soap?wsdl"));
$server->setClass("Application_Model_SoapService");
$server->handle();
}
public function testAction()
{
$client = new Zend_Soap_Client("http://YOURDOMAIN/soap?wsdl");
try {
echo $client->testMethod('test');
} catch (Exception $e) {
echo $e;
}
}
}
In the class above, the WSDL is automatically generated using Zend_Soap_Autodiscover with a SoapService.php file at application/models/SoapService.php used as the template. Note the DocBock comments above each method in your target class are integral to this process.
Next create the SoapService.php file in the default models folder:
<?php
class Application_Model_SoapService
{
/**
* testMethod
*
* #param string $string
* #return string $testSuccess
*/
public function testMethod(string $string)
{
$testSuccess = 'Test successful, the message was: ' . $string;
return $testSuccess;
}
}
If all is working as it should be you can visit:
http://YOURDOMAIN/soap?wsdl
to see the WSDL and visit:
http://YOURDOMAIN/soap/test
to get a success message with the string you specified in the client request within the testAction() code in the SoapController class as part of the message.
Let me know if it's working or not and we can go from there.
I'll be able to have another look on Monday.