I'm trying to improve the performance of some code in my site and found this profiler: https://github.com/loic-sharma/profiler
I've followed the guidance on the site and included the following in one of the sites controllers:
public function getTest() {
$logger = new Profiler\Logger\Logger;
$profiler = new Profiler\Profiler($logger);
$profiler->startTimer('testLogging');
$data = Article::select(array(
'articles.id',
'articles.article_date',
'articles.image_link',
'articles.headline',
'articles.category'
)) ->get()
->toArray();
var_dump($data);
$profiler->endTimer('testLogging');
Log::info('Hello World!');
echo $profiler;
In the browser I get the expected results and can see the profiler bar at the bottom.
I have one problem: in this basic test the profiler bar, when clicked doesn't stay open so I'm unable to view the logs etc. I'm not sure why or how to go about fixing. THe pane opens then closes again immediately.
If I remove the final echo it works correctly.
I can't though seem to see the timer 'testLogging' in the toolbar.
Have I misunderstood a concept here?
How can I time specific functions in my code and display the results?
Thanks
To use the profiler(loic-sharma/profiler) correctly in Laravel 4 it does not require you the create an instance of the objects for example.
$logger = new Profiler\Logger\Logger;
$profiler = new Profiler\Profiler($logger);
Laravel has these beautiful things called Facades (http://laravel.com/docs/facades), the profiler implements them so you can call the Profiler and Log like this:
public function getTest() {
Profiler::startTimer('testLogging');
$data = Article::select(array(
'articles.id',
'articles.article_date',
'articles.image_link',
'articles.headline',
'articles.category'
)) ->get()
->toArray();
var_dump($data);
Profiler::endTimer('testLogging');
Log::info('Hello World!');
}
This does not require you to echo the $profiler, all output will be displayed in the profiler bar in the browser automatically.
Notice the :: now after Profiler this usually means you are using the facade, it is important you understand that the facade and the $profiler are completely different entities.
If you have not yet installed the facades and or service provider do the following:
First you have to install the package with composer, make sure you
have run composer update after adding it in your composer.json in
"require".- you have already done this.
Next add 'Profiler\ProfilerServiceProvider', to the list of service
providers in app/config/app.php
Next add 'Profiler' => 'Profiler\Facades\Profiler', to the list of
class aliases in app/config/app.php
Then in the console run php artisan config:publish
loic-sharma/profiler
After you have complete that the amended code above should work perfectly.
Just to clarify what you did wrong, you created a new Instance of the profiler with new Profiler\Logger\Logger; if you already had the facades set up the profiler bar would be displayed (echoed) to the browser already so when you echo $profiler; you now have two profilers in your browser causing the open close issue, and when you don't echo $profiler the bar is still displayed because it not the one you created thus not showing your output correctly.
If you still want to use your own instance of the profiler:
remove 'Profiler\ProfilerServiceProvider', from the list of service
providers in app/config/app.php
remove 'Profiler' => 'Profiler\Facades\Profiler', from the list of
class aliases in app/config/app.php
Then in the console run php artisan dump-autoload
Then this will work:
public function getTest() {
$logger = new Profiler\Logger\Logger;
$profiler = new Profiler\Profiler($logger);
$profiler->startTimer('testLogging');
$data = Article::select(array(
'articles.id',
'articles.article_date',
'articles.image_link',
'articles.headline',
'articles.category'
)) ->get()
->toArray();
$logger->debug(var_dump($data));
$profiler->endTimer('testLogging');
$logger->info('Hello World!');
echo $profiler;
}
Related
I have created a service provider which provides a class App\Path. This is loaded up through Eloquent using $request->getPathInfo()
$this->app->singleton(Path::class, function($app)
{
$request = $app->make(\Illuminate\Http\Request::class);
$path = Path::with(['template', 'parts'])->findOrFail($request->getPathInfo());
return $path;
});
The app works fine and as expected. However when I want to use Artisan I get the following error:
In Builder.php line 369:
No query results for model [App\Path] /
This prevents me from clearing caches, creating models etc. It seems that Laravel runs register() when running any artisan command and when this done, the request path is "/" which doesn't exist in the DB. Is there a better way to populate the Path object? The only way to solve this seems to add a dummy record for "/".
You can check whether the app is running from console and adjust its logic, for example:
$this->app->singleton(Path::class, function($app)
{
if ($app->runningInConsole()) {
return null;
}
$request = $app->make(\Illuminate\Http\Request::class);
$path = Path::with(['template', 'parts'])->findOrFail($request->getPathInfo());
return $path;
});
I have a guitar lessons site where there is an exercises table. The original developers placed some functions in ExercisePresenter to retrieve other bits of data associated with an exercise, such as its url.
Here is a function in ExercisePresenter that returns url for an exercise:
public function url()
{
return '/guitar-lesson-ex/' . $this->urlName() . '/' . $this->id;
}
So now I am creating an event on new exercise created so I can use pusher notifications. In the EventServiceProvider I have this:
public function boot(DispatcherContract $events)
{
parent::boot($events);
Exercise::created(function ($exercise) {
// need to update lesson difficulty
$lesid = $exercise->lesson_id;
$les = \App\Lesson::find($lesid);
$dif = $les->difficulty();
DB::table('lessons')
->where('id', $lesid)
->update(['difficulty' => $dif]);
// lets trigger NewExerciseEvent to send pusher notifications
$url = $exercise->url;
event(new NewExerciseEvent($message));
});
}
I thought in above code $url = $exercise->url; would work since I see $exercise->url used successfully in exercise views. But it is returning $url as null. Now, there is no url column in the exercise database, so I figure somehow when $exercise->url; is used in a view, laravel is figuring out that the ExercisePresenter is able to return the url.
I am debugging through PHPStorm, but when I get to $url = $exercise->url; and step in, I am just taken through various bits of laravel code that looks for a method, etc. I am not savvy enough with laravel to figure out what it is doing here differently than in the view (too bad we can't debug views...), but each time I try this $url is returned as null.
Any idea how to get this to work properly in my EventServiceProvider?
Thanks!
Figured it out:
$url = $exercise->present()->url;
I had been searching for how to use presenters but having just read (Laravel View Presenters From Scratch), everything is clear!
Sorry for posting prematurely.
I have the following code in 'saved' model event:
Session::flash('info', 'Data has been saved.')`
so everytime the model is saved I can have a flash message to inform users. The problem is, sometimes I just need to update a field like 'status' or increment a 'counter' and I don't need a flash message for this. So, is it possible to temporarily disable triggering the model event? Or is there any Eloquent method like $model->save() that doesn't trigger 'saved' event?
Solution for Laravel 8.x and 9.x
With Laravel 8 it became even easier, just use saveQuietly method:
$user = User::find(1);
$user->name = 'John';
$user->saveQuietly();
Laravel 8.x docs
Laravel 9.x docs
Solution for Laravel 7.x, 8.x and 9.x
On Laravel 7 (or 8 or 9) wrap your code that throws events as per below:
$user = User::withoutEvents(function () use () {
$user = User::find(1);
$user->name = 'John';
$user->save();
return $user;
});
Laravel 7.x docs
Laravel 8.x docs
Laravel 9.x docs
Solution for Laravel versions from 5.7 to 6.x
The following solution works from the Laravel version 5.7 to 6.x, for older versions check the second part of the answer.
In your model add the following function:
public function saveWithoutEvents(array $options=[])
{
return static::withoutEvents(function() use ($options) {
return $this->save($options);
});
}
Then to save without events proceed as follow:
$user = User::find(1);
$user->name = 'John';
$user->saveWithoutEvents();
For more info check the Laravel 6.x documentation
Solution for Laravel 5.6 and older versions.
In Laravel 5.6 (and previous versions) you can disable and enable again the event observer:
// getting the dispatcher instance (needed to enable again the event observer later on)
$dispatcher = YourModel::getEventDispatcher();
// disabling the events
YourModel::unsetEventDispatcher();
// perform the operation you want
$yourInstance->save();
// enabling the event dispatcher
YourModel::setEventDispatcher($dispatcher);
For more info check the Laravel 5.5 documentation
There is a nice solution, from Taylor's Twitter page:
Add this method to your base model, or if you don't have one, create a trait, or add it to your current model
public function saveQuietly(array $options = [])
{
return static::withoutEvents(function () use ($options) {
return $this->save($options);
});
}
Then in you code, whenever you need to save your model without events get fired, just use this:
$model->foo = 'foo';
$model->bar = 'bar';
$model->saveQuietly();
Very elegant and simple :)
Call the model Object then call unsetEventDispatcher
after that you can do whatever you want without worrying about Event triggering
like this one:
$IncidentModel = new Incident;
$IncidentModel->unsetEventDispatcher();
$incident = $IncidentModel->create($data);
To answer the question for anyone who ends up here looking for the solution, you can disable model listeners on an instance with the unsetEventDispatcher() method:
$flight = App\Flight::create(['name' => 'Flight 10']);
$flight->unsetEventDispatcher();
$flight->save(); // Listeners won't be triggered
In laravel 8.x :
Saving A Single Model Without Events
Sometimes you may wish to "save" a given model without raising any events. You may accomplish this using the saveQuietly method:
$user = User::findOrFail(1);
$user->name = 'Victoria Faith';
$user->saveQuietly();
See Laravel docs
In laravel 7.x you can do that as easy as
use App\User;
$user = User::withoutEvents(function () {
User::findOrFail(1)->delete();
return User::find(2);
});
See more in Laravel 7.x Muting Events documentation
You shouldnt be mixing session flashing with model events - it is not the responsibility of the model to notify the session when something happens.
It would be better for your controller to call the session flash when it saves the model.
This way you have control over when to actually display the message - thus fixing your problem.
Although it's a long time since the question, I've found a way to turn off all events at once. In my case, I'm making a migration script, but I don't want any event to be fired (either from Eloquent or any other).
The thing is to get all the events from the Event class and remove them with the forget method.
Inside my command:
$events = Event::getRawListeners();
foreach ($events as $event_name => $closure) {
Event::forget($event_name);
}
The only thing that worked for me was using the trait WithoutEvents. This will be executed inside the setUp method and does prevent any dispatch you have added to the code.
I'm running a functional test against my Symfony 1.4 project and it keeps failing because it doesn't fetch the latest data.
The test makes up a new site entry, then a survey at the site, then adds data to the survey. Each of these are on separate pages and each work. The data is definitely present in the database. After saving the last form, the survey_data, it returns to the survey page where it should get the survey details and a list of all the data items added to it. This works in dev and prod environments but in my functional tests the survey_data list is empty. Looking through the logs it doesn't try to fetch the data from the database (Doctrine & Postgres). But if I manually load the page in a browser just seconds later the list is there, and if I run a test that goes directly to the page (without clearing the database) the list is there, so the test login has the rights to see that page and the contents, but won't show it at first, as if it has cached the page survey page before the survey_data was added.
So my question is, how can I ensure my functional tests get the latest data from the DB or how can I refresh the local data object cache after saving a new item to the database?
Additional:
My functional tests extend sfPHPUnitBaseFunctionalTestCase and I use the sfBrowser class to check the contents of the pages and navigate through them
The issue appears to be with Doctrine caching any objects it updates for the duration of the execution. This gets cleared out after each web request in a production environment - one request per execution - but in the test environment it appears to persist between requests as they're all happening in the same execution.
It becomes troublesome when testing the process of, say, adding a new Item to a List - the List is in memory (test browser has been to its page before), the new Item gets created, saved but the link between them isn't formed in-memory.
Targeted refreshing of related objects
If you know what object you're looking to forcibly refresh you can:
$SomeDoctrineRecordObject->refreshRelated(); // for all relationships
$SomeDoctrineRecordObject->refreshRelated($relation); // for a specific one
( for the above List and Item example, you'd $list->refreshRelated('item') )
This is only useful if you're using the Doctrine object already, else you have to go pull it out of the route or database each time you need to refresh it. A more general approach is to extend the sfTestFunctional class and override the methods which might result in a relationship change and thus need to trigger a refresh.
General refreshing of all objects
The code to clear out Doctrine's object cache:
$manager = Doctrine_Manager::getInstance();
$connection = $manager->getCurrentConnection();
$tables = $connection->getTables();
foreach ( $tables as $table ) {
$table->clear();
}
And an example of how to hook it into a custom functional test object:
class myTestFunctional extends sfTestFunctional
{
public function get( $uri, $parameters = array(), $changeStack = true )
{
$this->clearEntityCache();
return parent::get( $uri, $parameters, $changeStack );
}
public function click( $name, $arguments = array(), $options = array() )
{
$this->clearEntityCache();
return parent::click( $name, $arguments, $options );
}
protected function clearEntityCache()
{
$manager = Doctrine_Manager::getInstance();
$connection = $manager->getCurrentConnection();
$tables = $connection->getTables();
foreach ( $tables as $table ) {
$table->clear();
}
}
}
Thus every time myTestFunctional gets a url or clicks to change page it clears out any Doctrine objects stored in memory. Not subtle, but effective and doesn't make the tests themselves more labourious to write!
I'm trying to unit-test my controller (Yii framework).
/**
* #dataProvider provider
*/
public function testActionEdit_view_login($controller){
$user = new CWebUser;
$user->id = 978;
$identity = new UserIdentity('me#test.com', '123456');
$user->login($identity);
$controller->actionEdit();
$output = ob_get_contents();
assertContains('Add/Change Profile Picture:', $output);
assertContains('bio', $output);
assertContains('specialties', $output);
assertContains('change login', $output);
assertContains('New Password', $output);
}
When I do
$user->login($identity);
in order to login, I get the following error:
session_regenerate_id(): Cannot regenerate session id - headers already sent
I've already tried buffering the output by putting this at the beginning of the class:
public static function setUpBeforeClass(){
ob_start();
}
I also put ob_clean() in setUp() and ob_end_clean() in tearDownAfterClass().
Still I get the message that headers have already been sent. There are no spaces or newlines in the file, when I comment out the specific test method, it works perfectly. The login() seems to cause the problem.
Anyone got ideas how to prevent this / maybe unit-test the controller differently?
Thanks,
MrB
Before your call to $user->login, add the following code:
$mockSession = $this->getMock('CHttpSession', array('regenerateID'));
Yii::app()->setComponent('session', $mockSession);
This overwrites the regenerateID method with a method that does nothing.
Adding ob_start() to the bootstrap also works, but there is no output from PHPUnit until everything has completed.
With this method, you can still see the progress as it is happening.
I upgraded from Yii 1.1.7 to 1.1.10 and the regenerateID method was added in 1.1.8 so I got this error today.
Got it. I had included some Yii files before the ob_start(), which seem to have printed the headers. Now I buffer that, too.