Jump to content

Archived

This topic is now archived and is closed to further replies.

cornofstarch

[SOLVED] Classes in OpenCart

Recommended Posts

Hello, I'm trying to figure out how the backend of OpenCart works as way to learn PHP classes.  Unfortunately, I'm stuck - very very stuck - and I'm hoping someone can handhold me through this little hurdle...

 

I'm looking at layout.php found in catalog/controller/common/.  layout.php makes the following call: $this->data['title'] = $this->document->title;  I understand the first part ($this->data['title'] = ) but I can't figure out what the data array is being set to.  I know it's the $title of the store which is pulled from index.php under "Settings" but how does the data array in Config get transferred to members in the Document class?  I think it has something to do with the Registry but I just can't seem to get it.  Would greatly appreciate some help!

Share this post


Link to post
Share on other sites

I haven't looked at the code in question, but zend_config from the zend framework provides an example of how this can be done easily.  With Zend_config you can create configuration files defining simple arrays, and the zend_config takes the arrays as input, and then allows you to access elements using the nested object element syntax.  If this is similar to what OpenCart is doing, this code should help explain how it can be done using the built in  __get() method.  If you have read about php 5 special methods, __get, if it exists for a class will be called if the accessor being specified does not exist in the class definition.  This allows you to call some code, and resolve the accessor dynamically.

 

  /**
     * Zend_Config provides a property based interface to
     * an array. The data are read-only unless $allowModifications
     * is set to true on construction.
     *
     * Zend_Config also implements Countable and Iterator to
     * facilitate easy access to the data.
     *
     * @param  array   $array
     * @param  boolean $allowModifications
     * @return void
     */
    public function __construct(array $array, $allowModifications = false)
    {
        $this->_allowModifications = (boolean) $allowModifications;
        $this->_loadedSection = null;
        $this->_index = 0;
        $this->_data = array();
        foreach ($array as $key => $value) {
            if (is_array($value)) {
                $this->_data[$key] = new self($value, $this->_allowModifications);
            } else {
                $this->_data[$key] = $value;
            }
        }
        $this->_count = count($this->_data);
    }

    /**
     * Retrieve a value and return $default if there is no element set.
     *
     * @param string $name
     * @param mixed $default
     * @return mixed
     */
    public function get($name, $default = null)
    {
        $result = $default;
        if (array_key_exists($name, $this->_data)) {
            $result = $this->_data[$name];
        }
        return $result;
    }

    /**
     * Magic function so that $obj->value will work.
     *
     * @param string $name
     * @return mixed
     */
    public function __get($name)
    {
        return $this->get($name);
    }

 

Hopefully you can understand what this code does.  If you have further questions about it, ask.

Share this post


Link to post
Share on other sites

Hello, thanks for the reply - much appreciated.  I read up on the stuff you suggested and I think I get it so I went back and tried following the code with the magic methods in mind. Unfortunately, I've hit another roadblock... I got to the end of one of the classes but that's it... somehow, the members seem to be automatically populated without any methods!

 

I hope it's ok if I post the relevant snippets of code...

 

index.php

// Config
$config = new Config();
Registry::set('config', $config);

// Database 
$db = new DB(DB_DRIVER, DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE, DB_PREFIX);
Registry::set('db', $db);

// Settings
$query = $db->query("SELECT * FROM setting");

foreach ($query->rows as $setting) {
$config->set($setting['key'], $setting['value']);
}

// Document
Registry::set('document', new Document());

 

controller.php

