In core I have aforementioned class (MemcachedStore), which has put method as:
$a = $this->memcached->set(
$this->prefix.$key, $value, $this->calculateExpiration($seconds)
);
Memcached's set method accepts three parameters: key, value, seconds_to_store_in_cache
My questions is: Why would I need to calculate expiration Carbon::now() + seconds passed to this function?
Result: This is not working. Memcached returns 0 "success". But entry is not written. (With "get" method I cant find it)
If I just pass seconds (override in core class), everything works as expected
UPD! Nothing to do with laravel or lumen
Actually Memchached's set method can accept third parameter as unixtimestamp, but in this case memcached time (os time) should be correct. Memcached time can be checked by Memchached's getStats
I have an API written in Laravel. There is the following code in it:
public function getData($cacheKey)
{
if(Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
// if cache is empty for the key, get data from external service
$dataFromService = $this->makeRequest($cacheKey);
$dataMapped = array_map([$this->transformer, 'transformData'], $dataFromService);
Cache::put($cacheKey, $dataMapped);
return $dataMapped;
}
In getData() if cache contains necessary key, data returned from cache.
If cache does not have necessary key, data is fetched from external API, processed and placed to cache and after that returned.
The problem is: when there are many concurrent requests to the method, data is corrupted. I guess, data is written to cache incorrectly because of race conditions.
You seem to be experiencing some sort of critical section problem. But here's the thing. Redis operations are atomic however Laravel does its own checks before calling Redis.
The major issue here is that all concurrent requests will all cause a request to be made and then all of them will write the results to the cache (which is definitely not good). I would suggest implementing a simple mutual exclusion lock on your code.
Replace your current method body with the following:
public function getData($cacheKey)
{
$mutexKey = "getDataMutex";
if (!Redis::setnx($mutexKey,true)) {
//Already running, you can either do a busy wait until the cache key is ready or fail this request and assume that another one will succeed
//Definately don't trust what the cache says at this point
}
$value = Cache::rememberForever($cacheKey, function () { //This part is just the convinience method, it doesn't change anything
$dataFromService = $this->makeRequest($cacheKey);
$dataMapped = array_map([$this->transformer, 'transformData'], $dataFromService);
return $dataMapped;
});
Redis::del($mutexKey);
return $value;
}
setnx is a native redis command that sets a value if it doesn't exist already. This is done atomically so it can be used to implement a simple locking mechanism, but (as mentioned in the manual) will not work if you're using a redis cluster. In that case the redis manual describes a method to implement distributed locks
In the end I came to the following solution: I use retry() function from Laravel 5.5 helpers to get cache value until it is written there normally with interval of 1 second.
I need configure default lifetime from cache Adapter, but something weird has been happening, the follows don't works!? :/
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
// in seconds; applied to cache items that don't define their own lifetime
// 0 means to store the cache items indefinitely (i.e. until the files are deleted)
$cache = new FilesystemAdapter('my_namespace', 5); // <-- default lifetime 5 seconds
$latestNews = $cache->getItem('latest_news');
if (!$latestNews->isHit()) {
$news = ['title' => '...', 'createdAt' => (new \DateTime())->format('Y-m-d H:i:s')];
$cache->save($latestNews->set($news));
} else {
$news = $latestNews->get();
}
Reference http://symfony.com/doc/current/components/cache/cache_pools.html#filesystem-cache-adapter
The first time, the cached file content shows:
2147483647 <-- 2038-01-18 22:14:07 :/ ?
latest_news
a:2:{s:5:"title";s:3:"...";s:9:"createdAt";s:19:"2016-10-07 09:16:50";}
and of course this item don't expire after 5 seconds :/ (I've cleared the cache directory manually).
On the other hand, if we use $latestNews->expiresAfter(5); all works fine:
1475849350 <-- 2016-10-07 10:09:10 \o/ OK
latest_news
a:2:{s:5:"title";s:3:"...";s:9:"createdAt";s:19:"2016-10-07 10:09:05";}
Reference http://symfony.com/doc/current/components/cache/cache_items.html#cache-item-expiration
5 seconds after the item expired correctly.
I tested that with Symfony\Component\Cache\Adapter\ApcuAdapter and occurs the same problem too.
What happens with default lifetime (constructor parameter) in cache adapters ? I missing something here :/ ?
Is an old issue [Cache] Fix default lifetime being ignored that affect framework version prior to the 3.1
Upgrading the Symfony framework should fix it.
I try to set a Cookie in Laravel 4 in a specific route.
Unfortunately, setting the Cookie does only work in the global App::after() filter.
First thing I tried was returning a response with a Cookiefrom my Controller.
This doesn't work:
return Response::make($view)->withCookie(Cookie::make('foo','bar'));
However, this does:
return Response::make()->withCookie(Cookie::make('foo','bar'));
But does not solve my problem.
Next I tried it with an after filter as follows.
Route::filter('cookie', function($route, $request, $response)
{
$response->withCookie(Cookie::make('foo', 'bar'));
});
This does not work either.
Next, I tried it using Cookie::queue(), which I've found in another answer - with no luck.
The only place the Cookie is set properly is in App::after().
App::after(function($request, $response)
{
$response->withCookie(Cookie::make('foo', 'bar'));
});
Besides I'm pretty sure that one of the other approaches should work, this solution doesn't give me the control I'm looking for.
I'm running Laravel v4.0.9.
Try this tested, working code.
Specify expiration time (in minutes from now). Dont you use some cookie extension in your browser, which may protect/blacklist specified cookies from being modified...
Route::get('cookieset', function(){
$cookie = Cookie::make('foo', 'bar', 60);
return Redirect::to('cookieget')->withCookie($cookie);
});
Route::get('cookieget', function(){
dd(Cookie::get('foo'));
});
... not knowing if 'mock' is the right word.
Anyway, I have an inherited code-base that I'm trying to write some tests for that are time-based. Trying not to be too vague, the code is related to looking at the history of an item and determining if that item has now based a time threshold.
At some point I also need to test adding something to that history and checking that the threshold is now changed (and, obviously, correct).
The problem that I'm hitting is that part of the code I'm testing is using calls to time() and so I'm finding it really hard to know exactly what the threshold time should be, based on the fact that I'm not quite sure exactly when that time() function is going to be called.
So my question is basically this: is there some way for me to 'override' the time() call, or somehow 'mock out' the time, such that my tests are working in a 'known time'?
Or do I just have to accept the fact that I'm going to have to do something in the code that I'm testing, to somehow allow me to force it to use a particular time if need be?
Either way, are there any 'common practices' for developing time-sensitive functionality that is test friendly?
Edit:
Part of my problem, too, is the fact that the time that things occurred in history affect the threshold. Here's an example of part of my problem...
Imagine you have a banana and you're trying to work out when it needs to be eaten by. Let's say that it will expire within 3 days, unless it was sprayed with some chemical, in which case we add 4 days to the expiry, from the time the spray was applied. Then, we can add another 3 months to it by freezing it, but if it's been frozen then we only have 1 day to use it after it thaws.
All of these rules are dictated by historical timings. I agree that I could use the Dominik's suggestion of testing within a few seconds, but what of my historical data? Should I just 'create' that on the fly?
As you may or may not be able to tell, I'm still trying to get a hang of all of this 'testing' concept ;)
I recently came up with another solution that is great if you are using PHP 5.3 namespaces. You can implement a new time() function inside your current namespace and create a shared resource where you set the return value in your tests. Then any unqualified call to time() will use your new function.
For further reading I described it in detail in my blog
Carbon::setTestNow(Carbon $time = null) makes any call to Carbon::now() or new Carbon('now') return the same time.
https://medium.com/#stefanledin/mock-date-and-time-with-carbon-8a9f72cb843d
Example:
public function testSomething()
{
$now = Carbon::now();
// Mock Carbon::now() / new Carbon('now') to always return the same time
Carbon::setTestNow($now);
// Do the time sensitive test:
$this->retroEncabulator('prefabulate')
->assertJsonFragment(['whenDidThisHappen' => $now->timestamp])
// Release the Carbon::now() mock
Carbon::setTestNow();
}
The $this->retroEncabulator() function needs to use Carbon::now() or new Carbon('now') internally of course.
For those of you working with symfony (>= 2.8): Symfony's PHPUnit Bridge includes a ClockMock feature that overrides the built-in methods time, microtime, sleep and usleep.
See: http://symfony.com/doc/2.8/components/phpunit_bridge.html#clock-mocking
You can mock time for test using Clock from ouzo-goodies. (Disclaimer: I wrote this library.)
In code use simply:
$time = Clock::now();
Then in tests:
Clock::freeze('2014-01-07 12:34');
$result = Class::getCurrDate();
$this->assertEquals('2014-01-07', $result);
I had to simulate a particular request in future and past date in the app itself (not in Unit Tests). Hence all calls to \DateTime::now() should return the date that was previously set throughout the app.
I decided to go with this library https://github.com/rezzza/TimeTraveler, since I can mock the dates without altering all the codes.
\Rezzza\TimeTraveler::enable();
\Rezzza\TimeTraveler::moveTo('2011-06-10 11:00:00');
var_dump(new \DateTime()); // 2011-06-10 11:00:00
var_dump(new \DateTime('+2 hours')); // 2011-06-10 13:00:00
Personally, I keep using time() in the tested functions/methods. In your test code, just make sure to not test for equality with time(), but simply for a time difference of less than 1 or 2 (depending on how much time the function takes to execute)
You can overide php's time() function using the runkit extension. Make sure you set runkit.internal_overide to On
Using [runkit][1] extension:
define('MOCK_DATE', '2014-01-08');
define('MOCK_TIME', '17:30:00');
define('MOCK_DATETIME', MOCK_DATE.' '.MOCK_TIME);
private function mockDate()
{
runkit_function_rename('date', 'date_real');
runkit_function_add('date','$format="Y-m-d H:i:s", $timestamp=NULL', '$ts = $timestamp ? $timestamp : strtotime(MOCK_DATETIME); return date_real($format, $ts);');
}
private function unmockDate()
{
runkit_function_remove('date');
runkit_function_rename('date_real', 'date');
}
You can even test the mock like this:
public function testMockDate()
{
$this->mockDate();
$this->assertEquals(MOCK_DATE, date('Y-m-d'));
$this->assertEquals(MOCK_TIME, date('H:i:s'));
$this->assertEquals(MOCK_DATETIME, date());
$this->unmockDate();
}
In most cases this will do. It has some advantages:
you don't have to mock anything
you don't need external plugins
you can use any time function, not only time() but DateTime objects as well
you don't need to use namespaces.
It's using phpunit, but you can addapt it to any other testing framework, you just need function that works like assertContains() from phpunit.
1) Add below function to your test class or bootstrap. Default tolerance for time is 2 secs. You can change it by passing 3rd argument to assertTimeEquals or modify function args.
private function assertTimeEquals($testedTime, $shouldBeTime, $timeTolerance = 2)
{
$toleranceRange = range($shouldBeTime, $shouldBeTime+$timeTolerance);
return $this->assertContains($testedTime, $toleranceRange);
}
2) Testing example:
public function testGetLastLogDateInSecondsAgo()
{
// given
$date = new DateTime();
$date->modify('-189 seconds');
// when
$this->setLastLogDate($date);
// then
$this->assertTimeEquals(189, $this->userData->getLastLogDateInSecondsAgo());
}
assertTimeEquals() will check if array of (189, 190, 191) contains 189.
This test should be passed for correct working function IF executing test function takes less then 2 seconds.
It's not perfect and super-accurate, but it's very simple and in many cases it's enough to test what you want to test.
Simplest solution would be to override PHP time() function and replace it with your own version. However, you cannot replace built-in PHP functions easily (see here).
Short of that, the only way is to abstract time() call to some class/function of your own that would return the time you need for testing.
Alternatively, you could run the test system (operating system) in a virtual machine and change the time of the entire virtual computer.
Here's an addition to fab's post. I did the namespace based override using an eval. This way, I can just run it for tests and not the rest of my code. I run a function similar to:
function timeOverrides($namespaces = array()) {
$returnTime = time();
foreach ($namespaces as $namespace) {
eval("namespace $namespace; function time() { return $returnTime; }");
}
}
then pass in timeOverrides(array(...)) in the test setup so that my tests only have to keep track of what namespaces time() is called in.
Disclaimer: I wrote this library.
If you are free to install php extensions in your system, you could then use https://github.com/slope-it/clock-mock.
That library requires ext-uopz >= 6.1.1 and by using ClockMock::freeze and ClockMock::reset you can move the internal php clock to whatever date and time you like. The cool thing about it is that it requires zero modifications to your production code because it mocks transparently \DateTime and \DateTimeImmutable objects as well as some of the global functions (e.g. date(), time(), etc...).
You can use libfaketime
https://github.com/wolfcw/libfaketime
LD_PRELOAD=src/libfaketime.so.1 FAKETIME="#2020-01-01 11:12:13" phpunit
It will be as if you changed your system clock but only for that process, and it will work regardless of how low level your phpcode is
(Except if they use an external API call to get the time of course !)