Why would you only raise 'onEndRequest' event once per application instance?

In http://code.google.com/p/yii/source/browse/tags/1.1.5/framework/base/CApplication.php#169




public function onEndRequest($event)

{

    if(!$this->_ended)

    {

        $this->_ended=true;

        $this->raiseEvent('onEndRequest',$event);

    }

}



As I understand it, you can only have one CApplication instance during your thread of execution. During this thread of execution, you may have multiple requests. The run() method process these requests as they come. However that little code above puzzle me. I would like the run() method, to raise both ‘onBeginRequest’ and ‘onEndRequest’. Before I override this method, I thought I should ask if I am missing something, or if this was a bug at all?

thanks,

ed

why not to call the methods you use as these events by yourself ?

like




class Bootstrap{

public static function beginRequest(){

echo "Hello ";

}

public static function endRequest(){

echo "World! ";

}

}



and then use in you application




Bootstrap::beginRequest();

//use the code you want here

echo "?";

Bootstrap::endRequest();



I believe that this code will have the same effect

You seem to have a misunderstanding here. Every request creates a new instance of CApplication (CWebApplication to be more precise) and then calls run() to process this single request. After that, the application does not process any more requests. If you take a look inside framework/base/CApplication.php:131, you see that onBeginRequest and onEndRequest get fired every time run() is called (except the script exits before, which is not very common).

You only have to make sure, that the onBeginRequest events where attached before you call run().

I was afraid that this is a design decision. That is, the script exists before any new requests were made. After overriding those methods, I found the logging system was making the same assumption. This behavior is nice when doing CWebApplication, because each instance of the script serves a single request, but no necessarily helpful in different scenarios.

Let me explain what I am working on. I am using beanstalkd in one of the projects to process work queues. I thought it would be nice to use Yii, since we already have the experience required after developing the front end system. Hence, I extended CApplication to process the work queue. And have run through this little problem that I thought I might share.

See attached for a patch that am currently testing, that might prove useful to anyone else. Otherwise, some one could tell me where I am going wrong with this at all.

And what prevents you from firing the Yii::app()->end() event yourself? You can do that ad libitum.

I do that myself in a jQuery long polling php callback and it seems to do the trick.

I remember getting an exception. Something like ‘application can only be created once’. I figured, that end() ends the application script, but YiiBase still thinks its there. Hence, I used my work around. That is assume that run() could process more then one request.

check the


Yii::app()->runController('request/path');

it might solve your problem

That’s not going to solve the issue. We are trying to use 1 Yii application to process multiple requests which currently you can’t do (at least not if you want to have logging working properly). So for the first request, the workflow goes as follows:

  1. Create application. Application class is instantiated with private variable _ended = false;

  2. We call application->run(). onBeginRequest event handler is called, request is processed, onEndRequest eventhandler is called.

  3. onEndRequest event handler says ‘if $this->_ended == false, set $->this_ended to true and raise onEndRequest event’

  4. CLogRouter registers processLogs() as an event handler for onEndRequest event and so processLogs() is called when onEndRequest is raised. processLogs() in turn calls collectLogs() of every configured CLogRoute with write to disk (aka $processLogs) set to true.

  5. CLogRoute collectLogs says: if $this->logs is empty then set $this->logs to $logger->getLogs(), otherwise merge $logger->getLogs() with existing logs then write to disk.

  6. Application ends.

So far so good. Now if a second request comes along and we try to use the same app we created in 1. above, the following happens:

a. We call application->run() again. onBeginRequest event handler is called, request is processed, onEndRequest eventhandler is called.

b. onEndRequest only raises onEndRequest event if $app->_ended == false, which it isn’t since its already been set to true by the first request. No onEndRequest event is raised, so no logs get written to disk.

Fine you say, just raise onEndRequest yourself. If we do that, then Steps 4,5 and 6 should occur and our logging should work fine. However according to the code of CLogRoute->collectLogs:




public function collectLogs($logger, $processLogs=false)

        {

                $logs=$logger->getLogs($this->levels,$this->categories);

                $this->logs=empty($this->logs) ? $logs : array_merge($this->logs,$logs);

                if($processLogs && !empty($this->logs))

                {

                        if($this->filter!==null)

                                Yii::createComponent($this->filter)->filter($this->logs);

                        $this->processLogs($this->logs);

                }

        }



The problem is that the collectLogs function never clears it’s memory ($this->logs), so everytime you run collectLogs you’ll get logs starting from request #1 to the current request. This is what the patches are aiming to resolve + some other events so that you can discern when a request ends and when the app ends.

It’s arguable that Yii was never meant to process more than one request per application, but it seems silly to limit such a powerful framework with such a constraint. So far it seems that it’s just the logging that’s an issue with running Yii in this manner, but i’m sure we’ll discover more as we go along.

I admit i didn’t have the time to fully go through your example. But i think you might have a valid point here. I also remember having some problems with the way logs are collected and never cleared. This can be problematic for long running shell scripts (there even was a discussion here somewhere).

So maybe file a bug? From what i read so far your change shouldn’t affect the current behavior and only improve situation for daemon like shell scripts.

http://code.google.com/p/yii/issues/list

yeah, its a good idea to sugest this improvement in the next version

until then I came up with a solution for you specific case

I believe that will fit your needs




    	//call the begin request event properly

    	Yii::app()->raiseEvent('onBeginRequest',new CEvent(Yii::app()));

    	//run the specified route 

    	Yii::app()->runController('route/path');

    	//call the end request event the right way

    	Yii::app()->raiseEvent('onEndRequest',new CEvent(Yii::app()));



I think it will solve it, but its a good idea to do what Mike said

Bug raised:

http://code.google.com/p/yii/issues/detail?id=1832

P.S Gustavo, that approach will still not solve the multiple logging issue.