• 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
loading my model to load_class

#1
I my project I need to create model without CI instance.

I use function load_class.

PHP Code:
    function &load_class($class$directory 'libraries'$param NULL)
    {
        static $_classes = array();

        // Does the class exist? If so, we're done...
        if (isset($_classes[$class]))
        {
            return $_classes[$class];
        }

        $name FALSE;

        // Look for the class first in the local application/libraries folder
        // then in the native system/libraries folder
        foreach (array(APPPATHBASEPATH) as $path)
        {
            if (file_exists($path.$directory.'/'.$class.'.php'))
            {
                $name 'CI_'.$class;

                if (class_exists($nameFALSE) === FALSE)
                {
                    require_once($path.$directory.'/'.$class.'.php');
                }

                break;
            }
        }

        // Is the request a class extension? If so we load it too
        if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'))
        {
            $name config_item('subclass_prefix').$class;

            if (class_exists($nameFALSE) === FALSE)
            {
                require_once(APPPATH.$directory.'/'.$name.'.php');
            }
        }

        // Did we find the class?
        if ($name === FALSE)
        {
            // Note: We use exit() rather than show_error() in order to avoid a
            // self-referencing loop with the Exceptions class
            set_status_header(503);
            echo 'Unable to locate the specified class: '.$class.'.php';
            exit(5); // EXIT_UNK_CLASS
        }

        // Keep track of what we just loaded
        is_loaded($class);

        $_classes[$class] = isset($param)
            ? new $name($param)
            : new $name();
        return $_classes[$class];
    

If file_exists($path.$directory.'/'.$class.'.php') in the applications/models directory then the $name variable is set to $name = 'CI_'.$class; and check for new class name.
And the $name variable's value won't changes to $class if CI.$class does not exist.
After that the system tries to load not existed class with name CI.$class instead of existed class $class.

Is this bug or I don't undestand logic of this function?

I use the latest version of CI 3.x.
Reply

#2
You understand the logic but maybe not the purpose of this function. Its purpose is to load core classes of the framework and classes that extend core classes. All the core classes are defined using "CI_" as a prefix to the class name. The name of any class that extends a core class must begin with the prefix defined by $config['subclass_prefix']. The filename of extending classes must begin with that prefix too. The default value of  $config['subclass_prefix'] is "MY_".

A workaround you can use is to name your file  and class name using the subclass_prefix.

FILE: APPPATH/libraries/MY_special_model.php
PHP Code:
<?php
defined
('BASEPATH') OR exit('No direct script access allowed');

class 
MY_special_model extends CI_Model 
{
 
   //your code

Reply

#3
(10-31-2017, 06:57 AM)Vitaly83 Wrote: I my project I need to create model without CI instance.

No, you don't need to do that. A need is having a controller, because CI doesn't work without one (it is literally "the CI instance").
You want to do this because you believe it will make something else easier or simpler for you. And as you're already hitting a brick wall - it obviously doesn't.

This is very common, both here and on StackOverflow ... People don't ask how to do a thing; they decide how they do it (usually the first idea that comes to mind) and then ask how to make that idea work, when it was never a good idea in the first place.

Take a step back, think it through again, and if you can't come up with a way that doesn't involve misusing the framework - ask us for ideas on the higher-level problem.
Reply

#4
(10-31-2017, 08:12 AM)dave friend Wrote: A workaround you can use is to name your file  and class name using the subclass_prefix.

I use to add prefix only for extended classes (like, libraries, core, helpers and etc.) and don't add prefix otherwise. Should I add prefix always?


(10-31-2017, 09:18 AM)Narf Wrote: This is very common, both here and on StackOverflow ... People don't ask how to do a thing; they decide how they do it (usually the first idea that comes to mind) and then ask how to make that idea work, when it was never a good idea in the first place.

Sorry, my mistake, you are absolutely right.

Let me tell why I decided to use this function.

I extended the Exception library to send emails on errors. Recipient(s) is(are) not a constant and can be get from a module (that I need to load). To load this module (and email library too)  I need to get an CI instance ($CI =& get_instace()).

Next, the CSRF Protection is turned on.
In Input library constructor we can see this code:

PHP Code:
    public function __construct()
    {
        ...
        // CSRF Protection check
        if ($this->_enable_csrf === TRUE && ! is_cli())
        {
            $this->security->csrf_verify();
        }
        ...
    

Then the Security library's method csrf_verify:
PHP Code:
    public function csrf_verify()
    {
        ...
        if ($valid !== TRUE)
        {
            $this->csrf_show_error();
        }
        ...
    

The csrf_show_error method just calls the show_error method of the Exception library.

As I said before, I extended the Exception library and get $CI instance on every error.

On POST requests with wrong csrf data I get such message:

Quote:<br />
<b>Fatal error</b>:  Class 'CI_Controller' not found in
<b>/var/www/html/site/system/core/CodeIgniter.php</b> on line
<b>369</b>
<br />
<br />
<b>Fatal error</b>:  Class 'CI_Controller' not found in
<b>/var/www/html/site/system/core/CodeIgniter.php</b> on line
<b>369</b>
<br />

I just need to be notifed about errors. If you know how to do this another way it would be great.

That's why I decided to not to use CI instance and load libraries and modules with load_class method.

P.S. Most of these POST requests from outside (not initiated by system). And I need the CSRF Protection to be turned on.

UPD:
Here is MY_Exceptions class:
PHP Code:
class MY_Exceptions extends CI_Exceptions
{
    /**
     * @param string $subject
     * @param string $message
     */
    private function sendAdminEmail($subject$message)
    {
        $CI =& get_instance();
        $CI->load->library('Employees');
        $admin $CI->Employees->getAdmin();
        $CI->load->library('email');
        $CI->email->clear(true);
        $CI->email->from('exception@site.com''Error notifier');
        $CI->email->to($admin['email']);
        $CI->email->subject($subject);
        $CI->email->message($message);
        $CI->email->send();
    }

    /**
     * @param string $heading
     * @param string|\string[] $message
     * @param string $template
     * @param int $status_code
     *
     * @return string
     */
    public function show_error($heading$message$template 'error_general'$status_code 500)
    {
        ...
        $this->sendAdminEmail(
            sprintf('Exception: %s'$heading),
            $buffer
        
);
        ...
    }

    /**
     * @param Exception $exception
     */
    public function show_exception($exception)
    {
        ...
        $this->sendAdminEmail(
            sprintf('Exception #%d: %s'$exception->getCode(), $exception->getMessage()),
            $buffer
        
);
        ...
    }

    /**
     * @param int $severity
     * @param string $message
     * @param string $filepath
     * @param int $line
     */
    public function show_php_error($severity$message$filepath$line)
    {
        ...
        $this->sendAdminEmail(
            'PHP Exception',
            $buffer
        
);
        ...
    }



I overwrited standart Exceptions methods to just add call of the sendAdminEmail method. Nothing else.
Reply

#5
I see ... Well, CI certainly doesn't make this easy and you will indeed have to use load_class() to be 100% safe, but only for the email library.

You've made 2 design decisions that are inherently bad for this:

1. Getting the recipients from a model. It's fine if you don't want to hard-code them, but they belong in a configuration file.
2. Emailing about every single show_error() call. What you need is notifications for actual application logic errors, not ones caused by the client (CSRF and 404 errors are client ones). A single bot trying to access random URIs on your app will quickly flood your email recipients; this is a very bad idea.

And then there's another detail that you haven't thought about - all of the important show_*() calls are only triggered if you have display_errors enabled, which must never be the case in production. So, you should rather hook into the actual error and exception handlers (where it is decided whether an error should be logged, displayed, both or neither).

Having eliminated CSRF and 404 errors from the set, recipients being in standard config, and knowing that extending/overriding CI_Exceptions won't work, you can use a pre_controller hook to do something like this:

Code:
function send_error_email($error_contents)
{
    // get recipients via get_config()
    // load CI_Email via load_class('Email', 'library')
    // send the $error_contents
}


function _custom_error_handler($severity, $message, $filepath, $line)
{
    send_error_email(func_get_args());
    _error_handler($severity, $message, $filepath, $line); // this forwards the call to CI's error handler
}

function _custom_exception_handler($exception)
{
    send_error_email($exception);
    _exception_handler($exception); // again, forward back to CI's handler
}

set_error_handler('_custom_error_handler');
set_exception_handler('_custom_exception_handler');
Reply

#6
(11-01-2017, 05:05 AM)Narf Wrote: And then there's another detail that you haven't thought about - all of the important show_*() calls are only triggered if you have display_errors enabled.

I didn't know about it.

(11-01-2017, 05:05 AM)Narf Wrote: So, you should rather hook into the actual error and exception handlers (where it is decided whether an error should be logged, displayed, both or neither).

Understood. Thanks.
Reply

#7
(10-31-2017, 10:31 PM)Vitaly83 Wrote: I use to add prefix only for extended classes (like, libraries, core, helpers and etc.) and don't add prefix otherwise. Should I add prefix always?

No. Add the prefix only when extending "native" classes. "Native" in this context means: Any class found in the system folder or its subfolders.
Reply

#8
(11-01-2017, 05:05 AM)..Narf Wrote: Having eliminated CSRF and 404 errors from the set, recipients being in standard config, and knowing that extending/overriding CI_Exceptions won't work, you can use a pre_controller hook to do something like this:

With pre_controller hook custom error handlers aren't called. If I change hook to pre_system, they are called, but there is another error with Email library:
PHP Code:
    protected function _set_error_message($msg$val '')
    {
        $CI =& get_instance();
        $CI->lang->load('email');

        if (sscanf($msg'lang:%s'$line) !== OR FALSE === ($line $CI->lang->line($line)))
        {
            $this->_debug_msg[] = str_replace('%s'$val$msg).'<br />';
        }
        else
        
{
            $this->_debug_msg[] = str_replace('%s'$val$line).'<br />';
        }
    

And again, issue with getting CI instance.
This method is called from another Email method _spool_email.

As I understand the error is triggerred in CodeIgniter.php when the system calls load_class('Input', 'core') - before pre_controller hook (I think only pre_system hook can used for this purpose).
And then "Input constructor" -> "Security->csrf_verify" -> "Security->csrf_show_error" (error is cycled).
Reply

#9
(11-02-2017, 02:30 AM)Vitaly83 Wrote:
(11-01-2017, 05:05 AM)..Narf Wrote: Having eliminated CSRF and 404 errors from the set, recipients being in standard config, and knowing that extending/overriding CI_Exceptions won't work, you can use a pre_controller hook to do something like this:

With pre_controller hook custom error handlers aren't called. If I change hook to pre_system, they are called, but there is another error with Email library:
PHP Code:
    protected function _set_error_message($msg$val '')
    {
        $CI =& get_instance();
        $CI->lang->load('email');

        if (sscanf($msg'lang:%s'$line) !== OR FALSE === ($line $CI->lang->line($line)))
        {
            $this->_debug_msg[] = str_replace('%s'$val$msg).'<br />';
        }
        else
        
{
            $this->_debug_msg[] = str_replace('%s'$val$line).'<br />';
        }
    

And again, issue with getting CI instance.
This method is called from another Email method _spool_email.

As I understand the error is triggerred in CodeIgniter.php when the system calls load_class('Input', 'core') - before pre_controller hook (I think only pre_system hook can used for this purpose).
And then "Input constructor" -> "Security->csrf_verify" -> "Security->csrf_show_error" (error is cycled).

You've literally quoted my answer to that:

(11-01-2017, 05:05 AM)..Narf Wrote: Having eliminated CSRF and 404 errors from the set

Simply ignore the CSRF validation errors.
Reply

#10
(11-02-2017, 03:38 AM)Narf Wrote: Simply ignore the CSRF validation errors.

How can I ignore these errors if I need to be notified about them?
With hook I only add error handlers.

CodeIgniter.php:
Lines 138-139: set standart CI errors handlers
Line 207: call pre_system hooks <-- here I can replace standart CI errors handlers
Line 342: load Input class <-- here is triggered the CSRF Exception
Line 508: call pre_controller hooks <-- will no effect if I change standart CI errors handlers here


There is no way to send emails about errors, is there?
Reply


Digg   Delicious   Reddit   Facebook   Twitter   StumbleUpon  


  Theme © 2014 iAndrew  
Powered By MyBB, © 2002-2019 MyBB Group.