I am writing my own logging class to save data in a DB. As I looked how CI is doing I noticed there is a log_message() function which handles the logging. There is a load_class function I can't assign to anything in the CI user guide.
1 Why do they put this into an extra function?
2 What/where loads this function files from?
Hope there are some CI guys how can answer :-)
Short answer:
You can write your own log class to override the default CI class:
<?php
// this file is /application/libraries/MY_Log.php
class MY_Log extends CI_Log {
public function write_log($level = 'error', $msg, $php_error = FALSE)
{
// Put your own logging function in here.
// If you want it to still log to a file as usual, use this:
parent::write_log($level, $msg, $php_error);
}
}
Long answer:
The load_class() function is basically a singleton loader. If the class has already been loaded, return a previous instance; otherwise, load it and create the singleton. It is very important in a framework like CI. You have to know that every time you call, say, a database function, it is applying it to the same object, not instantiating a new one (that would get really messy). All CI libraries function this way by default.
An important note: they changed how this functions significantly in version 2.0. Previously, it would only load from the /libraries folder, but now, it will load from /core or wherever you specify when calling the function.
Here's the process for loading, say, the Log class (from your example):
$_log =& load_class('Log');
$_log->write_log($level, $message, $php_error);
This runs the following checks, in sequence:
If the Log class already exists, we're done. Return the singleton.
If not, first check the /system/libraries folder for a "Log.php" file
If no file existed for step #2, now check /application/libraries for a "MY_Log.php" file (or whatever your subclass prefix is set to in your configuration)
If it loaded the default CI class (from the /system folder), but you DO have an extended class under /application, load that class too.
Return a new instance of the class (YOURS if it exists; otherwise, it's the CI_* class)
I've actually never needed to use the load_class() function, as it allows extension fairly seamlessly. However, it's good to know how it works.
So, to override a class, first find where the original resides (usually /system/libraries or /system/core). Put your extending file in the corresponding /application folder (this is important! If it's under /system/core, the extension MUST be under /application/core). Prefix both the filename and the class name with MY_ (or whatever you set in your configuration), and have it extend the CI_ base class.
Related
I'd like to get the path to a package public directory (css etc) based on the package alias.
Is there anything already built into the laravel framework?
In other words something like:
public_path('myalias');
When I'm talking about alias, you would typically "alias" a module by adding the following within your service provider's boot method:
$this->package('namespace/package','alias_name');
For those wondering why someone might want to do this:
We are running a multi domain/subdomain application that makes use of a central piece of code for all of the domains and then specific packages per domain (I'll refer to them as funnels).
Each funnel has its own controllers that can possibly extend base controllers to implement their own functionality while re-using code where they can. They also have their own views.
The funnel refers to its own views by way of something like:
View::make('funnel::path.to.view')
The way we accomplish this is by doing some business logic on page load to only load the FunnelServiceProvider related to that particular domain and aliasing it to "funnel". This way our base controllers can also refer to funnel and not be tied to a particular packages views,includes,blocks etc.
My hope is to do something similar on the views so that I can simply call something like get_funnel_path() to get the path to the funnel that is currently being loaded.
The value could then be used to load css,js,images etc without worrying about the funnel path.
This would allow us to simply copy and paste views from one domain to the next without having to modify all of the paths in potentially multiple files. We could also make use of globally included files in all/most of the views.
An example of this might be the head. The head section should be the same for 99% of the files, however the path where it loads its resources should change based on the funnel.
We use the same naming conventions for css files as well as use sass, imports, merging for all of the funnels; so only the path needs to change.
You can do something like this although it will only work with your own packages and require a bit of work. Because the alias is not really stored somewhere you can easily access you have to do that yourself.
First create some kind of class to store your package names in. I called mine PackageManager:
class PackageManager {
private $packages = array();
public function addPackage($fullName, $alias){
$this->packages[$alias] = $fullName;
return $this;
}
public function getPublicPath($alias){
if(!isset($this->packages[$alias])) return public_path();
$path = 'packages/' . $this->packages[$alias];
return public_path($path);
}
}
Now let's register that class as a singleton in a service provider:
$this->app->singleton('packagemanager', function(){
return new PackageManager();
});
Then, in every package you want to register, add this call in the boot method right next to $this->package():
$this->app['packagemanager']->addPackage('vendor/package', 'alias');
After that you can do this anywhere in your application:
app('packagemanager')->getPublicPath('alias');
If you want a shorter syntax, add this helper function somewhere:
function public_package_path($alias){
return app('packagemanager')->getPublicPath($alias);
}
And just do:
public_package_path('alias');
I'm developing my second Magento module, which should retrieve some data and render a block containing it. Such block would then be used by other pages.
At the moment, I have almost the whole module in place, but, for some reason, the block is not rendered when I call the controller method. I put some debug information, and I could see that the block's __construct() method is called correctly, but then the template doesn't seem to be loaded and the returned page is blank.
Here's the code for the block, which I copied from another module and modified:
class Company_CustomerData_Block_CustomerSummary extends Mage_Core_Block_Template {
const _TEMPLATE = 'customerdata/customersummary.phtml';
public function __construct() {
// This method is called correctly
parent::_construct();
$this->setTemplate(self::_TEMPLATE);
}
}
The file customersummary.phtml is in app/design/frontend/base/default/template/customerdata, which should be the correct place (or, at least, I think it is). Its content is the following:
It works!
Just some plain text. No tags, no code, nothing. I don't mind that it's a static text, it will be populated with data once complete.
In case of need, here's the code for the Controller (I removed the parts where the data is retrieved, as they don't make a difference):
public function dashboardAction() {
// Customer Data to render in the block
$CustomerData = array(); // Data is retrieved elsewhere
$this->getResponse()->setBody(
$this->getLayout()->createBlock('customerdata/customersummary')
->toHtml()
);
}
What could I be doing wrong? I'm afraid I made some stupid mistake again, but I really can't see it.
Finally two more questions:
How do I pass to the template the data I retrieve in the Controller? Specifically, variable $CustomerData.
Once the block renders, how do I render its content from within a page? The block should return a with some stuff in it, I'd like to render it inside the Customer Dashboard, just below the information that is already there.
Thanks in advance for the help.
Most classes on Magneto derive from Varien_Object. Varien_Object (and it's descendents) claim PHP's constructor (__construct) for themselves, and provide an _construct callback which you can use for whatever you like. What this means is that if you override the native PHP constructor (__construct) you need to remember to do a few things:
Accept the same number of parameters as the base class, and...
Call the parent constructor (parent::__construct) with the parameters your constructor was supplied.
Or, alternatively, use the _construct callback supplied by Varien_Object and you're done. There's no need to remember to call parent::_construct if you're using the Magento callback.
So to fix your code snippet above, you can either change...
public function __construct() {
... to ...
public function _construct() {
This will switch you over the using the Magento callback. Or you can change...
parent::_construct();
.. to ...
parent::__construct();
... to call the parent class' constructor. Remembering to add in the parameters that the parent class supplies.
Alan Storm write a great article about Magento's Block Lifecycle and call back methods recently, which might be of assistance.
To your other questions:
Passing information from the controller to a block is generally done via Magento's registry.
Rendering your template as part of a page requires that you create layout instructions (written in XML) which load your block and place it inside a parent block.
Although Jim's Answer is correct, I'm adding a second part because I found out what else was wrong: symbolic links. As I usually do when I develop plugins for a framework, I use symlinks to avoid copying the files over and over again. However, Magento uses function RealPath(), which resolves the symlink to its full path. As a result, the Template file to be loaded resides in a path outside Magento installation directory, and it can't be loaded for security reasons. This makes perfectly sense, pity it's just not very visible.
To fix the issue, I enabled Allow Symbolic Links in configuration on my Development PC, and now the Template is loaded and rendered correctly.
I think that Magento could do with a Log Viewer in the Admin interface. If there isn't a module that does it already, perhaps I should create one myself.
Thanks everybody for the help.
I have made some changes to Mage_Core_Controller_Request_Http but in the file distributed by with magento. This is not the best way, i know, but i have not been able to work out how to override a file in the Controller directory. I can find out how to override files in the controllers directory.
Can anyone tell me how i can override Mage_Core_Controller_Request_Http in my own extension.
thanks
If you don't want to revert to the include path hack, you can also use reflection to set your own request class on the Mage_Core_Model_App model. You can use an observer for controller_front_init_before event for that.
I'll assume you are familiar how to create an event observer, soI'll only add the code for the observer method. If you need additional information please ask.
// Observer method
public function controllerFrontInitBefore(Varien_Event_Observer $observer)
{
$app = Mage::app();
$reflection = new ReflectionClass($app);
$property = $reflection->getProperty('_request');
$property->setAccessible(true);
$myRequest = new Your_Module_Controller_Request_Http();
$myRequest->setOrigRequest($app->getRequest()); // if needed
$property->setValue($app, $myRequest);
// Proof of concept:
// Loggs Your_Module_Controller_Request_Http
Mage::log(get_class(Mage::app()->getRequest()));
}
Create the class Your_Module_Controller_Request_Http and extend the original Mage_Core_Controller_Request_Http.
After that event your request object will be used instead of the original.
This enables you to stay as upgrade safe as possible, because you do not have to copy over the full class from the cor code pool.
Edit: Vinai's solution is the better one.
Because this class is instantiated directly, you will have to use the so-called include path hack to override.
The order of precedence for include paths which affect Varien_Autoload's work is set in app/Mage.php. That order is as follows:
app/code/local/
app/code/community/
app/code/core/
lib/
Therefore, if you copy your file to an analogous path under the local or community codepools, your definition of that class will be used.
Since Magento 1.7 you can use the method Mage::app()->setRequest($request) to replace the request object within an observer for the controller_front_init_before event as suggested by Vinai.
WARNING for Magento Enterprise: The Full Page Cache won't work with this method, as it relies on changes to the request object done before controller_front_init_before. You either need to manually copy all properties from the old request to the new - or replace the request class with benmarks solution.
Am having problems understanding where classes should be kept in CI. I am building an application that describes / markets mobile phones.
I would like for all of my functions (i.e. getphone, getdetails etc.) to reside in one class called Mobile - I understand that this file should be called Mobile.php and reside in the controllers folder.
Can I then have multiple functions inside Mobile.php? E.g.
public function getphone() {
xxx
xx
xx
}
public function getdetails() {
xxx
xx
xx
}
Or do I need to put each function in its own class?
I'd really appreciate looking at some sample code that works. I've been going through the documentation and google for a few hours, and tried all sorts of variations in the URL to find a test class, but without much luck! I've even messed around with the routes and .htaccess...
All I am trying to achieve is the following:
http:///model/HTC-Desire/ to be re-routed to a function that accepts HTC-Desire as a parameter (as I need it for a DB lookup). The default controller works fine, but can't get anything to work thereafter.
Any ideas?
Thanks
Actually it works like this:
Controllers and Models go to their perspective folders as you know it
If you want to create functions that are not methods of an object, you must create a helper file. More info here :
http://codeigniter.com/user_guide/general/helpers.html
Now if you want to create your own datatypes (classes that don't extend Models and Controllers), you add them to the library folder. So if let's say you want to create a class "Car" you create this file:
class Car{
function __construct(){}
}
and save it in the libraries folder as car.php
To create an instance of the Car class you must do the following:
$this->load->library('car');
$my_car = new Car();
More information on libraries here:
http://codeigniter.com/user_guide/general/creating_libraries.html
Yes, you can have as many functions in a controller class as you'd like. They are accessible via the url /class/function.
You can catch parameters in the class functions, though it's not advisable.
class Mobile extends CI_Controller{
public function getPhone($phoneModel=''){
echo $phoneModel;
//echo $this->input->post('phoneModel');
}
}
http://site.com/mobile/getPhone/HTC-Rad theoretically would echo out "HTC-Rad". HOWEVER, special characters are not welcome in URL's in CI by default, so in this example you may be met with a 'Disallowed URI characters" error instead. You'd be better off passing the phone model (or any other parameters) via $_POST to the controller.
Classes can exist both as Controllers and Models, as CodeIgniter implements the MVC pattern. I recommend reading more about that to understand how your classes/functions/etc. can best be organized.
Off the top of my head, Pyro CMS is an application built with CodeIgniter and the source code is freely available. I'm sure there are others.
I think it's best you handle it from one perspective, that is; create a utility class with all your functions in it.
The answer to the question of where to put/place the class file is the "libraries" folder.
This is clearly stated in the documentation. Place your class in the libraries folder.
When we use the term “Libraries” we are normally referring to the
classes that are located in the libraries directory and described in
the Class Reference of this user guide.
You can read more on creating and using libraries Creating Libraries — CodeIgniter 3.1.10 documentation
After placing the newly created class in the libraries folder, to use just simply load the library within your controller as shown below:
$this->load->library('yourphpclassname');
If you wish to receive several arguments within you constructor you have to modify it to receive an argument which would be an array and you loading/initialization would then be slightly different as shown below:
$params = array('type' => 'large', 'color' => 'red');
$this->load->library('yourphpclassname', $params);
Then, to access any of the functions within the class simply do that as shown below:
$this->yourphpclassname->some_method();
I hope this answers your question if you have further question do leave a comment and I would do well to respond to them.
I have a user class with static methods getById and getByUsername
I have the class in the application/libraries folder
How do I call the classes from a controller?
Theory 1:
$this->user = new User();
$this->user::getById;
Theory 2:
$user = new User();
$user::getById;
or is there a clean way of doing it much like how Kohana helpers do it; much like:
text::random();
here's what I am trying to accompplish:
I want to call a static mehthod in the user library from my controller
In PHP you usually include the file (User.php) and the static methods are ready
User:getById
but then how would I do the same thing in an MVC framework?
shall I do an include too?
Like include ('User.php');?
User::getById();
and
User::getByUserName();
Edit: In response to your question edit, generally frameworks have an auto-loading mechanism that will find and load a class file for you once you reference that class. So when you type User::getById(), the PHP interpreter will see that it needs to load the User class (if it hasn't been loaded already), and run the autoloading procedure to find the correct code to include.
I've never used Kohana, but I would be quite surprised if it didn't use some form of autoloading. If it does not, then yes, a simple include('User.php') will be enough to make the static method calls to User work.
The confusing thing is Kohana's convention of writing "helper" classes with lowercase names.
Your user php file will probably all ready be loaded if your using it as a model, so you can use zombat's suggests of User::getById();.
I don't like to follow their naming convensions for helpers or libraries and instead do:
require_once(Kohana::find_file('libraries', 'user_utils', TRUE, 'php'));