I am currently developing OpenCart extensions. I am looking through existing extensions to understand the architecture (and familiarizing MVC/PHPOO concepts).
Frequently, this line of code comes up:
$seller_email = $this->config->get('service_seller_email');
My interpretation based on reading this thread:
I understand that this is a variable assignment, where it's accessing a model (?) called config to retrieve a string called service_seller_email from the admin settings portal. However, I have tried navigating through the various MVC folders within OpenCart, and I could not find a specific config.php. Could you please correct my interpretation if it's wrong?
EDIT: To add to the confusion, the article I linked specifies that you need to load a config in a controller before using it, like $this->language->load('product/search');, but I cannot find a line such as $this->config->load('...'); or the like.
Your interpretation is incorrect. The config class is not considered as model (despite it is accessing DB directly) but as a system library class - that's why you can find it under system/library/ folder.
OpenCart has it's implementation of service locator (registry) which is used to to store the config object so that you can access it directly from anywhere (inside of OpenCart of course).
It is loaded in both index.php files (in root and under admin/) like this:
$config = new Config();
$registry->set('config', $config);
This $registry is our service locator and it is passed over to any controller in it's __construct() method. Each controller has also a magic method __get() which is responsible for retrieving objects from this $registry - take a look at abstract Controller class at system/engine/controller.php which each controller extends (and should extend).
That's why you can freely call directly
$this->config->get('some_config_key');
The first part, $this->config will fall into the __get() method passing over the 'config' value as a $key which in turn is passed over to next call
return $this->registry->get($key);
And since the config object is registered under 'config' key in our service locator, it is retrieved and returned so that we could call get('some_config_key') on it.
Now the some_config_key key is stored in DB in setting table and the config object tries to find it and get it's value (you can take a look at how this works in system/library/config.php).
Hopefully this clarifies your confusion.
Looking at the docs, it looks like the config values are stored in the setting table. The Config class itself is located in /system/library/config.php.
In admin > index.php you wil find
// Config
$config = new Config();
$registry->set('config', $config);
That's where it is initialized. The class is located in
system > library > config.php
Config loaded in system > engine >loader.php:
public function config($config) {
$this->config->load($config);
}
Related
I've created a video about one of the major problem that I have. I need to understand the file loading architecture, how it's done when writing a class in WP plugin development (how one class knows about the existence of another class), and how this compares with WP MVC file loading architecture. It's one big question, and I went through the various smaller questions that helped me arrived at the question summary in the video in the drive link below. I'm putting the plugin code in the Google Drive folder also: https://drive.google.com/open?id=1JVSSlkSJ5pCfNojRh6jen3ax2w-HZr5d
WP side:
plugin.php has this line: $wp_filter[ $tag ]->do_action( $args );
$tag = 'init'
This calls WP_Hook->do_action, then WP_Hook->apply_filters
WP MVC side:
This then calls MvcLoader->init, which then calls the needed load_controllers/libs/models/settings/functions (only the files under the bolded folder names will be loaded)
load_controllers will use the file_includer and make all files under the [plugin app path]/controllers be required
This means that any class under the specified folders above will be auto-required for the plugin that depends on WP MVC [tested]
This also explains why if one create a helper function, following the documentation, one would put the class under helper folder, but that doesn't get auto-loaded and one will get class not found error (because helpers doesn't get loaded/not in the list above)
Upon a closer inspection, it looks like only classes extending MvcController can load any helper files (base class MvcController is the only class that has load_helper() function). The helper files need to be named xyzHelper, under helpers folder, and needs to extend MvcHelper. In the child controller class, call $this->load_helper('xyz') and the base class will automatically try to load the helper class from the helpers folder. The controller class will now have a helper object named xyz (the class name, including underscores, without the _helper suffix). One can now have access to the helper class through the controller class (in the view classes also, etc. Used like $controller->xyz->function()) [tested]
How to get the above info:
Activate an example plugin from WP MVC and navigate to a public view
Put the breakpoint in mvc_file_includer.php require_file() [I actually just run the debugger without any breakpoint and there was a deprecated function warning at the above function. I didn't think to put a breakpoint at that function previous to seeing this, and I didn't know where to put the breakpoints previous to this realization]
Look at the stack trace and try to understand
Also, in WP MVC, there are other calls that will automatically include files
An example is render_view() - This will either call the controller's, or the helper's, render_view(), which will use the file_includer to find the desired file path, and call require on that file path. Therefore, it's more like this is a dynamic-loading of files that is being used.
Now, how did MvcLoader->init() get triggered?
In the main plugin's file: wp_mvc.php, depending on if it's in the admin/public functionality view, wp_mvc_load_global_functionality is called with the needed parameter. One of the actions in that function is add_action('init', array($loader, 'init')), which triggers the init hook for the MvcAdminLoader, which is a child of MvcLoader [tested by commenting out the add_action and it no longer works]
I am trying to register my modules with the front controller using the bootstrap class by using the code below:
$this->bootstrap('FrontController');
$front = $this->getResource('FrontController');
$front->addModuleDirectory(APPLICATION_PATH."/modules");
$front->setParam('prefixDefaultModule', true);
This works fine and all the modules are registered. But when I do the following my module directory is not registered and I get the "controller not found error".
$front = Zend_Controller_Front::getInstance();
$front->addModuleDirectory(APPLICATION_PATH."/modules");
$front->setParam('prefixDefaultModule', true);
As the front controller implements the singleton design pattern shouldn't both the code blocks refer to the same instance and both of my code blocks should work?
The issue is this line in your application.ini:
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
The file structure you gave doesn't include this directory, so I think you can remove it and it will solve your problem.
Longer explanation of why it works one way and not the other:
There are two ways of configuring resources with Zend Application: class resources and plugin resources. The resources.xxx lines in application.ini are plugin resources, and the _init methods are class resources. Class resources run before plugin resources, but if you run $this->bootstrap('...') it will ensure that resource has been initialized (regardless of type).
So in your first code example, $this->bootstrap('FrontController'); triggers the frontController resource which you defined in your application.ini. This then sets the controller directory using the path you gave it. You then add a module directory which seems to be what your application actually uses.
In your second code example, you get an instance of the front controller and then add your module directory to it (all good). But then the plugin resource will run (as remember these run after). This will grab your existing front controller instance, but setting the controller directory overrides the module directory, so this is what causes your error later on.
I generally try and avoid mixing and matching plugin and class resources as it can cause some weird problems. So either put it all in application.ini or all in the Bootstrap class. Personally I find the latter more readable.
If you do need the controller directory as well, adding $this->bootstrap('FrontController'); to the start of your second code example should work as well, since that will trigger the plugin resource.
Here is what is going on. Tim Fountain is correct. The following line in your application.ini file is the culprit. If you remove it, your application should load correctly.
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
You may also have to remove this line since it is part of the frontcontroller too.
resources.frontController.params.displayExceptions = 1
But you also have 2 other options to be able to use Zend_Controller_Front::getInstance()
Option 1. You change your index.php to bootstrap specific resources:
$application->bootstrap(array('FrontController', 'ModuleConfig'))->run();
This will bootstrap your FrontController first from your application.ini and then run your initModuleConfig method. Essentially this allows you to control what resources are loaded and in what order. This is helpful when you have resources that you only want to bootstrap at specific times.
I think if you do not provide an array to the bootstrap method here, then it will call all methods prefixed with init in the order they are declared.
Option 2. You could do the configuration of your of your module directory within your application.ini
resources.frontController.moduleDirectory = APPLICATION_PATH "/modules"
resources.frontController.params.prefixDefaultModule = 1
resources.frontController.params.displayExceptions = 1
This will do what you were trying to do in code. One thing to note is that you may want to bootstrap your FrontController in the constructor of your Bootstrap class. This is just in case you need to use it during your custom initialization.
Now here is an explanation for why option 1 works.
This is a method from Zend_Application_Bootstrap_BootstrapAbstract
protected function _bootstrap($resource = null)
{
if (null === $resource) {
foreach ($this->getClassResourceNames() as $resource) {
$this->_executeResource($resource);
}
foreach ($this->getPluginResourceNames() as $resource) {
$this->_executeResource($resource);
}
} elseif (is_string($resource)) {
$this->_executeResource($resource);
} elseif (is_array($resource)) {
foreach ($resource as $r) {
$this->_executeResource($r);
}
} else {
throw new Zend_Application_Bootstrap_Exception('Invalid argument passed to ' . __METHOD__);
}
}
This _bootstrap is called when you call the public bootstrap method. Example $this->bootstrap("FrontController") or $this->bootstrap();
Notice the case when you don't pass in a parameter. This will call the null case which is the case you get in your index.php - $application->bootstrap()->run();
First it will load your class resources and then it will also call your plugin resources. Notice that the the plugin resources are not loaded in the other cases.
If you follow the method calls for class resources, it is basically call your init methods in your bootstrap call class.
The plugin resources are called afterward and one of the plugin resources. I'm not entirely sure how all the plugin resources are loaded, but I believe one source is in your application.ini file. These will be the lines that start with resources. Examples includes views, frontcontroller, db.
So in your situation where you call $application->bootstrap()->run();, your init methods are loaded first. But your FrontController is not bootstrapped yet. It eventually gets bootstrapped as a plugin resource which is taken from your application.ini. This apparently overwrites what you did in your bootstrap class.
Another question you may be asking is why is the FrontController instance not overridden when you call $this->bootstrap("FrontController) explicitly. I guess this pretty obvious but personally I had this question myself.
In the Bootstrap class, there is a method called _executeResource and this will check if the resource has already been bootstrapped. It uses an associative array to keep track. The associative array is called $this->_started.
This is why the plugin resource for the front controller is not called in your first case where you explicitly bootstrap the front controller. Hence your front controller instance is not replaced.
You need to bootstrap the front controller first so it is initialized. Once done in the cycle you could always retrieve it with the static getInstance.
This is a very basic question but I can't figure it out.
I have a class that could be named myClass.php, this is a standalone PHP script that contains a standard object definition like this:
<?php
class myClass
{
public function myFunction($param1, $param2){
return $param1*$param2;
}
}
All I want to do is to be able to call to this class from a model, this is from App/Models/MyModel.php be able to simply do $myClass = new MyClass();
Where should I store myClass.php in the file structure and how can I make it visible for MyModel?
Thanks!
You'll note that you have a 'vendors' directory inside the root (ie, on the same level as the 'app' folder). This is for placing non-CakePHP related files that are shared across applications. You also have an 'app/Vendor' directory, which is for storing non-CakePHP files that are specific to that one app.
So, it should go in one of these two directories - most likely, in app/Vendor.
There's a little bit to know about loading vendor files - you can read the details here.
In your case, you probably just need:
App::uses('myClass', 'Vendor');
I've been using registry pattern for a very long time. Basically, I load all the classes using a main object (even if they're not required by the controller itself) and controllers can reach them.
It loads like 20 classes currently and I want to change my approach.
I want to define dependencies for my controllers. For example, my register controller only depends on database class, recaptcha class and filter class.
So, I want to create a solution like this:
//dependencies
$registerDependencies = array(new Database(), new Recatpcha(), new Filter());
//load register controller
$this->loadController->('register', $this->loadDependencies($registerDependencies));
Is it called DI/DI Container?
Is this a better approach than my current system?
I would probably use this approach:
$this->loadController->register('database.main', 'Database')
->register('database.user', 'Database')
->register('recaptcha', 'Racatpcha');
And the register function would look like this
public function register($serviceName, $serviceClass)
{
// you can inject options to your class via a config array or a conf file
$this->registry[$serviceName] = new $serviceClass();
}
If you give an alias to your service, you could have multiple services that share the same class but with different parameters.
The service 'database.main' could connect to a DB and 'database.user' to another DB.
Symfony2 uses dependency injection and you can find documentation about the component on their website.
I'm using the default Zend_Application design pattern which loads a zend config ini file automatically in the application bootstrap and I need to ini file's variables across many models and controllers.
Right now, I'm solving it by settings the config object as a key into Zend_Registry:
protected function _initConfig()
{
$config = new Zend_Config($this->getOptions());
Zend_Registry::set('config', $config);
}
Generally, I don't like using Zend_Registry, as it doesn't offer code auto complete in my IDE, and it's hard to keep track on what I have in the registry namespace.
Is there another way to access the Zend_Application's config ini?
In a controller you should be able to do:
$this->getInvokeArg('bootstrap')->getOptions();
to access the config. For models you really should be passing in the options you need. Otherwise your only choice is really the registry.
You could always initialise it as needed yourself with
$options = new Zend_Config_Ini('/path/to/config.ini',
'config');
Wich is pretty much what the bootstrap does for you. Then you would have autocomplete on $options. But you would have to initialise it everytime you need it.
I think modifying your code to suit autocomplete is not the greatest idea ever. But this is personnal.
If I am not mistaken with Zend Studio 8/9 (maybe 7) you DO have autocomplete even for objects returned by Zend_Registry::get().