I'm trying to convert a certain excel sheet to a php app.
Within the excel sheet you could do time calculations as eg:
A starting time A1 (could be negative), and for every day you add or substract a certain amount of hours and minutes.
- A1= -10:59
- A2= -7:36
- A3= -18:35 (A1 + A2)
- B2= 4:24
- B3= -14:11 (A3 + B2)
And so on ...
Here is some code i've tested with without succes ofcourse...
$startTime = new \DateTime('00:00:00');
$startVal = new \DateInterval('PT10H59M');
$startTime->sub($startVal); // -10:59:00
$timeSpan = new \DateInterval('PT7H36M'); // 7:36:00
$addTime = $startTime->add($timeSpan);
What is the best way to solve this in PHP? I've been testing around with DateTime but this won't allow me te start with a negative time value.
Hope someone can give me a hand.
Kind regards,
Jochem
I have written a simple class to handle this. It works for your given example but I was unable to test further as I cannot get negative times in my version of Excel. Let me know if it fails at some point.
class MyTime
{
private $positive = true;
private $hour=0;
private $minute=0;
/**
* MyTime constructor.
* Split the given time string into hours and minutes and whether it is positive or negative
*
* #param string $timeString In the format '-10:59', '4:35', or optionally just minutes '24'
*/
public function __construct($timeString)
{
if(!empty($timeString))
{
if(strpos($timeString,'-')===0)
{
$this->positive = false;
$timeString = substr($timeString,1);
}
$timeParts = explode(':',$timeString);
if(!empty($timeParts))
{
if(count($timeParts) == 1)
{
$this->hour = 0;
$this->minute = intval($timeParts[0]);
}
else
{
$this->hour = intval($timeParts[0]);
$this->minute = intval($timeParts[1]);
}
}
}
}
public function getHour()
{
return $this->hour;
}
public function getMinute()
{
return $this->minute;
}
/**
* Convert into minutes either negative or positive
* #return int
*/
public function inMinutes()
{
$minutes = ($this->hour*60)+$this->minute;
if(!$this->positive)
{
$minutes *= -1;
}
return $minutes;
}
/**
* Convert back to a string for output
* #return string
*/
public function __toString()
{
return ($this->positive?'':'-').$this->getHour().':'.str_pad($this->getMinute(),2,'0',STR_PAD_LEFT);
}
/**
* Add on the given time which could be negative
* #param MyTime $time
*
* #return $this
*/
public function add(MyTime $time)
{
$newMinutes = $this->inMinutes() + $time->inMinutes();
if($newMinutes<0)
{
$this->hour = (int) ceil($newMinutes/60);
}
else
{
$this->hour = (int) floor($newMinutes/60);
}
$this->minute = abs($newMinutes-($this->hour*60));
if($newMinutes<0)
{
$this->positive = false;
$this->hour *= -1;
}
else
{
$this->positive = true;
}
return $this;
}
}
$time = new MyTime('-10:59');
echo $time."\n";
echo $time->add(new MyTime('-7:36'))."\n";
echo $time->add(new MyTime('4:24'))."\n";
echo $time->add(new MyTime('18:32'))."\n";
$time = new MyTime('10:59');
echo $time."\n";
echo $time->add(new MyTime('-59'))."\n";
I'm using PHP for my as my backend for my webservice, I need the webservice to be able to set a rate limit of maximum of 1000 requests within the last 24 hours, based off the users PHP session. Is that possible to do it without using a database and just rate limit using only PHP. I have currently already made a rate limiter for 1 request per second per session, but I am looking to set a rate limit of 1000 requests per session in the last 24 hours. PS I'm new to PHP, any help would be great.
Here the code I did for the 1 per second rate limit.
class gaqsession {
public $lastrequest;
public function __construct() {
$this->lastrequest = time();
}
public function ratelimited() {
if($this->lastrequest == time()) {
$this->lastrequest = time();
return true;
} else {
$this->lastrequest = time();
return false;
}
}
}
You can do this provided your sessions are set up to last at least 24 hours. The only real trick is saving rate counts in the session in a way that allows you to maintain them within a 24 hour window. You can do that with a simple array that uses time as keys. Here's a class that adds some features around an array like that in order to easily manage the data, along with some example code that runs it through it's paces.
/**
* Class RateLimitCounter
* Track number of events by time. Intended to be set on a session.
*/
class RateLimitCounter
{
// The time -> request count data
private $timeline = [];
/**
* Log an event in the timeline
*/
public function increment()
{
$now = time();
if (!array_key_exists($now, $this->timeline))
{
$this->timeline[$now] = 0;
}
$this->timeline[$now]++;
}
/**
* Return the total number of events logged in the counter
* #return int
*/
public function getTotal()
{
return array_sum($this->timeline);
}
/**
* Remove any timeline data older than 24 hours
*/
private function trim()
{
// Get the current time
$now = time();
// Time is in seconds, so subtract 1 day worth of seconds
$timeFloor = $now - 86400;
// Filter out all timeline entries more than 24 hours old
$this->timeline = array_filter($this->timeline, function ($key) use ($timeFloor) {
return $key > $timeFloor;
}, ARRAY_FILTER_USE_KEY);
}
public function __serialize(): array
{
return [
'timeline' => $this->timeline
];
}
/**
* Wake up! Set the timeline data and trim data older than 24 hours
* #param array $data
*/
public function __unserialize(array $data): void
{
$this->timeline = $data['timeline'];
$this->trim();
}
}
/**
* Verify that the rate limit has not been exceeded. Bail out if it has been.
* #param $counter
* #return bool
*/
function rateLimit($counter)
{
$limit = 1000;
if ($counter->getTotal() > $limit)
{
// Do whatever you need to here, throw an exception, redirect to an error page, etc.
exit('Rate limit exceeded' . PHP_EOL);
}
return true;
}
/*
* Instantiate a counter - this is what you would do if you do not already have one on the session
*/
$counter = new RateLimitCounter();
/*
* Simulate some prior activity
* Let's get close to the limit then save to the "session"
*/
for ($i = 0; $i <= 995; $i++)
{
$counter->increment();
}
// Mock session
$dummySession = ['counter' => $counter];
// Serialize the session
$serializedSession = serialize($dummySession);
// Unserialize the session
$session = unserialize($serializedSession);
$counter = $session['counter'];
// Do API calls until we hit our limit. There should be 5 remaining.
while (rateLimit($counter))
{
apiCall();
// Don't forget to increment the counter for each call
$counter->increment();
}
// Dummy function to simulate your API call
function apiCall()
{
echo 'Doing something interesting' . PHP_EOL;
}
Hello i'm writing a custom validation for input data in my laravel project. I'm using Carbon::createFromDate()->age in order to get user age and check whether he's 16 or more. I reckon i'm not doing it properly because i get
InvalidArgumentException with such errors:
The separation symbol could not be found
The separation symbol could not be found
A two digit minute could not be found
Unexpected data found.
Trailing data
$rok is a year(1996 eg.) $miesiac is a month and $dzien is a Day. Pesel is an unique ID number of a Polish citizen. From pesel i get the date(year, month, day)
I'm getting some big numbers and i don't know what they mean Here's dd: "year:21586738427 month:1900167 day:9001727"
Here's code of my AppServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Carbon\Carbon;
use Validator;
use Log;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//Custom validator for pesel validation
Validator::extend('pesel',function($attribute,$value,$parameters)
{
$v = $value;
//check if psesel is 11 chars long
if (strlen($v) != 11)
{
Log::info("Pesel is not 11 chars long");
return;
}
//check whether all chars are numbers
$aInt = array();
for($i = 0; $i < 11; $i++)
{
$val = substr($v,$i+1);
$valInt = (int) $val;
$aInt[$i] = $valInt;
if(is_nan($aInt[$i]))
{
Log::info("User inserted invalid number");
return;
}
}
//check control sum
$wagi = [1,3,7,9,1,3,7,9,1,3,1];
$sum = 0;
for($i = 0;$i < 11;$i++)
{
$sum += $wagi[$i]*$aInt[$i];
if(Log::info(($sum%10)!=0))
{
return;
}
//count the year,month,and day from pesel
$rok = 1900+$aInt[0]*10+$aInt[1];
if($aInt[2]>=2 && $aInt[2]<8)
{
$rok += floor($aInt[2]/2)*100;
}
if($aInt[2]>=8)
{
$rok -= 100;
}
$miesiac = ($aInt[2]%2)*10+$aInt[3];
$dzien = $aInt[4]*10+$aInt[5];
Log::info("Parsing the date in carbon");
//validate whether user is 16 years or more
$userAge = Carbon::createFromDate($rok, $miesiac, $dzien,'Europe/Warsaw')->age;
if($userAge < 16)
{
Log::info("user is not 16 or more");
return;
}
}
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
Ok i solved the problem. It was fault of my code
$val = substr($v,$i+1);
is wrong
it should be
$val = substr($v,$i,1);
Now it works
We are planning to building real time bidding and we are evaluating performance of PHP compare to Java in terms of throughput/response times etc.
(Java part is taken care by other member of team)
Initial start:
I have a test script which makes 50 http connection to different servers.
1st approach
- I am using curl_multi_init function and I get response under 7 seconds.
2nd approach
- I am using PHP pthreads api and trying to make parallel calls and expecting same response time or less.But total time on average is around 25 seconds
Here is the code
<?php
$g_request_arr = array(
'0' => array(
'request_url' => 'https://www.google.co.uk/?#q=56%2B12'
),
..
..
..
'49'=>array(
'request_url' => 'https://www.google.co.uk/?#q=256%2B132'
)
);
class ChildThread extends Thread {
public function __construct($urls) {
$this->data = $urls;
}
public function run(){
foreach($this->data as $url_info ){
$url = $url_info['request_url'];
file_get_contents($url);
}
$this->synchronized(function($thread){
$thread->notify();
}, $this);
}
}
$thread = new ChildThread($g_request_arr);
$thread->start();
$thread->synchronized(function($thread){
}, $thread);
?>
I want to know what is missing in above code or is it possible to bring the response under 7 seconds.
You are requesting all the data in one thread, here's a better approach:
<?php
class WebRequest extends Stackable {
public $request_url;
public $response_body;
public function __construct($request_url) {
$this->request_url = $request_url;
}
public function run(){
$this->response_body = file_get_contents(
$this->request_url);
}
}
class WebWorker extends Worker {
public function run(){}
}
$list = array(
new WebRequest("http://google.com"),
new WebRequest("http://www.php.net")
);
$max = 8;
$threads = array();
$start = microtime(true);
/* start some workers */
while (#$thread++<$max) {
$threads[$thread] = new WebWorker();
$threads[$thread]->start();
}
/* stack the jobs onto workers */
foreach ($list as $job) {
$threads[array_rand($threads)]->stack(
$job);
}
/* wait for completion */
foreach ($threads as $thread) {
$thread->shutdown();
}
$time = microtime(true) - $start;
/* tell you all about it */
printf("Fetched %d responses in %.3f seconds\n", count($list), $time);
$length = 0;
foreach ($list as $listed) {
$length += strlen($listed["response_body"]);
}
printf("Total of %d bytes\n", $length);
?>
This uses multiple workers, which you can adjust by changing $max. There's not much point in creating 1000 threads if you have 1000 requests to process.
I would like to make a PHP if condition code that will check if the last 10 articles or 10 minutes from the article reading by the user have already elapsed.
E.g.
A user open a page with id = 235 (this id value is in the url localhost/article/235 )
and this id value will be saved in session with a current timestamp and maybe his IP address
Then he read another article and the same will happen.
I need to remember the clicked stuff for another ten clicks and then reset that only for the first row. E.g. after the 10th click the id and timestamp will not became 11th row but will replace the 1st row in the list.
The php condition in CodeIgniter will then check these values and will update the article hit counter value in the articles table and column counter like this:
$this->db->where('id', $id);
$this->db->set('counter', 'counter+1', FALSE);
$this->db->update('articles');
But before calling this code I need to make this check from the session?
How to do that?
I think storing e.g. 10 entries in the session with timestamps per user will be enough.
Just don't save the same page in the session twice.
And the condition will check the current timestamp with the saved one and if it is more than e.g. 10 minutes or the user have read/clicked another 10 articles it will allow the update counter php code.
I don't need to have this bulletproof. Just to disable the increment using browser's refresh button.
So, if he wants to increment the counter he will need to wait ten minutes or read another 10 articles ;)
You should definitely go for Sessions. It saves you bandwidth consumption and is much easier to handle. Unless, of course, you need the data on the client-side, which, by your explanation, I assume you don't. Assuming you went for sessions, all you gotta do is store an array with the data you have. The following code should do it:
$aClicks = $this->session
->userdata('article_clicks');
// Initialize the array, if it's not already initialized
if ($aClicks == false) {
$aClicks = array();
}
// Now, we clean our array for the articles that have been clicked longer than
// 10 minutes ago.
$aClicks = array_filter(
$aClicks,
function($click) {
return (time() - $click['time']) < 600; // Less than 10 minutes elapsed
}
);
// We check if the article clicked is already in the list
$found = false;
foreach ($aClicks as $click) {
if ($click['article'] === $id) { // Assuming $id holds the article id
$found = true;
break;
}
}
// If it's not, we add it
if (!$found) {
$aClicks[] = array(
'article' => $id, // Assuming $id holds the article id
'time' => time()
);
}
// Store the clicks back to the session
$this->session
->set_userdata('article_clicks', $aClicks);
// If we meet all conditions
if (count($aClicks) < 10) {
// Do something
}
I assumne that $clicks is an array with up to ten visited articles. The id is used as key and the timestamp as value. $id is the id of the new article.
$clicks = $this->session->userdata('article_clicks');
//default value
$clicks = ($clicks)? $clicks : array();
//could be loaded from config
$maxItemCount = 10;
$timwToLive= 600;
//helpers
$time = time();
$deadline = $time - $timeToLive;
//add if not in list
if(! isset($clicks[$id]) ){
$clicks[$id] = $time;
}
//remove old values
$clicks = array_filter($clicks, function($value){ $value >= $deadline;});
//sort newest to oldest
arsort($clicks);
//limit items, oldest will be removed first because we sorted the array
$clicks = array_slice($clicks, 0, $maxItemCount);
//save to session
$this->session->>set_userdata('article_clicks',$clicks)
Usage:
//print how mch time has passed since the last visit
if(isset($clicks[$id]){
echo "visited ".($time-$clicks[$id]). "seconds ago." ;
} else {
echo "first visit";
}
EDIT: you have to use arsort not rsort or the keys will be lost, sorry
Based on Raphael_ code and your question you can try this:
<?php
$aClicks = $this->session
->userdata('article_clicks');
$nextId = $this->session->userdata('nextId');
// Initialize the array, if it's not already initialized
if ($aClicks == false) {
$aClicks = array();
$nextId = 0;
}
// Now, we clean our array for the articles that have been clicked longer than
// 10 minutes ago.
$aClicks = array_filter($aClicks, function($click) {
return (time() - $click['time']) < 600; // Less than 10 minutes elapsed
}
);
// We check if the article clicked is already in the list
$found = false;
foreach ($aClicks as $click) {
if ($click['article'] === $id) { // Assuming $id holds the article id
$found = true;
break;
}
}
// If it's not, we add it
if (!$found) {
$aClicks[$nextId] = array(
'article' => $id, // Assuming $id holds the article id
'time' => time()
);
$nextId++;
$this->session->set_userdata('nextId', $nextId);
}
$this->session->set_userdata('article_clicks', $aClicks);
if (count($aClicks) > 10 && $nextId > 9) {
$this->session->set_userdata('nextId', 0);
echo "OK!";
}
?>
I hope I understood correctly what you need.
Usage:
$this->load->library('click');
$this->click->add($id, time());
The class API is very simple and the code is commented. You can also check if an item expired(), if exists() and you can get() item saved time.
Remember that:
Each item will expire after 10 minutes (see $ttl)
Only 10 items are saved in session (see $max_entries)
class Click
{
/**
* CI instance
* #var object
*/
private $CI;
/**
* Click data holder
* #var array
*/
protected $clicks = array();
/**
* Time until an entry will expire
* #var int
*/
protected $ttl = 600;
/**
* How much entries do we store ?
* #var int
*/
protected $max_entries = 10;
// -------------------------------------------------------------------------
public function __construct()
{
$this->CI =& get_instance();
if (!class_exists('CI_Session')) {
$this->CI->load->library('session');
}
// load existing data from user's session
$this->fetch();
}
// -------------------------------------------------------------------------
/**
* Add a new page
*
* #access public
* #param int $id Page ID
* #param int $time Added time (optional)
* #return bool
*/
public function add($id, $time = null)
{
// If page ID does not exist and limit has been reached, stop here
if (!$this->exist($id) AND (count($this->clicks) == $this->max_entries)) {
return false;
}
$time = !is_null($time) ? $time : time();
if ($this->expired($id)) {
$this->clicks[$id] = $time;
return true;
}
return false;
}
/**
* Get specified page ID data
*
* #access public
* #param int $id Page ID
* #return int|bool Added time or `false` on error
*/
public function get($id)
{
return ($this->exist($id)) ? $this->clicks[$id] : false;
}
/**
* Check if specified page ID exists
*
* #access public
* #param int $id Page ID
* #return bool
*/
public function exist($id)
{
return isset($this->clicks[$id]);
}
/**
* Check if specified page ID expired
*
* #access public
* #param int $id Page ID
* #return bool
*/
public function expired($id)
{
// id does not exist, return `true` so it can added
if (!$this->exist($id)) {
return true;
}
return ((time() - $this->clicks[$id]) >= $this->ttl) ? true : false;
}
/**
* Store current clicks data in session
*
* #access public
* #return object Click
*/
public function save()
{
$this->CI->session->set_userdata('article_clicks', serialize($this->clicks));
return $this;
}
/**
* Load data from user's session
*
* #access public
* #return object Click
*/
public function fetch()
{
if ($data = $this->CI->session->userdata('article_clicks')) {
$this->clicks = unserialize($data);
}
return $this;
}
public function __destruct()
{
$this->save();
}
}
You could easily wrap that into a class of it's own that serializes the information into a string and that is able to manipulate the data, e.g. to add another value while taking care to cap at the maximum of ten elements.
A potential usage could look like, let's assume the cookie last would contain 256 at start:
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(10), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(20), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(30), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(40), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(50), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(60), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(70), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(80), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(90), "\n";
echo $_COOKIE['last'] = (new StringQueue($_COOKIE['last']))->add(100), "\n";
And the output (Demo):
10,256
20,10,256
30,20,10,256
40,30,20,10,256
50,40,30,20,10,256
60,50,40,30,20,10,256
70,60,50,40,30,20,10,256
80,70,60,50,40,30,20,10,256
90,80,70,60,50,40,30,20,10,256
100,90,80,70,60,50,40,30,20,10
A rough implementation of that:
class StringQueue implements Countable
{
private $size = 10;
private $separator = ',';
private $values;
public function __construct($string) {
$this->values = $this->parseString($string);
}
private function parseString($string) {
$values = explode($this->separator, $string, $this->size + 1);
if (isset($values[$this->size])) {
unset($values[$this->size]);
}
return $values;
}
public function add($value) {
$this->values = $this->parseString($value . $this->separator . $this);
return $this;
}
public function __toString() {
return implode(',', $this->values);
}
public function count() {
return count($this->values);
}
}
It's just some basic string operations, here with implode and explode.