How to use query caching in yii2 ActiveRecord - php

I am quoting the guide:
``Query caching is a special caching feature built on top of data caching. It is provided to cache the result of database queries.
Query caching requires a DB connection and a valid cache application component. The basic usage of query caching is as follows, assuming $db is a yii\db\Connection instance:
$result = $db->cache(function ($db) {
// the result of the SQL query will be served from the cache
// if query caching is enabled and the query result is found in the cache
return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
});
``
I do not think that I will manually create db connection in AR classes. So how to do this in my AR models ?
I have asked the same question on yii2 forum but I got no answer. It seems that people do not know how to do query caching in Active Record.

Yii 2 now requires closures to wrap the query. AR does a query eventually so you can put that in the closure. In an AR class, get the db and wrap the query you want to use. The closure has a signature function($db) and you usually need to access more variables, so add use($variable) to make variables visible within the closure.
$db = self::getDb();
$object = $db->cache(function ($db) use($id) {
return self::findOne($id);
});
If you write to the db, the cache above won't know about it until the cache duration expires. So dependency should be added to the cache function to tell it when to invalidate the cache. Dependency gets complicated fast...
http://www.yiiframework.com/doc-2.0/yii-caching-dependency.html

maybe this help: yii2 issues on github
qiangxue commented on 11 Jan 2014
In 2.0, you need to use the following code:
$db->beginCache();
// your db query code here...
$db->endCache();

Related

Codeigniter: DB Cache check if record loaded from Database OR from Cache

