I have a little issue with Zend. I am trying to make some fake HTTP requests and after the module->controller->action is executed, to return random variables that are set in that action. Like in the case of variables set with view->assign(,) - I can access them later from view file (.phtml).
Here is part of my code:
/application/controller/IndexController.php
<?php
class IndexController extends Zend_Controller_Action
{
public function init(){}
public function indexAction()
{
#$this->view seems to be a NULL value from the fake request, so I can't pass variables to it
//$this->view->x = 'y';
echo 'test';
return array(
'x' => 'y'
);
}
}
/public/index2.php
<?php
//--cut--
$application->bootstrap();
$options = array(
'action' => 'index',
'controller' => 'index',
'module' => 'default'
);
if( isset($options['action'], $options['module'], $options['controller']) )
{
$request = new Zend_Controller_Request_Http ();
$request->setModuleName($options['module'])->setActionName($options['action'])->setControllerName($options['controller']);
$frontController = Zend_Controller_Front::getInstance ()->returnResponse ( true );
$response = new Zend_Controller_Response_Http ();
$frontController->getDispatcher ()->dispatch ( $request, $response );
echo '$response:<b>Zend_Controller_Response_Http</b><br>';
//This returns me the body of what I echo in the indexAction but not the variables.
var_dump($response);
}
Thank you so much!
If you want to assign variables to view when the request is dispatched, you can create a Zend_View instance in the indexAction and assign values, as shown below:
public function indexAction()
{
echo "test";
$view = new Zend_View();
$view->setBasePath(APPLICATION_PATH."/views/");
$view->x = 'y';
echo $view->render("index/index.phtml");
}
Try embedding the variable in the index view script, and var_dump of your response will contain both the echoed "test" and the index.phtml output.
If you want to return the array to the response, use json:
public function indexAction()
{
$array = array('x' => 'y');
echo json_encode($array);
}
index2.php:
//.....
var_dump($response);
var_dump(json_decode($response->getBody()));
You need to extend Zend_Controller_Action for this because standard Action didn't receive returned variables.
You will not be able to fetch the view parameters in the way you are trying (outside the view/MVC). This is because the action controller, in your case, IndexController only exists in memory for the duration of the dispatched method (IndexAction).
The parameters that are defined with the view class are only referenced when a call is made to view->render() and then just the HTML is generated.
You can however get the user defined variables (public scope) from within the controller action like so:
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
/** The view class uses PHP's magic set/get/isset methods to
NULL any access to values that are protected (or more precisely
have a underscore _ prefix for the property name)
hence $this->_view = null;
**/
/** we do however have access to this view (or create a new one if not already
defined within the action controller) with: **/
$view = $this->initView();
/** An array of the public controller parameters **/
$viewParams = $view->getVars();
}
}
Also, the code dispatching theses requests can now be simplified:
$front = Zend_Controller_Front::getInstance();
$front->setRequest(
new Zend_Controller_Request_Http()->setParams($options)
)->dispatch();
Note: I'm using version 1.11
Related
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();
I'm creating my own framework. It works like this
localhost/controller/action/firstVariable/second/third (And so on...)
My bootstrap look like this:
$request = Util::getInput('request');
$requestList = explode("/",$request);
$modelName = #ucwords($requestList[0]);
$action = #$requestList[1];
$parameters = array_slice($requestList,2);
$controllerName = $modelName.'s'.'Controller';
I'm getting parameters from an url and save them in a variable $parameters. I would like to send them to the current action in my controller the way Laravel 5 is doing.
Example, in Laravel I specify parameters in the url and thats it.
To call them, I need to do a simple step. Just define them:
public function firstAction($first,$second){
}
When I go to an url like:
localhost/Main/firstAction/first/second/
Function of action 'firstAction' will catch those 2 parameters and then basically I can call them inside of the controller and send it to view.
My extends Controller class:
class Controller{
public function __construct($model,$action){
$modelClass = new main();
$reflection = new ReflectionClass($model.'sController');
$reflection->hasMethod($action) ? $this->$action() : die ('Base Controller call error: Method '. $action .' does not exist in Controller '. $model.'sController');
}
public static function renderView($action,$model,$data){
$model = str_replace('sController','',$model);
//include '../application/views/'.$model.'/'.$action.'.php';
$loader = new Twig_Loader_Filesystem('../application/views/'.$model);
$twig = new Twig_Environment($loader);
echo $twig->render($action.'.php', $data);
}
}
class MainsController extends Controller {
private $_data = array();
public function __construct($model,$action){
parent::__construct($model,$action);
}
public function firstAction($first,$second){
echo 'Hoi';
}
}
How can I do it, the good way? I can of course send the variable $parameter to MainController and than call
$this->_data inside of my action but It is not efficient.
I think I need to use arrays to do it, but I have no idea how.
Thank you.
Check out http://php.net/manual/en/function.call-user-func-array.php
P.S.
You do not have to use reflection in order to check if method on that object's instance exist. Single function call can be enough. Check out http://php.net/manual/en/function.is-callable.php
It would be nice if you would use more descriptive names. Now they are confusing.
Is there any simple solution to pass $this->data variable from parent controller to all children controllers.
All what I try bring empty array to child.
When I change the file "system/engine/controller.php" like below:
protected function getChild($child, $args = array()) {
$action = new Action($child, $args);
if (file_exists($action->getFile())) {
require_once($action->getFile());
$class = $action->getClass();
$controller = new $class($this->registry);
//$controller->{$action->getMethod()}($action->getArgs());
$controller->{$action->getMethod()}($action->getArgs()+array('parent-data'=>$this->data));
return $controller->output;
} else {
trigger_error('Error: Could not load controller ' . $child . '!');
exit();
}
}
Then I try to read the variable 'parent-data' from the passed arguments in the child controller:
if (isset($setting['parent-data'])) {
echo "<pre>".print_R($setting['parent-data'],true)."</pre>";
}
As a result I get an empty array:
Array
(
[modules] => Array
(
)
)
The data variable is empty. Thats why it prints blank array.
Also there is no need to pass the data varaiable.Its a global one and you will get it till upto the .tpl files.
The issue was in the parent that was the column controller with empty data variable rather the main page parent controller that has had necessary data variable.
on opencart2 and maybe older:
ex: parse ver (steps) from parent controller product-custom to child controller header
parent
<?php
class ControllerProductCustom extends Controller {
private $error = array();
public function index() {
$this->load->language('product/product');
...
$data['footer'] = $this->load->controller('common/footer');
$data['header'] = $this->load->controller('common/header', array('steps' => true));
child
<?php
class ControllerCommonHeader extends Controller {
public function index($arg) {
$data['title'] = $this->document->getTitle();
if (isset($arg['steps'])) $data['steps'] = $arg['steps'];
...
I have a situation where I'm creating a controller file that echo's json output to be used client-side with ajax:
echo json_encode($response);
The main class in another file, among other things, grabs all the setting vars from the CMS.
Now, the controller file has a class that generates the request from the API, but the setting vars in the class (username, id, count, etc.) are hard coded because I can't figure out how exactly to get them from the main class in the other file. With the settings hard coded, the controller file creates and echos the json output as expected. It just needs the dynamic vars from the main class.
Please excuse my lack of knowledge and usage of OOP. I've been trying it with a structure like this, where again, just trying to get the username and other vars from the main class into another class within separate file.
** EDIT ** Rethinking this a bit based on the comment by #Dave Just as it makes better sense. So if I move the api_request function into mainClass and return the response, I can get the variables I need and the request still works. So that would lead me to ask - how can I still echo the $response from the api_request function in a separate file? That separate file with the json is what I'm using for my ajax script.
class mainClass {
public $username;
function __construct() {
...
}
public function api_settings( $username ) {
...
}
}
$main_class = new mainClass;
$main_class->api_settings();
// OR
$main_class->username;
api-call.php
class apiCall extends mainClass {
public $username;
function __construct() {
parent::__construct;
...
}
public function api_request() {
...
$return = $api_auth->request(
'GET',
$api_auth->url( '/cms-plug' ),
array(
//where I need to be able to grab the $username from the main class
'username' => 'joebob'
)
);
echo json_encode($response);
}
}
$api_class = new apiCall;
Since you asked me to point out this,
There are so many flaws in your architecture
First,
When you do it like,
class apiCall extends mainClass {
you break the Single Responsibility Principle and Liskov Substitution principle at the same time
Second,
A controller should never echo anything
MVC itself looks like
$modelLayer = new ModelLayer();
$view = new View($modelLayer);
$controller = new Controller($modelLayer);
$controller->indexAction($request);
echo $view->render();
You actually implementing something that is close to Model-View-Presenter, not MVC
Third
Since your class starts from api.. then there's no need to include that name in methods.
Fourth,
You don't have to tightly couple json_encode() with generation logic. That method should only return an array, then you'd json_encode() that array. Benefits? 1) Separation of Concerns 2) You can event convert that array to YAML or XML, not only JSON
And also, you should avoid inheritance in your case. Write singular class that deals with ApiCalls. So, it would look like as,
final class ApiCall
{
/**
* I'd use a name that makes sense
*
* #param string $username
* #return array on success, FALSE on failure
*/
public function fetchByUsername($username)
{
$return = $api_auth->request(
'GET',
$api_auth->url( '/cms-plug' ),
array('username' => $username)
);
if ($response !== false){
return $response;
} else {
return false;
}
}
}
And you would use it like,
if (isset($_GET['username'])){
$api = new ApiCall();
$result = $api->fetchByUsername($_GET['username']);
if ($result !== false){
// Respond as JSON
die(json_encode($result));
} else {
die('Wrong username');
}
}
You can access properties from the current object with this. This also works for inherited properties from parent classes.
api-call.php
class apiCall extends mainClass {
//public $username; // you don't have to decalre $username again, it gets already inherited from mainClass since its public there
function __construct() {
parent::__construct;
...
}
public function api_request() {
...
$return = $api_auth->request(
'GET',
$api_auth->url( '/cms-plug' ),
array(
//where I need to be able to grab the $username from the main class
'username' => this->username // vars of the current object and inherited vars are available with "this"
)
);
echo json_encode($response);
}
}
$api_class = new apiCall;
Basically everything works if I hard code the URL in my Ajax_Controller, but I want it to access the URL from the CMS field I created.
Thanks in advance. (please ignore when I don't close my braces - just trying to copy / paste efficiently)
In /mysite/_config.php I created a custom config:
Object::add_extension('SiteConfig', 'CustomSiteConfig');
In /mysite/code/CustomSiteConfig.php I added a field where I'll store a URL:
class CustomSiteConfig extends DataObjectDecorator {
function extraStatics() {
return array(
'db' => array(
'COJsonPath' => 'Text'
)
);
}
public function updateCMSFields(FieldSet &$fields) {
$fields->addFieldToTab("Root.CO", new TextField("COJsonPath", "CO JSON URL"));
}
public function getCOJsonPath(){
return $SiteConfig.COJsonPath;
}
This successfully creates a tab in the main parent in the CMS called "CO" and a field named "CO JSON URL". I logged into my CMS and saved http://api.localhost/mymethod/ to that field.
Now I have created an Ajax page type to facilitate running Ajax commands without letting the web site user find where my APIs are, and because jQuery Ajax no likey XSS (cross site scripting).
In /mysite/code/Ajax.php:
class Ajax extends Page {
static $db = array(
);
static $has_one = array(
);
function getCMSFields()
{
$fields = parent::getCMSFields();
return $fields;
}
}
class Ajax_Controller extends Page_Controller {
public function getCO()
{
$buffer = self::createHttpRequest("http://api.localhost/mymethod/");
//$buffer = self::createHttpRequest($CustomSiteConfig::getCOJsonPath());
return $buffer;
}
This code works, but when I try to execute my createHttpRequest() with the line you see commented out, it fails. I know my syntax is wrong, I just can't figure out what it should be. Thanks for helping - I've done this before I just can't figure it out - its Friday.
I spotted several syntax errors in your code:
public function getCOJsonPath(){
return $SiteConfig.COJsonPath;
}
should be:
public function getCOJsonPath(){
return $this->owner->COJsonPath;
}
1) $SiteConfig is never defined at that point.
2) usually you would use $this, but in your case you are inside a DataObjectDecorator, so you have to use $this->owner
3) you can not use . to access properties of an object, in php you have to use ->
moving on to class Ajax_Controller, inside getCO there are the following errors:
1) $CustomSiteConfig is not defined, therefore can not be used
2) getCOJsonPath is not a static function, but you try to call it as static (again you have to use ->
so, the code should look something like this:
public function getCO() {
$siteConfig = SiteConfig::current_site_config();
$buffer = self::createHttpRequest($siteConfig->getCOJsonPath());
return $buffer;
}
that should work, but there is another think that could be improved.
As I understand it, you are creating an ajax page, which you then create once in the CMS and tell your website content authors never to touch the ajax page?
This is quiet ugly, and there are several nice ways to do what you want to do.
Here is how I would create an Ajax controller:
_config.php
// tell SilverStripe what URL your AjaxController should have,
// here we set it to AjaxController::$URLSegment which is 'myAjaxController'
// so the url to the controller is mysite.com/myAjaxController
Director::addRules(100, array(
AjaxController::$URLSegment => 'AjaxController',
));
AjaxController.php
<?php
class EventAssetsController extends Controller {
public static $URLSegment = 'myAjaxController';
// tell SilverStripe what URL should call what function (action)
// for example, mysite.com/myAjaxController/foo should call the function foo
public static $url_handlers = array(
'foo' => 'foo',
'bar/$ID/$OtherID' => 'bar',
'co' => 'getCO'
);
public function Link($action = null) {
// this function is just a helper, in case you evern need $this->Link()
return Controller::join_links(self::$URLSegment, $action);
}
public function AbsoluteLink($action = null) {
return Director::absoluteURL($this->Link($action));
}
public function foo(SS_HTTPRequest $request) {
// do something here
// this method is an action, the url to this action is:
// mysite.com/myAjaxController/foo
}
public function bar(SS_HTTPRequest $request) {
// do something here
// this method is an action, the url to this action is:
// mysite.com/myAjaxController/bar
// you notice that the $url_handlers has "bar/$ID/$OtherID",
// that means you cann call mysite.com/myAjaxController/bar/21/42
// and silverstripe will make 21 the ID, and 42 the OtherID
// you can access ID and OtherID like this:
// $ID = $request->param('ID'); // which is 21
// $OtherID = $request->param('OtherID'); // which is 42
}
public function getCO() {
// this method is an action, the url to this action is:
// mysite.com/myAjaxController/co
$siteConfig = SiteConfig::current_site_config();
$buffer = self::createHttpRequest($siteConfig->getCOJsonPath());
return $buffer;
}
}