Situation
In this web app I am building there is a "bootstrap" sequence that defines (through constants) and initiates an extended controller. Currently, the controller keeps track of assets (script files, css, etc.) that will be deployed at the later render stage through a series of static variables. I will simplify the code here, think of it as pseudo-PHP.
/* CONTROLLER CLASS */
class Controller {
protected static $aryScriptFiles = array();
public function __construct() {
/* Behaviour */
/* Some logic that identifies/calls Home_Controller method Index */
}
public static function Add_Script($strFileName) {
static::$aryScriptFiles[] = $strFileName;
}
}
/* HOME_CONTROLLER CLASS */
class Home_Controller extends Controller {
protected static $aryScriptFiles = array('default', 'carousel', 'etc');
protected function Index() {
/* Behaviour */
/* Load the view as an include. It is "part" of the User_Controller */
}
}
/* EXAMPLE_HELPER */
class Example_Helper {
public static function Test() {
/* THE NEXT LINE IS IMPORTANT FOR THE QUESTION */
$objController = CONTROLLER;
$objController::Add_Script('dominoes');
}
}
/* INDEX VIEW FILE */
<h1>Welcome!</h1>
<?php
echo get_class(); <-- Would echo 'User_Controller'
Example_Helper::Test();
/* Simplification of render process */
foreach(static::$aryScriptFiles as $strFileName) {
/* Render the HTML script tag */
}
?>
Flow
Ok, given the above there is a bootstrap that ends up calling User_Controller. For examples sake, I have simply defined them to let you know what state the script will follow.
$strControllerName = 'User_Controller';
define('CONTROLLER', $strControllerName);
$objController = new $strControllerName();
What you end up with is the aryScriptFiles array having 4 entries and this works great.
Problem
Before reading on, please note I do not want to use magic methods, globals or have to pass a reference of the controller name to the Helper function.
I would like to try and remove the line in the helper file that pulls the current controller name to a variable from the constant.
$objController = CONTROLLER; <-- I want this to shoo shoo
If I were to just try and use the following, the script file that gets added by aid of the Helper is part of the original Controller array as opposed to the Home controller.
Controller::Add_Script('dominoes'); <-- Will not be part of the Home_Controller array
Question
Please can I have some opinions from the SO community on what you feel the best approach to tackle this would be taking in to account that the controller name will differ? My primary objectives in this exercise are:
Keep the View file VERY simple
Keep the Helper files simple.
Avoid the need to add any code more than necessary to the Home_Controller
I'm currently thinking that one of the best options would be to host the "assets" within a seperate class, just want to know whether it is possible.
Thanks for reading.
First of all, think about your seperation of concerns. Should it really be the responsibility of a controller to manage assets?. Why did you made the method for adding assets static in the first place?
I do not want to use magic methods, globals or have to pass a reference of the controller name to the Helper function.
What are you expecting? If you try to force a class to depend on another class in a completely different scope and context your only option is to use ugly hacks to make your object globally accessible.
Dependency Injection to the rescue
Why should your helper know about what controller and how the controller is treated from the outside?
The only thing your helper should do is to operate with the controller (in your case). It should not try to magically detect what controller is being used. It should just take a controller and operate with it.
class Example_Helper {
public static function Test($controller) {
$controller::Add_Script('dominoes');
}
}
Example_Helper::Test($objController);
Since the addScript() method and the $aryScriptFiles property is static anyways, you could also just call the method in the helper on the parent controller. It would make no difference.
Also why do you want to talk to your controller from the view? The view should be "dumb" it should not be able to hold and operate with data except those that were passed to it by the controller.
Wouldn't it make more sense to add functionality to your controller or one of it's services that passes the required assets to your view, instead of forcing the view to get it's data from from the controller by itself?
I think there are a few logical flaws in your code here. Especially your usage of static properties and methods. If you could clarify that a bit I could go in detail a bit.
Apart from architectural concerns (assets should indeed be managed by a separate AssetManager) your problem can be relatively easily solved because of PHP's rather peculiar own architecture, specifically exposed through methods like get_called_class. This allows you to write code like this:
$assets = []; // Global for brevity of example
class Base {
static function addScript($script)
{
global $assets;
$myName = get_called_class();
$assets[$myName][] = $script;
}
}
class Derived extends Base {
public function __construct()
{
self::addScript('test');
}
}
$foo = new Derived();
var_dump($assets);
Which will then output the following:
array(1) {
["Derived"]=>
array(1) {
[0]=>
string(4) "test"
}
}
Note that using get_class instead of get_called_class would here show the array's name as Base instead of Derived, while Derived is what you need. This way you can embed helper functions in Controller, which automatically derive the class name and forward it to the central asset manager.
Related
I've been trying to learn CakePhp for a while now but I still can't get alot of stuff. I've been reading a lot and watching videos. I just want to ask a very simple question.
I've been trying to mess with the bookmarks tutorial and i'm watching a video. In the video he baked a component called Validate. In the cmd he typed
bin/cake/bake component Validate
Then a ValidateComponent.php appeared in the component folder in the controller. Now he used the ValidateComponent.php by going to the BookmarksController and adding to the initialize method
$this->loadComponent('Validate');
I just want to ask where did the word validate come from? shouldn't it be ValidateComponent? and where does he get the loadComponent from? I've seen him using $this->method(); or $this->method('string', [array]); I just want to know how the syntax works and what each word means. Sorry for the long explanation. I'm really trying to learn and i'm really confused. thank you very much.
ValidateComponent.php
<?php
namespace App\Controller\Component;
use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
/**
* Validate component
*/
class ValidateComponent extends Component
{
/**
* Default configuration.
*
* #var array
*/
protected $_defaultConfig = [];
public function validLimit($limit, $ default)
{
if (is_numeric($limit)){
return $limit;
}
return $default;
}
}
part of BookmarksController.php
public function initialize()
{
parent::initialize();
$this->loadComponent('Validate');
}
I can't seem to find where he got the word 'Validate'
Every controller in your application extends a base Controller Controller or AppController which extends Controller
Controller have bunch of methods, One of these methods is the loadComponent() (See Source)
public function loadComponent($name, array $config = [])
{
list(, $prop) = pluginSplit($name);
$this->{$prop} = $this->components()->load($name, $config);
}
Why Validate instead of ValidateComponent?
Short answer: suffix.
See predefined suffix in App class
CakePHP uses suffix to load classes, When you hit loadComponent() You go to ComponentRegistery class to Register the component, ComponentRegistery will call App class to load class. __loadClass()
Almost everything in CakePHP has a suffix, In your case ValidateComponent has the Component suffix.
return App::className($class, 'Controller/Component', 'Component'); (Source)
I hope this will make more sense to you
$this isn't specifically anything to-do with cake but part of PHP itself. In object oriented context $this refers simply to the current class.
$this->something refers to an object within the current scope. This could be within the current class or from an extends or use.
$this->something(); refers similarly to a method or function within the current scope.
If are using an IDE such as netbeans you can usually click through these references to see the object they refer to so for example if you do in fact use Netbeans you could ctrl-click on $this->loadComponent('Validate'); to see the actual function it refers to.
Regarding where does 'Validate' come from, it's a string you are passing to that object. On the other end it will be used in the function, probably in a switch or if statement to return something.
Eg:
Public function loadComponent($type){
If($type == 'Validation'){
//do something
}
}
I'll try to explain my situation as clear as I can.
I'm building a framework. Its system contains following directories:
1) Core - routing, main class (autoloading, configs, constants), requests
2) Helpers
3) Libraries
In index.php, I set all most important configuration variables such as the base url, or index file. I set them inside of the "main class".
These attributes are private, because I dont want anyone to change them during the request flow.
So the only point of access to these variables is from the "main class" methods.
Now, one of these private variables is $_base_url.
I want users to be able to access base_url from some better place however, like URL class (inside of the helper directory).
To achieve this, I have to create two methods - get_base_url() inside of the "main class", and get_base_url() inside of the url class, which will do something like this:
class Url {
public static function get_base_url() {
return Main_Class::get_base_url();
}
}
My problem is - I have a couple of such variables which I'd like to call from different classes.
I will have a lot of repeated code.
The only solution I see is just setting these variables in different classes directly, but I'd like to have them centralized inside the main class.
How should I handle this?
You can create another class called variables (or whatever you prefer) then use static variables with that clase to be able to call them back and forth. Something like this:
<?php class Variables {
static $_base_url = "http://base_url";
} ?>
That way you can call your variables like this:
Variables::$_base_url;
To expand on your comment:
With inheritance you can have a situation where 2 classes have different values for the same $variable. In this example all instances will hold a different value for $_base_url;
class ParentClass {
protected $_base_url = "SOME VALUE";
}
class SubClassA extends ParentClass {
protected __construct() {
$this->_base_url = 'VALUE X';
}
}
class SubClassB extends ParentClass {
protected __construct() {
$this->_base_url = 'DIFFERENT VALUE THAN SubClassA';
}
}
Again, it depends on what you are trying to accomplish. If you want your variables act more like Settings or if you just want to have those variables within your instance.
Why you need a variable to be called from the classname, if the classes extends your MainClass then use parent::get_base_url();
I was too sleepy when I asked the question, so sorry for that, anyway to make things clear I prepared the question for 2 hours.
I'm trying to organize my code and decided to organize it mvc'ish(mvc-like), I don't know if I can follow all the principles, but I wanted to be at least close to that.
My application has a front-controller (dunno if my definition is right), so that all the http-request of my application will be passing through a single point, in my case the index.php in the root directory of my application.
Having said that I have set it up like that, you can imagine that I used .htaccess to direct all request to index.php.
I exploded the url and created an array out of it, $url[] like so. So whenever I access my app like this http://localhost/app/pagename it'll be accessing a controller (pagename_controller)
I did it like this :
$file = $controller_path . $page . '_controller.php';
if (file_exists($file)) {
require $file;
$class_name = ucfirst($page) . '_controller';
$target = new $class_name();
}
also I wrap it up in a Container, the 'decorator pattern', for future use, validations maybe.
like this :
$controller = new Wrap($target);
$controller->index();
I don't know if the use of $controller variable name is appropriate so please forgive me when it is all wrong.
I kinda think that I can setup my application like this :
user sends a request, how? by using the application means that he/she sends out a http-request, that will load the initial state of the application
As you can see in the diagram of my desired application structure, I was able to do only the first part which is to direct the request to a single entry (index.php)
Now the problems are the initialization of other parts of the application.
As of this moment, I have 3 files that I want to setup, but I am confused on how.
index_controller, index_view, Template
class Index_controller {
private $model;
private $view;
public function __construct(){
// optional model -> $this->model = 'index'
$this->view = 'index' //
}
public function index(){
$this->load->view($this->view)
}
}
class Index_view {
private $model;
private $template;
public function __construct(Model $model = null){
$this->template = new Template('default');
}
public function view() {
$this->template->assign('css', 'default_css'); // don't know if this is efficient
// or $this->template->assign('header', 'default_header');
// or $this->template->assign('sidebar', 'default_sidebar');
// or $this->template->assign('footer', 'default_footer');
// or any other things I want to use in the template
}
}
class Template {
public $data = array();
private $tmpl;
public function __construct($template) {
$this->tmpl = $template . '_tmpl.php';
}
public function assign($name, $value){
$this->data[$name] = $value;
}
// public function output
// function that will explode the data array and render it out as a webpage
// I'll create templates and
}
With that at hand, I want to know now how do I link those things together. At the moment I have a system folder that can contain classes, and I setup a autoloader for that folder.
I am thinking of creating a Controller class and View class that acts as the ActionFactory and ViewFactory as illustrated in the diagram, although I know that these are not their responsibilities.
I am thinking of this :
class Controller {
protected $load;
public function __construct() {
$this->load = new View();
}
}
class View {
public function __construct() {
// some things i don't know
}
public function view() {
// some things i don't know
}
}
What are your suggestions and comments in my setup. How can I initiate the triad?
Well, let's not get stuck on your implementation details too much. You can go read about securing your framework at some other time. Let's deal with your question...
I actually created a framework that works along the lines you are trying to implement. I think what you are missing is a RoutingHandler class. Routing is the physical manipulation of the URL, which tells your application which Controller to load, and which Action to run.
In my world I also have Modules, so the basic routing scheme is
Module -> Controller -> Action
These three items map to my URI scheme in that fashion. Variables can be appended also like so...
http://www.domain.com/module/controller/action/var1/val1/var2/val2
So, what happens after the URI is parsed, and control is passed over to the appropriate controller and action? Let's make some code up to demonstrate a simple example...
<?php
class indexController extends Controller {
protected function Initialize() {
$this->objHomeModel = new HomeModel;
$this->objHeader = new Header();
$this->objFooter = new Footer();
$this->objHeader
->SetPageId('home');
}
public function indexAction() {
$this->objHeader->SetPageTitle('This is my page title.');
}
}
?>
In the Initialize method, I'm setting some controller-wide stuff, and grabbing an instance of my Model to use later. The real meat is in the indexAction method. This is where you would set up stuff to use in your View. For example...
public function randomAction() {
$this->_CONTROL->Append($intSomeVar, 42);
}
_CONTROL is an array of values that I manipulate and pass onto the View. The Controller class knows how to find the right template for the View because it is named after the Action (and in a sibling directory).
The Controller parent class takes the name of the action method and parses it like so...
indexAction -> index.tpl.php
You can also do some other fun stuff here, for example...
Application::SetNoRender();
...would tell the Controller not to render inside a template, but just complete the method. This is useful for those situations where you don't actually want to output anything.
Lastly, all of the controllers, models, and views live inside their own directory like so...
my_module
controllers
indexController.class.php
someotherController.class.php
:
:
models
HomeModel.class.php
:
:
templates
index.tpl.php
someother.tpl.php
:
:
I could go on, but I'm writing this from memory, and there are some wrinkles here and there, but hopefully this gives you food for thought.
Currently I'm using a view-helper to help my debugging process. Basically I call this function and it checks if 1: I'm logged in as a developer by checking a Zend_Session_Namespace variable and 2: if the application is run in debug_mode using Zend_Registry. If both of them are true I show a number of different debug variables and any parameters I give the helper as input.
Originally this function was only intended to check that I got the right info in the objects assigned to the view, but I quickly discovered that it was useful in other places as well. At the moment the function works in controllers using $this->view, and I guess I could technically use something along new Zend_View(); or Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); to get a view-object in my models, but that is just plain ugly even if it's only for debugging.
So my question is: How can I rebuild this helper into a global function (usable in models, views and Controllers) and still be able to use the Zend_Session_Namespace and Zend_Registry objects, while (as far as possible) maintaining the MVC structure.
I think if you made a static class or a singleton class, you could have all of the desired functionality without breaking your MVC structure at all.
Consider the following simple class with one static function:
<?php
class My_DebugHelper
{
public static function dump()
{
$ns = new Zend_Session_Namespace('the_namespace'); // the namespace you refer to with the developer flag
$debug_mode = Zend_Registry::get('debug_mode');
if (!isset($ns->isDeveloper) || !$ns->isDeveloper || !$debug_mode) {
return;
}
foreach(func_get_args() as $arg) {
Zend_Debug::dump($arg);
}
}
protected function __construct() {}
protected function __clone() {}
}
This code gives you:
The ability to call from anywhere in your application (model, controller, helper, view etc)
All of the protections to prevent it from being executed when called out of context
A simple base that you can expand upon
Depending on your needs, at least one thing you could do is make it static so it could store some of the information rather than access it each call, or add additional methods or specialized parameters so you could pass a Zend_View object to it if necessary and have data injected into the view.
You could call it from anywhere in your application and pass one or more values to dump:
My_DebugHelper::dump($someVar, $this->view, $model->getCustId());
I'm not sure how/what your view helper displays data currently, but hopefully that will help you merge your view helper into the generic class that you can call anywhere.
Here is a simple view helper (notice the pass-by-reference argument):
class Zend_View_Helper_MyViewHelper extends Zend_View_Helper_Abstract
{
public function MyViewHelper(&$array)
{
unset($array['someExistingKey']);
}
}
This does not work in the view. $array['someExistingKey'] is still set (except within the immediate context of the method). Zend must be doing something to prevent the array from being passed in by reference. Any ideas on a solution?
When you call $this->MyViewHelper($array) from your templates you are not actually calling the helper class directly, Zend_View is instantiating the class and calling it for you. So I think you might have trouble getting this working. Your best bet is probably to use Zend_Registry, or refactor to take a different approach not requiring a global.
I just thought of a workaround. You just have to call the helper manually, instead of letting ZF call it through call_user_func_array.
Ref.php
class Zend_View_Helper_Ref extends Zend_View_Helper_Abstract
{
public function removeFromRef(&$ref)
{
// change your var value here
unset($ref['key']);
}
/**
* ZF calls this for us, but we'll call what we want, so you can skip this.
*/
// public function ref()
// {}
}
As you can see, you can skip the convention of having to name your main method as the filename, but I still recommend it.
Now, you can pass references in views/controllers:
// in view:
$this->getHelper('Ref')->removeFromRef($someVar2Change);
// in controller
$this->view->getHelper('Ref')->removeFromRef($someVar2Change);
Basically, this is what $this->ref() does: gets the helper, then calls call_user_func_array.
Some people may have problems using $this->getHelper('Ref')->ref() instead of $this->ref() though, but it works.