Yii SQL Profiling of AJAX calls - php

Yii offers a setting that profiles all the SQL calls with the execution time of each (CProfileLogRoute). Except it does not work for ajax calls. How can I access this data?
I am trying to find the bottleneck of an ajax call that opens up a login popup...
On a similar note, does the execution time shown in CProfileLogRoute includes the network trip to the SQL server if any? My database is hosted by Amazon's RDS, and I want to know if that's where the bottleneck is (it seems fine locally).

You can try to extend CFileLogRoute in the manner proposed below and enable it in application's configuration like this:
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
array(
'class'=>'ProfileLogRoute',
'levels' => "profile"
)
)
)
In this case all profiled queries will be written to a log file located in protected/runtime directory (overriding of processLogs method is implemented likewise summary report of CProfileWebRoute...unfortunately CProfileWebRoute is derived from CWebLogRoute):
<?php
class ProfileLogRoute extends CFileLogRoute
{
protected function processLogs($logs)
{
$logFile=$this->getLogPath().DIRECTORY_SEPARATOR.$this->getLogFile();
if(#filesize($logFile)>$this->getMaxFileSize()*1024)
$this->rotateFiles();
$fp=#fopen($logFile,'a');
#flock($fp,LOCK_EX);
$profileStack = array();
$profileResults = array();
foreach ($logs as $log)
{
if ($log[1] === CLogger::LEVEL_PROFILE)
{
$message = $log[0];
if (!strncasecmp($message, 'begin:', 6))
{
$log[0] = substr($message,6);
$profileStack[] = $log;
}
else if(!strncasecmp($message, 'end:', 4))
{
$token = substr($message,4);
if(($last = array_pop($profileStack)) !== null && $last[0] === $token)
{
$info = array(
'delta' => $log[3] - $last[3],
'category' => $last[2],
'time' => $last[3]
);
$this->aggregateResult($token, $info, $profileResults);
}
else
{
throw new CException(Yii::t('yii','CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.',
array('{token}'=>$token)));
}
}
}
else
{
#fwrite($fp,$this->formatLogMessage($log[0],$log[1],$log[2],$log[3]));
}
}
if (!empty($profileResults))
{
$now = microtime(true);
while(($last = array_pop($profileStack)) !== null)
{
$info = array(
'delta' => $now - $last[3],
'category' => $last[2],
'time' => $last[3]
);
$token = $last[0];
$this->aggregateResult($token, $info, $profileResults);
}
$entries = array_values($profileResults);
$func = create_function('$a,$b','return $a["total"]<$b["total"]?1:0;');
usort($entries, $func);
foreach ($entries as $entry)
{
$message = sprintf("Min: % 11.8f Max: % 11.8f Total: % 11.8f Calls: % 3d %s", $entry['min'], $entry['max'], $entry['total'], $entry['calls'], $entry['token']);
#fwrite($fp, $this->formatLogMessage($message, CLogger::LEVEL_PROFILE, $entry['category'], $entry['time']));
}
}
#flock($fp,LOCK_UN);
#fclose($fp);
}
protected function aggregateResult($token, $info, &$results)
{
if (isset($results[$token]))
{
if ($info['delta'] < $results[$token]['min'])
$results[$token]['min'] = $info['delta'];
else if($info['delta'] > $results[$token]['max'])
$results[$token]['max'] = $info['delta'];
$results[$token]['calls']++;
$results[$token]['total'] += $info['delta'];
return;
}
$results[$token] = array(
'token' => $token,
'calls' => 1,
'min' => $info['delta'],
'max' => $info['delta'],
'total' => $info['delta'],
'category' => $info['category'],
'time' => $info['time']
);
}
}
If you want to know how Yii measures a time of SQL execution you can look at the source code of CDbCommand class - queryInternal method, to be more precise. Profiling is between Yii::beginProfile('system.db.CDbCommand.query...') and Yii::endProfile('system.db.CDbCommand.query...') calls. As you can see both these calls are within the same method, as the result the time of profiling doesn't include a network transfer. Probably, the issue is that your remote database server runs under huge load.

There is a property of the CProfileLogRoute class called ignoreAjaxInFireBug which defaults to "true" by setting it to false you can get your profiling info on ajax requests.
http://www.yiiframework.com/doc/api/1.1/CWebLogRoute#ignoreAjaxInFireBug-detail
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
array(
'class'=>'CProfileLogRoute',
'ignoreAjaxInFireBug'=>false,
'levels'=>'error, warning, trace, info, profile',
),
),
I don't believe ou need to use firebug for that work (it's kind of a misnomer). If you do however, just download firefox and install firebug and then your problem is still solved.