I'm implementing a DB cache in Codeigniter and want to check if the record or the cache file EXISTS against the query.
As far as I read the Codeigniter Documentation probably, there is no way to check it. So, can anyone give me a hint if its possible?
Here is the flowchart that I would like to achieve
if (!$this->db->query_exists("QUERY HERE"))
// DO SOMETHING. Query Doesn't exists in CACHE
// Run the query in any case
$this->db->query("QUERY HERE");
Thank you!
It is unclear why you need/want to do this. Assuming query caching is enabled, the Codeigniter DB driver already does this checking and will use a cached query if it exists or will perform the query and cache it if not. The main thing you have to implement is clearing the appropriate cache when you perform record inserts, updates, or deletions that invalidate the cached results.
That said, the easiest way to determine if a cache exists is the CI_DB_Cache::read($sql) method. That method returns FALSE if a matching cache file is not found.
The DB driver has a public property - $CACHE. (Yes, it's an all uppercase variable name.) $CACHE is an instance of the CI_DB_Cache class.
This property will only be defined after a read type query has been performed. That could be a "catch 22" - you can't check for a cache file until a read query is performed, but you want to check before performing one.
Take a look at protected function _cache_init() in /system/database/DB_driver.php to see how the CI_DB_Cache class is instantiated. You might need to implement the same sort of thing in your code.
You might also find it useful to see how $this->CACHE is used in public function query($sql, $binds = FALSE, $return_object = NULL) in /system/database/DB_driver.php.
You might be able to determine if the query in question exists by using the approach that follows. I say might because I have not tested this.
$sql = "SELECT * from some_table";
//check that CACHE is defined
if(isset($this->db->CACHE))
{
$query = $this->db->CACHE->read($sql);
if($query === FALSE)
{
// DO SOMETHING. Query Doesn't exists in CACHE
}
}
$query will be a query result object if the cache is found.
However, you have to delete the cache before you can accomplish
// Run the query in any case
$this->db->query("QUERY HERE");
Without the delete you're going to end up with the same query result object you have already retrieved.
Keep in mind that the Caching class is completely dependent on the URI being requested by the browser. Caching uses the first two URI segments (controller class name and function name) to determine the folder that holds the cache file.

FOSElasticaBundle: Is it possible to change "query_builder_method" in controller?

According to FOSElasticaBundle documentation it is possible to configure application to use custom query builder method like this:
user:
persistence:
elastica_to_model_transformer:
query_builder_method: createSearchQueryBuilder
But is it possible to choose QB method live, e.g. in controller action?
I'd like to be able to control what's being fetched from DB while transforming Elastica results to Doctrine entities. E.g. sometimes I'll want to do eager fetch on some relations, but can't do that by default.
Since FOSElasticaBundle documentation is not very precise, I went through its code and found it impossible to control what query builder is used on controller level.
It is possible to change whole elastica_to_model_transformer to a custom service, but still it's statically defined in configuration. Maybe with some dirty solution it would be possible going this way, but I don't think it's worth it.
I decided to just not using this feature of FOSElasticaBundle. The main problem I had was that when you use fos_elastica.index instead of fos_elastica.finder or elastica repository (in order to get plain not transformed results Elastica\Resultset), there's no findPaginated method with returns Pagerfanta paginator object, which is very helpful in my case.
Fortunately although it's not mentioned in documentation it's possible to create the Pagerfanta this way too, but a little bit more manually.
Here's a code snippet:
//generate ElaticaQuery somehow.
$browseQuery = $browseData->getBrowseQuery();
$search = $this->container->get('fos_elastica.index.indexName.typName');
//create pagerfanta's adapter manually
$adapter = new \Pagerfanta\Adapter\ElasticaAdapterElasticaAdapter($search, $browseQuery);
// now you can create the paginator too.
$pager = new Pagerfanta($adapter);
//do some paging work on it...
$pager->setMaxPerPage($browseData->getPerPage());
try {
$pager->setCurrentPage($browseData->getPage());
} catch(OutOfRangeCurrentPageException $e) {
$pager->setCurrentPage(1);
}
//and get current page results.
/** #var Result[] $elasticaResults */
$elasticaResults = $pager->getCurrentPageResults();
// we have to grab ids manyally, but it's done the same way inside FOSElasticaBundle with previous approach
$ids = array();
foreach($elasticaResults as $elasticaResult) {
$ids[] = $elasticaResult->getId();
}
//use regular Doctrine's repository to fetch Entities any way you want.
$entities = $this->getDoctrine()->getRepository(MyEntity::class)->findByIdentifiers($ids);
This actually has a few advantages. In general it gives you back control over your data and doesn't tie ElasticSearch with Doctrine. Therefore you can resign on fetching data from Doctrine if you have all needed data in ElasticSearch (if they are read only data of course). This lets you optimize your application performance but reducing amount of SQL queries.
The code above may be wrapped with some kind of service in order to prevent making mess in controllers.

Caching Database Query - remember

I am using Caching Database Queries. My code is below.
$Categories = \App\Models\Skill\Category_Model::paginate(1)->remember(60);
Then I got the below runtime error.
Method remember does not exist.
Am I missing something ?
remember used to be part of Eloquent before at 4.2 but with the new Laravel now it's part of caching itself.
As I quote from Laravel documentation in this link:
Eloquent Caching
Eloquent no longer provides the remember method for caching queries.
You now are responsible for caching your queries manually using the
Cache::remember function. For more information on caching, consult the
full documentation.
Answering your question the best way to cache Database queries in Laravel 5.1:
$value = Cache::remember('Categories', 60, function() {
return \App\Models\Skill\Category_Model::paginate(1);
});
If the item does not exist in the cache, the Closure passed to the remember method will be executed and its result will be placed in the cache.
You may also combine the remember and forever methods, like what you used to do with 4.2 as part of eloquent itself:
$value = Cache::rememberForever('Categories', function() {
return \App\Models\Skill\Category_Model::paginate(1);
});
Since you are using pagination you might want to add postfix to your caching keys like Categories_1

Get the Query Executed in Laravel 3/4

How can I retrieve the raw executed SQL query in Laravel 3/4 using Laravel Query Builder or Eloquent ORM?
For example, something like this:
DB::table('users')->where_status(1)->get();
Or:
(posts (id, user_id, ...))
User::find(1)->posts->get();
Otherwise, at the very least how can I save all queries executed to laravel.log?
Laravel 4+
Note for Laravel 5 users: You'll need to call DB::enableQueryLog() before executing the query. Either just above the line that runs the query or inside a middleware.
In Laravel 4 and later, you have to call DB::getQueryLog() to get all ran queries.
$queries = DB::getQueryLog();
$last_query = end($queries);
Or you can download a profiler package. I'd recommend barryvdh/laravel-debugbar, which is pretty neat. You can read for instructions on how to install in their repository.
Laravel 3
In Laravel 3, you can get the last executed query from an Eloquent model calling the static method last_query on the DB class.
DB::last_query();
This, however, requires that you enable the profiler option in application/config/database.php. Alternatively you could, as #dualed mentioned, enable the profiler option, in application/config/application.php or call DB::profile() to get all queries ran in the current request and their execution time.
You can enable the "Profiler" in Laravel 3 by setting
'profiler' => true,
In your application/config/application.php and application/config/database.php
This enables a bar at the bottom of each page. One of its features is listing the executed queries and how long each one took.
For Eloquent you can just do:
$result->getQuery()->toSql();
But you need to remove the "->get()" part from your query.
I would recommend using the Chrome extension Clockwork with the Laravel package https://github.com/itsgoingd/clockwork. It's easy to install and use.
Clockwork is a Chrome extension for PHP development, extending
Developer Tools with a new panel providing all kinds of information
useful for debugging and profiling your PHP scripts, including
information on request, headers, GET and POST data, cookies, session
data, database queries, routes, visualisation of application runtime
and more. Clockwork includes out of the box support for Laravel 4 and
Slim 2 based applications, you can add support for any other or custom
framework via an extensible API.
Since the profiler is not yet out in Laravel 4, I've created this helper function to see the SQL being generated:
public static function q($all = true)
{
$queries = DB::getQueryLog();
if($all == false) {
$last_query = end($queries);
return $last_query;
}
return $queries;
}
NOTE: Set the $all flag to false if you only want the last SQL query.
I keep this sort of functions in a class called DBH.php (short for Database Helper) so I can call it from anywhere like this:
dd(DBH::q());
Here is the output I get:
In case you are wondering, I use Kint for the dd() formatting.
http://raveren.github.io/kint/
For Laraver 4 it's
DB::getQueryLog()
Here is a quick Javascript snippet you can throw onto your master page template.
As long as it's included, all queries will be output to your browser's Javascript Console.
It prints them in an easily readable list, making it simple to browse around your site and see what queries are executing on each page.
When you're done debugging, just remove it from your template.
<script type="text/javascript">
var queries = {{ json_encode(DB::getQueryLog()) }};
console.log('/****************************** Database Queries ******************************/');
console.log(' ');
queries.forEach(function(query) {
console.log(' ' + query.time + ' | ' + query.query + ' | ' + query.bindings[0]);
});
console.log(' ');
console.log('/****************************** End Queries ***********************************/');
</script>
Laravel 5
Note that this is the procedural approach, which I use for quick debugging
DB::enableQueryLog();
// Run your queries
// ...
// Then to retrieve everything since you enabled the logging:
$queries = DB::getQueryLog();
foreach($queries as $i=>$query)
{
Log::debug("Query $i: " . json_encode($query));
}
in your header, use:
use DB;
use Illuminate\Support\Facades\Log;
The output will look something like this (default log file is laravel.log):
[2015-09-25 12:33:29] testing.DEBUG: Query 0: {"query":"select * from
'users' where ('user_id' = ?)","bindings":["9"],"time":0.23}
***I know this question specified Laravel 3/4 but this page comes up when searching for a general answer. Newbies to Laravel may not know there is a difference between versions. Since I never see DD::enableQueryLog() mentioned in any of the answers I normally find, it may be specific to Laravel 5 - perhaps someone can comment on that.
You can also listen for query events using this:
DB::listen(function($sql, $bindings, $time)
{
var_dump($sql);
});
See the information from the docs here under Listening For Query Events
Using the query log doesnt give you the actual RAW query being executed, especially if there are bound values.
This is the best approach to get the raw sql:
DB::table('tablename')->toSql();
or more involved:
$query = Article::whereIn('author_id', [1,2,3])->orderBy('published', 'desc')->toSql();
dd($query);
If you are using Laravel 5 you need to insert this before query or on middleware :
\DB::enableQueryLog();
Or as alternative to laravel 3 profiler you can use:
https://github.com/paulboco/profiler
or
https://github.com/barryvdh/laravel-debugbar
in Laravel 4 you can actually use an Event Listener for database queries.
Event::listen('illuminate.query', function($sql, $bindings)
{
foreach ($bindings as $val) {
$sql = preg_replace('/\?/', "'{$val}'", $sql, 1);
}
Log::info($sql);
});
Place this snippet anywhere, e.g. in start/global.php. It'll write the queries to the info log (storage/log/laravel.log).
Event::listen('illuminate.query', function($sql, $param)
{
\Log::info($sql . ", with[" . join(',', $param) ."]<br>\n");
});
put it in global.php it will log your sql query.
The Loic Sharma SQL profiler does support Laravel 4, I just installed it. The instructions are listed here. The steps are the following:
Add "loic-sharma/profiler": "1.1.*" in the require section
in composer.json
Perform self-update => php composer.phar self-update in the console.
Perform composer update => php composer.phar update loic-sharma/profiler in the console as well
`
Add 'Profiler\ProfilerServiceProvider', in the provider array in
app.php
Add 'Profiler' => 'Profiler\Facades\Profiler', in the
aliasses array in app.php as well
Run php artisan config:publish loic-sharma/profiler in the console
Last query print
$queries = \DB::getQueryLog();
$last_query = end($queries);
// Add binding to query
foreach ($last_query['bindings'] as $val) {
$last_query['query'] = preg_replace('/\?/', "'{$val}'", $last_query['query'], 1);
}
dd($last_query);
L4 one-liner
(which write query):
$q=\DB::getQueryLog();dd(end($q));
Laravel 3
Another way to do this is:
#config/database.php
'profiler' => true
For all Queries result:
print_r(DB::profiler());
For last Result:
print_r(DB::last_query());
To get the last executed query in laravel,We will use DB::getQueryLog() function of laravel it return all executed queries. To get last query we will use end() function which return last executed query.
$student = DB::table('student')->get();
$query = DB::getQueryLog();
$lastQuery = end($query);
print_r($lastQuery);
I have taken reference from http://www.tutsway.com/how-to-get-the-last-executed-query-in-laravel.php.
There is very easy way to do it, from your laravel query just rename any column name, it will show you an error with your query.. :)
In Laravel 8.x you can listen to the event by registering your query listener in a service provider as documented in laravel.com website.
//header
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
public function boot()
{
DB::listen(function ($query) {
Log::debug("SQL : " . $query->sql);
});
}
You can then see all the queries in the laravel.log file inside storage\logs\laravel.log

Escape SQL queries in PHP PostgreSQL

I have a website with lots of PHP files (really a lot...), which use the pg_query and pg_exec functions which do not
escape the apostrophe in Postgre SQL queries.
However, for security reasons and the ability to store names with
apostrophe in my database I want to add an escaping mechanism for my database input. A possible solution is to go
through every PHP file and change the pg_query and pg_exec to use pg_query_params but it is both time consuming
and error prone. A good idea would be to somehow override the pg_query and pg_exec to wrapper functions that would
do the escaping without having to change any PHP file but in this case I guess I will have to change PHP function
definitions and recompile it which is not very ideal.
So, the question is open and any ideas that would
allow to do what I want with minimum time consumption are very welcome.
You post no code but I guess you have this:
$name = "O'Brian";
$result = pg_query($conn, "SELECT id FROM customer WHERE name='{$name}'");
... and you'd need to have this:
$name = "O'Brian";
$result = pg_query_params($conn, 'SELECT id FROM customer WHERE name=$1', array($name));
... but you think the task will consume an unreasonable amount of time.
While it's certainly complex, what alternatives do you have? You cannot override pg_query() but it'd be extremely simple to search and replace it for my_pg_query(). And now what? Your custom function will just see strings:
SELECT id FROM customer WHERE name='O'Brian'
SELECT id FROM customer WHERE name='foo' OR '1'='1'
Even if you manage to implement a bug-free SQL parser:
It won't work reliably with invalid SQL.
It won't be able to determine whether the query is the product of intentional SQL injection.
Just take it easy and fix queries one by one. It'll take time but possibly not as much as you think. Your app will be increasingly better as you progress.
This is a perfect example of when a database layer and associated API will save you loads of time. A good solution would be to make a DB class as a singleton, which you can instantiate from anywhere in your app. A simple set of wrapper functions will allow you to make all queries to the DB go through one point, so you can then alter the way they work very easily. You can also change from one DB to another, or from one DB vendor to another without touching the rest of the app.
The problem you are having with escaping is properly solved by using the PDO interface, instead of functions like pg_query(), which makes escaping unnecessary. Seeing as you'll have to alter everywhere in your app that uses the DB, you may as well refactor to use this pattern at the same time as it'll be the same amount of work.
class db_wrapper {
// Singleton stuff
private $instance;
private function __construct() {
// Connect to DB and store connection somewhere
}
public static function get_db() {
if (isset($instance)) {
return $instance;
}
return $instance = new db_wrapper();
}
// Public API
public function query($sql, array $vars) {
// Use PDO to connect to database and execute query
}
}
// Other parts of your app look like this:
function do_something() {
$db = db_wrapper::get_db();
$sql = "SELECT * FROM table1 WHERE column = :name";
$params = array('name' => 'valuename');
$result = $db->query($sql, $params);
// Use $result for something.
}

Categories