While i was working on a school project I ran into a problem.
I have made an Router class where it get the right files, but I am getting the following error:
Fatal error: Method mvc\App::__toString() must not throw an exception, caught Error: Call to a member function getHTML() on string in /opt/lampp/htdocs/School/testPHP/mvc/public/index.php on line 0
The echo returns the correct file path so that is not the issue I'm running into.
I think the issue is because I try to execute an function into a string.
Does anyone know how I can fix my issue?
App Class:
<?php
namespace mvc;
class App{
private $router;
public function __construct(){
$this->router = new \mvc\Router();
}
public function __toString(){
try {
echo $this->router->getView(); //this returns the correct file path
return $this->router->getView()->getHTML();
} catch (Exception $e) {
return $e.getMessage;
}
}
}
?>
Router.php:
<?php
namespace mvc;
class Router{
private $route;
private $view;
private $controller;
private $model;
public function __construct(){
require_once(LOCAL_ROOT. "php/Routes.php");
if (isset($_GET['route'])){
$this->route = explode("/" , $_GET['route']);
}
$route = isset($routes[$this->getRoute()])? $this->getRoute() : DEFAULT_ROUTE;
$this->controller = "\\controllers\\". $routes[$route]['controller'];
$this->view = "\\views\\". $routes[$route]['view'];
// $model = "\\models\\". $routes[$route]['model'];
}
private function getRoute(){
return count($this->route) > 0 ? $this->route[0] : DEFAULT_ROUTE;
}
public function getView(){
return $this->view;
}
}
?>
Routes.php
<?php
define("DEFAULT_ROUTE", "home");
$routes = array(
"home" => array(
"view" => "HomeView",
"controller" => "HomeController",
),
"form" => array(
"view" => "FormView",
"controller" => "FormController",
),
"test" => array(
"view" => "TestView",
"controller" => "TestController",
),
)
?>
TestView.php
<?php
namespace views;
class TestView extends \mvc\View{
public function getHTML(){
// return 'dit is testView';
$klik = $this->controller->getGetData("klik");
$output = "";
$output .= "<h1>".$klik++ ."</h1>";
$output .= "klik";
$output .= "<br>";
return $output;
}
}
?>
The Problem:
Okay so the problem is that you're making a call to a function from a string.
This is the getView method in the Router class:
public function getView(){
return $this->view;
}
This method returns a string:
$this->view = "\\views\\". $routes[$route]['view'];
You are then trying to call a method from this string:
return $this->router->getView()->getHTML();
The Potential Solution:
Obviously you're trying to access the getHTML method in the TestView class, so without knowing more about your setup, it's hard to get exact, but I'll take a guess at the following and you can guide me in the right direction in the comments.
If you change the constructor in your App class to this:
public function __construct(){
$this->router = new \mvc\Router();
$this->testView = new \views\TestView();
}
Then you can change your __toString method to the following:
echo $this->router->getView(); //this returns the correct file path
return $this->testView->getHTML();
I'm not sure why you'd echo and then return, usually it's one or the other, but if you elaborate more on your expected output I can help you some more, or hopefully my explanation has given you enough to work from.
Solution:
After reading your comments, it looks like you want to instantiate the class from the result of the getView method, you can do this by setting the result into a variable and calling a new class from that string and THEN calling the method from that class.
$class = $this->router->getView();
$class = new $class;
return $class->getHTML();
Related
I have a settings.php file that looks something like this:
$user="adam";
$pass="something";
then I have a class called funcs.php, in which I have a require "../settings.php" on top, but I don't know how to get the scope of "$user" into my methods...
I'm sure this is an easy fix, but I've been googling in circles for a while with no luck.
My class looks something like this:
<?php
require_once __DIR__."/../settings.php";
class Funcs
{
public function something()
{
// do stuff
return $user;
}
}
you can use the return construct inside file which can return anything, but if you want to return multiple values, it's best to use an array.
settings.php
return array(
'user' => 'adam',
'pass' => 'something'
);
file.php
$settings = require __DIR__.'settings.php';
echo $settings['user']; // adam
To pass everything returned from the file into a class:
class MyClass {
public function __construct(array $array) {
foreach($array as $key => $value) {
$this->{$key} = $value;
}
}
}
$class = new MyClass(require __DIR__ . '/settings.php');
var_dump($class);
/*
class MyClass#1 (2) {
public $user =>
string(4) "adam"
public $pass =>
string(9) "something"
}
*/
I think the issue is that these are global variables used inside a function or class scope.
If you want to access these from within a function scope you could use the "global" keyword, for example:
public function foo() {
global $user;
echo $user;
}
However – using global (and global variables in general) is usually considered pretty smelly these days.
Some alternatives:
Pass the configuration into the constructor of your class, therefore:
$config = array(
'user' => 'adam',
'pass' => 'something'
);
class MyConsumer {
private $config;
public function __construct($config) {
$this->config = $config;
}
}
$instance = new MyConsumer($config);
This is usually termed "dependency injection", because your config can be easily substituted for something else depending on the context (such as in unit tests).
An alternative is to have a singleton class which encapsulates your config, which you'd access something like (not tested but you get the idea):
// production_config.php
return array(
'user' => 'adam',
'password' => 'something'
);
// config.php
class Config {
protected $config;
protected static $instance = null;
protected function __construct() {
$this->config = require('production_config.php');
}
protected function getKey($key) {
if(isset($this->config[$key])) {
return $this->config[$key];
}
// Possibly throw exception or something else if not found
}
public static function get($key) {
if(self::$instance === null) {
self::$instance = new self();
}
return self::$instance->getKey($key);
}
}
So you can go:
$user = Config::get('user');
This is perhaps easier than passing your config into every class, but it's more difficult to substitute your config for testing purposes.
I have spent a day trying debugging and trying to understand what is going wrong but...
So here is my code:
<?php
namespace RememberCalories\Rest;
interface MyJsonResponseInterface
{
public function getResponse();
}
And here is the class which I want to inject:
<?php
namespace RememberCalories\Rest;
class MyJsonResponse implements MyJsonResponseInterface
{
protected $success;
protected $responseCode;
protected $responseMsg;
protected $data;
public function __construct($data, $responseCode=0, $responseMsg='')
{
$this->data = $data;
$this->responseCode = $responseCode;
$this->responseMsg = $responseMsg;
if ( $this->responseCode === 0 ) {
$this->success = true;
}
}
...
Binding:
\App::bind('MyJsonResponseInterface', function($app, $parameters) {
$obj = new \RememberCalories\Rest\MyJsonResponse(null);
// var_dump($obj);
// die();
return $obj;
});
And at last the controller:
<?php
use \RememberCalories\MainMenu\MainMenu;
use \RememberCalories\Repository\TargetEloquentRepository as TargetRepository;
use \RememberCalories\Rest\MyJsonResponseInterface;
//use \RememberCalories\Rest\MyJsonResponse;
class BaseController extends Controller
{
protected $viewVars;
protected $mainMenu;
//Dependency injection classes
protected $target;
protected $myJsonResponse;
public function __construct(TargetRepository $target, MyJsonResponseInterface $myJsonResponse )
{
$this->beforeFilter('accessFilter');
$this->target = $target;
//$this->myJsonResponse = $myJsonResponse;
$this->mainMenu = (new MainMenu())->build();
$this->prepareViewVariables();
}
So the problem is with the second parameter of BaseController: MyJsonResponseInterface. The first is injected without problems but this one I get an error:
Illuminate \ Container \ BindingResolutionException
Target [RememberCalories\Rest\MyJsonResponseInterface] is not
instantiable.
It seems that the Closure in \App::bind('MyJsonResponseInterface' ...) is not called.
I have moved it to service provider with the same result.
But at the same if to call manually \App::make('MyJsonResponseInterface') everything is created ideally.
Please advise what way to investigate.
You need to App::bind the full namespace - so in your example, App::bind('RememberCalories\Rest\MyJsonResponseInterface').
I try to extend the CheckfrontAPI class with my new class.
In my case I use the Singleton pattern in order to load only one instance at a time of my class and I get that error
Fatal error: Declaration of CheckFrontIntegrator::store() must be compatible with that of CheckfrontAPI::store() in /home/my_web_site/public_html/wp-content/plugins/checkfront/class/Checkfront_Integration.php on line 83
Any idea on how to solve that issue ?
Here is the CheckfrontAPI source code : https://github.com/Checkfront/PHP-SDK/blob/master/lib/CheckfrontAPI.php
And here is my class that extends that class:
<?php
class CheckFrontIntegrator extends CheckfrontAPI
{
private static $instance = null;
public $tmp_file = '.checkfront_oauth';
final protected function store($data = array())
{
$tmp_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR. $this->tmp_file;
if(count($data))
{
file_put_contents(
$tmp_file,
json_encode(
$data,
true
)
);
}
elseif(is_file($tmp_file))
{
$data = json_decode(
trim(
file_get_contents(
$tmp_file
)
),
true
);
}
return $data;
}
public function session($session_id, $data = array())
{
$_SESSION['checkfront']['session_id'] = $session_id;
}
public static function instance($data)
{
if(!isset(self::$instance))
{
self::$instance = new CheckFrontIntegrator($data);
}
return self::$instance;
}
public function __construct($data)
{
if(session_id() == '')
{
session_start();
}
parent::__construct($data, session_id());
}
}
?>
And I initiate the new instance of that class like that:
$this->checkfront_integrator = CheckFrontIntegrator::instance($args);
where args are all the important information needit by the class to initiate a new object
AFTER EDIT
I have change my method store from:
final protected function store($data = array())
....
to
protected function store($data)
....
and the problem still occure :(
CheckfrontAPI is an abstract class? in this case your CheckFrontIntegrator::store() arguments count must be identical to original declaration
EDIT
I see on github
abstract protected function store($data);
your override must be:
protected function store($data) {
}
You are extending CheckfrontAPI. CheckfrontAPI has a method store(). If you override that method you must do it properly.
Post the code of CheckfrontAPI and your class Checkfront_Integration: when can understand what's the problem.
When you want to extent the functionality of an existing class by writing your own class and the class you are extending is is an abstract one, you'll need to make sure that the function calls are compatible.
What does this mean?
If the class you are extending has this function call for example :
function walk($direction, $speed = null);
Then you will have to honor the function signature in your implementation - that means you'll still have to have to pass two function arguments in your version.
You will not be able to alter is to be like this :
function walk($direction, $speed, $clothing);
OK. here is what I'm trying to do:
class Image{
public $_image;
public $_extension;
public $_mime;
public $_size;
public $_location;
public $_description;
public function __construct($image, $location){
$this->_image = $image;
$this->_location = $location;
$this->_extension = getExtension();
$this->_mime = getMime();
$this->_size = getSize();
}
private functions fallow.....
}
But I keep getting an internal server error when I try to run it. When I comment out the method calls it works. So the question is can I call methods from inside the constructor or am I doing something wrong with the methods.
Do your functions getExtension, getMime and getSize exist? Are they methods on this class? If they are methods, they need to be called with $this->... as in
$this->_extension = $this->getExtension();
If they are not methods, and are functions, you need to make sure the files that contain/define them are loaded before you run the constructor.
Well ..this fragment of code will work as expected:
class Foo
{
protected $secret = null;
public function __construct( $data )
{
$this->secret = $this->makeSecret($data);
}
public function makeSecret( $data )
{
return md5( $data );
}
}
$bar = new Foo( 'lorem ipsum' );
That is not a problem.
But you should know, that is considered to be a bad practice - to do computation/work in the constructor. It makes that class practically untestable. Instead, if you need to perform some computation before "releasing" the object to the rest of the code, you should use a factory. Something along the lines of :
class ImageFactory
{
public function build($image, $location)
{
$instance = new Image($image, $location);
$instance->prepare();
return $instance;
}
}
The class would need some changes:
class Image
{
protected $_image; // you were leaking abstraction
protected $_extension;
protected $_mime;
protected $_size;
protected $_location;
protected $_description;
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
}
public function prepare()
{
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
private functions fallow.....
}
Now when you need to create new object you do:
$factory = new ImageFactory;
$image = $factory->build( $file, '/uploads/' );
Of course the instance of ImageFactory can be reusable, and if all your images use the same $location, then you would pass that variable to factory at the initialization. And the factory would be able to "remember it" and pass to all the images it creates:
$factory = new ImageFactory('/uploads/');
$img1 = $factory->build( $file );
$img2 = $factory->build( $something_else );
This is actually how one should deal with creating multiple objects, which all need access to same DB connection instance.
Yes, you can call methods from within the constructor. Remember that the __construct() magic method was implemented in PHP 5. Prior to that, you created a function named the same as your class which acted as your constructor so depending on your PHP version, that could be a problem.
Additionally, the function calls you are making, are they in the class or external? If they are inside the class you need to call them this way:
$this->_extension = $this->getExtension();
You didnt specified what error you are expiriencing clearly. But try calling you class methods even inside the class using this keyword, otherwise it would not work:
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
Would be a better idea to post your code for the methods you wrote. There could be something wrong within them as well. Possibly forgetting to return a result or something...
I'm working through Practical Web 2.0 Appications currently and have hit a bit of a roadblock. I'm trying to get PHP, MySQL, Apache, Smarty and the Zend Framework all working correctly so I can begin to build the application. I have gotten the bootstrap file for Zend working, shown here:
<?php
require_once('Zend/Loader.php');
Zend_Loader::registerAutoload();
// load the application configuration
$config = new Zend_Config_Ini('../settings.ini', 'development');
Zend_Registry::set('config', $config);
// create the application logger
$logger = new Zend_Log(new Zend_Log_Writer_Stream($config->logging->file));
Zend_Registry::set('logger', $logger);
// connect to the database
$params = array('host' => $config->database->hostname,
'username' => $config->database->username,
'password' => $config->database->password,
'dbname' => $config->database->database);
$db = Zend_Db::factory($config->database->type, $params);
Zend_Registry::set('db', $db);
// handle the user request
$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory($config->paths->base .
'/include/Controllers');
// setup the view renderer
$vr = new Zend_Controller_Action_Helper_ViewRenderer();
$vr->setView(new Templater());
$vr->setViewSuffix('tpl');
Zend_Controller_Action_HelperBroker::addHelper($vr);
$controller->dispatch();
?>
This calls the IndexController. The error comes with the use of this Templater.php to implement Smarty with Zend:
<?php
class Templater extends Zend_View_Abstract
{
protected $_path;
protected $_engine;
public function __construct()
{
$config = Zend_Registry::get('config');
require_once('Smarty/Smarty.class.php');
$this->_engine = new Smarty();
$this->_engine->template_dir = $config->paths->templates;
$this->_engine->compile_dir = sprintf('%s/tmp/templates_c',
$config->paths->data);
$this->_engine->plugins_dir = array($config->paths->base .
'/include/Templater/plugins',
'plugins');
}
public function getEngine()
{
return $this->_engine;
}
public function __set($key, $val)
{
$this->_engine->assign($key, $val);
}
public function __get($key)
{
return $this->_engine->get_template_vars($key);
}
public function __isset($key)
{
return $this->_engine->get_template_vars($key) !== null;
}
public function __unset($key)
{
$this->_engine->clear_assign($key);
}
public function assign($spec, $value = null)
{
if (is_array($spec)) {
$this->_engine->assign($spec);
return;
}
$this->_engine->assign($spec, $value);
}
public function clearVars()
{
$this->_engine->clear_all_assign();
}
public function render($name)
{
return $this->_engine->fetch(strtolower($name));
}
public function _run()
{ }
}
?>
The error I am getting when I load the page is this:
Fatal error: Call to a member function fetch() on a non-object in /var/www/phpweb20/include/Templater.php on line 60
I understand it doesn't see $name as an object, but I don't know how to go about fixing this. Isn't the controller supposed to refer to the index.tpl? I haven't been able to discover what the $name variable represents and how to fix this to get the foundation working.
Any help you have is much appreciated!
The problem isn't with the $name variable but rather with the $_engine variable. It's currently empty. You need to verify that the path specification to Smarty.class.php is correct.
You might try this to begin your debugging:
$this->_engine = new Smarty();
print_r($this->_engine);
If it turns out that $_engine is correct at that stage then verify that it is still correctly populated within the render() function.
Zend has an example of creating a templating system which implements the Zend_View_Interface here: http://framework.zend.com/manual/en/zend.view.scripts.html#zend.view.scripts.templates.interface
That might save you some time from trying to debug a custom solution.
removing the __construct method, from the class, solved the similar issue I was facing.
Renaming __construct() to Tempater() worked for me.