Related

Symfony shows error 503 - How to solve strange caching error in PhpFilesAdapter?

I am running a project using Symfony 6.1.11. While everything works fine most of the time, every now and than something breaks down and the page only responds with an error 503.
Using some logging I was able track down the problem to the Symfony\Component\Cache\Adapter\PhpFilesAdapter tries to include a cache file:
// include of file path/to/symfony/var/cache/prod/pools/system/j-38nw2TzR/0/X/EqYiQ19j2NoAELUDbLoQ fails
} elseif (false === $values[$id] = include $value->file) {
unset($values[$id], $this->values[$id]);
$missingIds[] = $id;
}
While clearing the cache solves the problem, I would like to fix it to avoid future problems.
Any idea how to do this? Can there be anything wrong with the cache config?
Details
By adding some debug output the my project and the Symfony file, I was able to track down the problem to Symfony\Component\Cache\Adapter\PhpFilesAdapter which is called by Doctrine ORM to create an SQL query:
// Symfony\Component\Cache\Adapter\PhpFilesAdapter
class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface {
...
// Using direct / custom logging instead of Symfony tools to not call
// Symfony code from here...
private function log(string $text) {
file_put_contents(
'/path/to/log.txt',
"$text\n",
FILE_APPEND
);
}
protected function doFetch(array $ids): iterable {
...
set_error_handler($this->includeHandler);
try {
$getExpiry = true;
foreach ($missingIds as $k => $id) {
try {
$file = $this->files[$id] ??= $this->getFile($id);
$this->log("doFetch 1 - $file");
if (isset(self::$valuesCache[$file])) {
$this->log("doFetch 2");
[$expiresAt, $this->values[$id]] = self::$valuesCache[$file];
} elseif (\is_array($expiresAt = include $file)) {
$this->log("doFetch 3");
if ($this->appendOnly) {
self::$valuesCache[$file] = $expiresAt;
}
[$expiresAt, $this->values[$id]] = $expiresAt;
} elseif ($now < $expiresAt) {
$this->log("doFetch 4");
$this->values[$id] = new LazyValue($file);
}
$this->log("doFetch 5");
if ($now >= $expiresAt) {
unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]);
}
} catch (\ErrorException $e) {
unset($missingIds[$k]);
}
}
} finally {
restore_error_handler();
}
...
}
}
This shows the following output in /path/to/log.txt
...
doFetch 1 - path/to/symfony/var/cache/prod/pools/system/j-38nw2TzR/0/X/EqYiQ19j2NoAELUDbLoQ
The log output for doFetch 2 etc. is not called. It seems that calling $expiresAt = include $file fails.
This is what the cache file looks like:
<?php //0b23b7d5e54ce908e69bdd64b26b7e73
return [PHP_INT_MAX, static function () { return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['Doctrine\\ORM\\Query\\ParserResult'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\ParserResult')),
clone ($p['Doctrine\\ORM\\Query\\Exec\\SingleSelectExecutor'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\Exec\\SingleSelectExecutor')),
clone ($p['Doctrine\\ORM\\Query\\ResultSetMapping'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\ResultSetMapping')),
],
null,
[
'Doctrine\\ORM\\Query\\ParserResult' => [
'_sqlExecutor' => [
$o[1],
],
'_resultSetMapping' => [
$o[2],
],
],
'Doctrine\\ORM\\Query\\Exec\\AbstractSqlExecutor' => [
'_sqlStatements' => [
1 => 'SELECT COUNT(u0_.id) AS sclr_0 FROM user u0_ ORDER BY u0_.id DESC',
],
],
'stdClass' => [
'scalarMappings' => [
2 => [
'sclr_0' => 1,
],
],
'typeMappings' => [
2 => [
'sclr_0' => 'integer',
],
],
],
],
$o[0],
[]
); }];
I cannot see any problem here. I also added logging to \Symfony\Component\VarExporter\Internal\Hydrator::hydrate but it was not called. So it seems that there is something wrong with the include it self.
When clearing the cache the problem is gone. When it shows up the next time, the include is again the problem. By now all cache file seems to belong to some Doctrine queries.
Any idea how to solve this or how to further track down the problem?

Laravel only saves first item to database why?

public function download****()
{
// Downloads new externals (if updateable)
$site_id = 1;
$sinceLastId = $this->lastExternalSiteOrderId($site_id);
$api_version = config('services.*****.api_version');
$path = '/admin/api/' . $api_version . '/orders.json';
// GET request - Shopify.php (orders.json)
$result = $this->basicShopifyApi*****()->rest('GET', $path, ['since_id' => $sinceLastId]);
$orders = $result->body->orders;
if (!isset($orders)) {
return Response::json(['message' => 'No new ***** orders to download!'], 204);
} else {
foreach ($orders as $order) {
// Save to External Log
ExternalLog::create([
'data' => $order,
]);
// Gets updatable status
$updatable = $this->updateable($order->id);
// Saves new externals (create), if updateable
if ($updatable) {
$this->updateOrCreateExternals($order, $site_id);
}
}
}
}
// Updates a external role
public function updateOrCreateExternals($request, $site_id) {
// Saves external updates
$external = External::updateOrCreate(
[
'site_order_id' => ['order_id' => $request->id],
'site_id' => $site_id],
[
// 'order_id' => 0, // not required
'site_order_id' => $request->id,
'site_id' => $site_id, // Shopify is 1 (within ShopifyController this is correct)
'site_sub_id' => 0,
'site_account_id' => 0,
'data' => $request,
'site_order_status' => 0,
'order_created_at' => date("Y-m-d H:i:s", strtotime($request->created_at)),
'order_updated_at' => date("Y-m-d H:i:s", strtotime($request->updated_at)),
]);
echo "ShopifyExternalController#updateOrCreate function called!";
// Creates Customers, Orders, Carts, Payments for external, then set Site_Order_Status to 10
$shopifyOrderController = new shopifyOrderController();
$shopifyOrderController->updateOrCreateOrders($site_id);
}
This code works locally, but on dev (staging) server it only saves the first item?
Any ideas, why it won't work on the server?
It is shared hosting, cent os, PHP 7.2, not set up with Laravel Forge or vapor.
Any tips on writing YAML scripts to deploy a site correctly?
I have tried doing "composer dump-autoload" as I recently changed some of the controller names from lowercase to uppercase.
updateOrCreate
Updates only one model rather than many .

Updating table fields on submit button

I am very much new to laravel framework.
I have one form , which i need to update on submit button click.
when submit button clicks control goes to controller.php 's update() function .
But I am unable to edit any field's value.
here is my code.
public function update($id)
{
//echo "<pre>";print_r(Input::all());exit;
$product = $this->product->find($id);
$input = Input::only('designer', 'sku', 'name', 'display_name', 'description', 'price', 'main_category', 'sub_category', 'lead_time', 'sizing', 'woven', 'body_fabric', 'lining_fabric', 'fit', 'primary_color', 'secondary_color', 'care_label', 'neck_type', 'closure', 'trims', 'special_finishings', 'image1', 'image2', 'image3', 'image4', 'image5','top', 'combo_products', 'keywords', 'visibility', 'featured');
//echo "<pre>";print_r($input);exit;
try
{
$this->adminNewProductForm->validate($input);
} catch(\Laracasts\Validation\FormValidationException $e)
{
return Redirect::back()->withInput()->withErrors($e->getErrors());
}
$slug = Str::slug(Input::get('name'));
$slug = $this->product->getSlug($slug);
$input = array_add($input, 'slug', $slug);
DB::transaction(function() use($product, $input)
{
$product->fill($input)->save();
$stock_count = 0;
if(!empty(Input::get('xsmall_size')))
{
$rows = DB::table('products_variants')->where('product_id', $product->id)->where('variant_name', 'XS')->get();
$stock_count += Input::get('xsmall_stock');
if(!empty($rows))
{
DB::table('products_variants')->where('product_id', $product->id)->where('variant_name', 'XS')->update(array('variant_specs' => Input::get('xsmall_size'), 'price_change' => Input::get('xsmall_price'), 'total_stock' => Input::get('xsmall_stock'), 'stock_used' => 0));
} else {
DB::table('products_variants')->insert(array('product_id' => $product->id, 'variant_name' => 'XS', 'variant_specs' => Input::get('xsmall_size'), 'price_change' => Input::get('xsmall_price'), 'total_stock' => Input::get('xsmall_stock'), 'stock_used' => 0));
}
}
$input = array();
$input['flagship_status'] = Input::get('flagship_status');
if(Input::get('flagship_status'))
{
$input['stock_count'] = Input::get('small_stock');
}else {
$input['stock_count'] = $stock_count;
}
$product->fill($input)->save();
});
//echo "<pre>";print_r(Input::all());exit;
return Redirect::back()->withFlashMessage('Product Updated Successfully!');
}
Also I cant understand , what is going on by this line ? because i did not find validate function anywhere in my code.
$this->adminNewProductForm->validate($input);
I need to update table products not products_variants.
validate is inherited from the FormRequst class.
https://laravel.com/api/5.0/Illuminate/Foundation/Http/FormRequest.html#method_validate
You've provided too much code and too little information. You said you need to update a specific table, but yet there are two lines where you are very intentionally manually updating a database entry.
This is one of them:
DB::table('products_variants')->where('product_id', $product->id)->where('variant_name', 'XS')->update(array('variant_specs' => Input::get('xsmall_size'), 'price_change' => Input::get('xsmall_price'), 'total_stock' => Input::get('xsmall_stock'), 'stock_used' => 0));
When you call this:
$product->fill($input)->save();
It also saves 'dirty' (modified) models that also belong to it, which can include products_variants relationships. From the sound of it, you are incorrectly applying changes directly through SQL, and then the model's save method is overwriting it.
You seem unclear about what your code is actually doing, and I would strongly suggest simplifying it down and adding in code as you begin to understand what each line does. I think your question is the byproduct of copying an example and adding your own work without understanding how Laravel handles relationships and models. There is almost never a good reason to use raw SQL or DB statements.

Cakephp not rendering views

I have a controller, that doesn't render a view (the file is present). It just simply shows a blank page.
Also it happens only on staging server - two other dev environments work fine.
Here's the code:
function category($catId = null)
{
if (!isset($catId) || empty($catId)) {
$this->data['category'] = 'all';
$this->data['categories'] = $this->ShopCat->find('all',array('order'=>array('ShopCat.title ASC')));
$this->paginate = array(
'limit' => 9,
'order' => array('ShopProd.featured DESC','ShopProd.title ASC')
);
$this->data['products'] = $this->paginate('ShopProd');
} else {
$catId = (int) $catId;
$this->ShopCat->id = $catId;
if (!$this->ShopCat->exists($catId)) $this->cakeError('error404');
$this->data['category'] = $this->ShopCat->find('first', array('ShopCat.id' => $catId));
$this->data['categories'] = $this->ShopCat->find('all',array('order'=>array('ShopCat.title ASC')));
$this->paginate = array(
'conditions' => array('ShopProd.shop_cat_id' => $catId),
'limit' => 9
);
$this->data['products'] = $this->paginate('ShopProd');
}
}
Why isn't this working? Cause I have no ideas ...
UPDATE : the whole controller code runs ok, it just simply doesn't render anything. In other controller methods - all fine, works perfectly.
UPDATE : issue resolved, thanks to everyone :) it was an error in a view file.
Your $catId will always exist. You have declared in the function.
Maybe is more useful updated your first if to
if (empty($catId)) {...}
Do you have imported the another model in your controller?
Like: $uses = array('ShopCat', 'ShopProd');
or use App::import('Model', 'ShopCat') before $this->find
Figured it out - there was an error in a view file.

What is AuthComponent::getModel() alternative in cakePHP2x?

I am using a Remember Me Component. Actually, migrating a CakePHP 1.3 app to CakePHP 2x. I am stuck with this LAST PIECE of code that is RememberMeComponent.
The script which I see here to SET the cookie is :
function make( ) {
$data = array(
$this->ident_field => $this->_create_token( ),
$this->token_field => $this->_create_token( ),
);
$this->Cookie->name = $this->cookie_name;
$this->Cookie->time = $this->period;
$this->Cookie->key = base64url_encode(implode('::', $data));
$this->Cookie->secure = true;
$this->Auth->getModel()->save(array($this->Auth->userModel => array_merge(array('id' => $this->Auth->user('id')), $data)), false);
}
and checks with :
function check( ) {
$cookie = $this->Cookie->read($this->cookie_name);
if (empty($cookie)) {
return false;
}
$data = explode('::', base64url_decode($cookie));
$user = $this->Auth->getModel( )->find('first', array(
'conditions' => array(
$this->Auth->userModel.'.ident' => $data[0],
),
));
if ( ! $user) {
return false;
}
function base64url_encode is defined in bootstrap - so, it is valid function.
Now there is line:
$this->Auth->getModel()->save(array($this->Auth->userModel => array_merge(array('id' => $this->Auth->user('id')), $data)), false);
That is giving me an error:
Error: Call to undefined method AuthComponent::getModel()
File: /var/www/FlintStones/Controller/Component/RememberMeComponent.php
I checked Auth Component documentation but, it did not have any option where I could find the model for auth.
Thanks in advance.
PS: We cannot directly move to Auto Login (as you might have that in mind) or if you can also refer to a quick-step-by-step, please share. I might even consider that but, so far it is just to get the Auth model.
I had the same issue in the same component.
How to get $settings data out of CakePHP 2.0 FormAuthenticate object
Summary:
Use $this->Auth->userModel to get the model. If the value is null, it will default to 'User'.

Categories