abstract class Controller {
protected $id;
protected $template;
protected $layout;
protected $children = array();
protected $data = array();
protected $output;

protected function __get($key) {
	return Registry::get($key);
}

protected function __set($key, $value) {
	Registry::set($key, $value);
}

 

registry.php

final class Registry {
static private $data = array();

static public function get($key) {
	return (isset(self::$data[$key]) ? self::$data[$key] : NULL);
}

static public function set($key, $value) {
	self::$data[$key] = $value;
}

 

layout.php

class ControllerCommonLayout extends Controller {
protected function index() {
	$this->data['title'] = $this->document->title;
	$this->data['description'] = $this->document->description;

 

layout.tpl

<title><?php echo $title; ?></title>
<?php if ($description) { ?>
<meta name="description" content="<?php echo $description; ?>" />
<?php } ?>

 

document.php

final class Document {
public $title;
public $description;
public $base;	
public $charset = 'utf-8';		
public $language = 'en-gb';	
public $direction = 'ltr';		
public $links = array();		
public $styles = array();
public $scripts = array();
public $breadcrumbs = array();
}

 

I followed index.php first.  Here, config is instantiated and dropped into the Registry.  It is then populated with settings from the database.  Document is also dropped into the Registry.

 

Then, in layout.php, we assign the key 'title' to the data array in Controller with a value of $this->document->title.  Since Controller doesn't have the document class, the __get() method is called which in turn calls the __get() method from the Registry.  The Registry __get() refers back to itself and looks for the Document class (which it finds) and pulls out the value from $title.  After that, we render the page (code not shown) and output to the template (layout.tpl).  $title and $description are replaced with their respective values from $data['title'] and $data['description'] that was defined in the Controller.

 

Here's where I'm stuck; how does the data from Config end up in the public members of Document?  ???  I've checked the other files available and there doesn't seem to be anything that does that.  I did see in language.php something that I was expecting for Document.

 

language.php

final class Language {
  	private $code;
  	private $languages = array();
private $data = array();

public function __construct() {
	$this->config  = Registry::get('config');
	$this->db = Registry::get('db');
	$this->request = Registry::get('request');
	$this->session = Registry::get('session');

    	$query = $this->db->query("SELECT * FROM language");

 

See how __construct() calls and loads the config, request, session, and db classes?  I didn't post all the code but it goes on to pull data from the DB and loads it into the private data array.  That makes sense to me... the Document class...  :(  Would really appreciate the help!

Share this post


Link to post
Share on other sites

It seems you have things pretty well down now.  You are correct that document is pretty much just a storage class, used to keep track of information later used by the MVC views.  It has no moving parts, so you know that other code must be writing to it.  You can think of the class variables in document as a sort of temporary scratch pad, that will be set in other places.

 

For example if you look in the /controller/account/login.php as an example you'll find this:

 

$this->document->title = $this->language->get('heading_title');

 

As you surmised the document class also aids in providing language support. 

Share this post


Link to post
Share on other sites

Is it the scope resolution operator?  Like in here...

 

// Config
$config = new Config();
Registry::set('config', $config);

 

So Config is instantiated but Registry is not.  Config is placed into Registry but because the both have similar methods and members (particularly $data and __get() and __set()), the Registry ones take precedence (its members and methods are set to static) and overload the Config ones. So, when this is done...

 

foreach ($query->rows as $setting) {
$config->set($setting['key'], $setting['value']);
}

 

It is actually Registry::set($setting['key']....)?  Did I get it right?  Or am I still a bit off?

Share this post


Link to post
Share on other sites

Well, you asked why you didn't see any methods for document and couldn't understand how its instance variables were being set.  An IDE that can search through code is helpful in these types of examples.

 

Now for the new code there's no quesiton of scope.  There is an instance of Registry, only it is a static class. 

 

In this case, Registry has a set of static methods and a static variable in it.  They do this to implement the "singleton" design pattern, because the implication is that the application should have one and only one registry.  This is often referred to as the "Registry" pattern.

 

final class Registry {
static private $data = array();

static public function get($key) {
	return (isset(self::$data[$key]) ? self::$data[$key] : NULL);
}

static public function set($key, $value) {
	self::$data[$key] = $value;
}

static public function has($key) {
    	return isset(self::$data[$key]);
  	}	
}
?>

 

Notice that the class defines:  static private $data = array();

 

Inside a class, this defines the $data variable as being a "class" variable.  In other words, no matter how many objects of class registry one might create, they will allow share the $data variable.

 

Now to the question of how a language can allow for static method calls.  Basically what happens is that when you make static method calls like:

 

Registry::set('config', $config);

 

The environment needs to in essence, manufacture an internal object, so that the static method can be executed.  You could think of that as: php makes this object, runs the method and discards it immediately.

 

In this case, however, because Registry::set adds elements to the static class variable $data, its contents can be accessed via the static get method at a later time.  So what you see happening is that they are creating instances of various objects that will be needed, and sticking them into the Registry, so that the application can make use of them at a later time without having to worry about how to get access to the object without having to pass it via parameter, or having a huge number of global variables floating around.

Share this post


Link to post
Share on other sites

Well, you asked why you didn't see any methods for document and couldn't understand how its instance variables were being set.  An IDE that can search through code is helpful in these types of examples.

 

I guess I can't put off setting up Eclipse any longer, can I? ;D Anyway, I think I found it!

 

home.php

$this->document->title = sprintf($this->language->get('title'), $this->config->get('config_store'));
$this->document->description = $this->config->get('config_meta_description');

 

and in index.php

// Router
if (isset($request->get['route'])) {
$action = new Router($request->get['route']);
} else {
$action = new Router('common/home');
}

 

... the else is called at default.  The puzzle is complete! :D

 

The environment needs to in essence, manufacture an internal object, so that the static method can be executed.  You could think of that as: php makes this object, runs the method and discards it immediately.

 

Ah, I get it now.  Another site attributed the Registry as a temporary basket.  I guess you could also look at it as a grocery basket - unless you place products back on the shelf, each page and all its elements are in the basket until you pay for it at the checkout counter.  Then it gets dumped and emptied onto your screen.  But what a mess!  The code is strewn all over the place. >:(

 

Thanks, gizmola, you're a life-saver!  And I learned more stuff! I think I'll stick around - it's so much nicer at this board knowing that there's someone who actually replies regardless of whether they know what's going on or not.  I get more help in a random forum from a random person in four days than at OpenCart dev (two weeks and counting and I'm not even graced with an insult, rude reply, or anything).  Not to mention I'm going through the code primarily to fill their non-existent documentation and code comments as a contribution to the OpenCart community... sheesh! No wonder people would rather get paid for it - free help isn't very much appreciated these days! [/end vent]

 

Anyway, thanks again!

Share this post


Link to post
Share on other sites

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.