I have a weird problem. I somehow can't use the above function in an array, but just see my source code:
public function getDeploymentHours($refDate = NULL)
{
if(empty($refDate)){
$refDate = Carbon::now();
} else {
$refDate = new Carbon($refDate);
}
echo $refDate->startOfWeek();
// outputs: 2015-03-16 00:00:00
echo "<br>";
echo $refDate->endOfWeek();
// outputs: 2015-03-22 23:59:59
$a = [$refDate->startOfWeek(),
$refDate->endOfWeek()];
print_r($a);
// outputs: Array
//(
// [0] => Carbon\Carbon Object
// (
// [date] => 2015-03-22 23:59:59
// [timezone_type] => 3
// [timezone] => UTC
// )
// [1] => Carbon\Carbon Object
// (
// [date] => 2015-03-22 23:59:59
// [timezone_type] => 3
// [timezone] => UTC
// )
//)
}
I find that very weird. Can someone imagine how this is possible?
That's because of two reasons.
1. Carbon modifies itself.
When you call startOfWeek(), $refDate actually changes. Not only do you get the new date returned but the changes are set to the actual carbon object so when you could as well do this:
$refDate->startOfWeek();
echo $refDate;
and would get the same result...
2. Carbon returns itself.
Most of Carbons functions actually return the object itself. For example if we track down startOfWeek() it ends up at startOfDay() which looks like this:
public function startOfDay()
{
return $this->hour(0)->minute(0)->second(0);
}
So after all it returns the result of second(0). Which is...
public function second($value)
{
$this->second = $value;
return $this;
}
$this!! So Carbon returns itself.
Conclusion
Because the methods actually change $refDate and the methods return itself you dump the exact same object twice.
Fix
You can simply clone the object instance to have two separate Carbon objects:
$refDate2 = clone $refDate;
$a = [$refDate->startOfWeek(),
$refDate2->endOfWeek()];
print_r($a);
Related
The filled function Vs using the empty function. What is the reason for choosing filled Vs !empty()?
I think they basically do the same work. The only difference that I noticed is that the filled() method is a Laravel's helper function and only available on a $request instance or in the Illuminate\Http\Request class. While the empty() method is available globally because its a PHP's helper function. You can use empty() on any variable in any class or controller. While on the other hand filled() can only be used wherever you're receiving a request or you have to manually create an instance of Request class.
Personally, I've never used the filled() method, so I can't tell you exactly what is the technical difference between them (if there is any).
The big difference between filled or its inverse blank and empty is what is considered empty.
For example:
filled(0); // true
blank(0); // false
!empty(0); // false
filled(' '); // false;
blank(' '); // true;
!empty(' '); // true;
filled(''); // false;
blank(''); // true;
!empty(''); // false;
The functions work in a very different way, so it's not a matter of just picking one of the two. It depends on what you are trying to do in your code.
empty example
$test = array(
1 => '',
2 => "",
3 => null,
4 => array(),
5 => FALSE,
6 => NULL,
7=>'0',
8=>0,
);
foreach ($test as $k => $v) {
if (empty($v)) {
echo "<br> $k=>$v is empty";
}
}
/**
Output
1=> is empty
2=> is empty
3=> is empty
4=>Array is empty
5=> is empty
6=> is empty
7=>0 is empty
8=>0 is empty
**/
if(isset($test)) // return true because $test is defined
if(is_null($test)) // return false because $test is not null
Laravel has the super handy optional() helper.
I would like to combine it with a custom Model attribute like this:
// this method is on the User model
public function getDataAttribute()
{
// this data comes from another service
$data = [
'one' => 1,
'two' => 2,
];
return optional($data);
}
So I can use it like this:
$user->data->one // 1
$user->data->two // 2
$user->data->three // null
However, I am also trying to return the entire array by doing:
dump($user->data); // this should dump the internal $data array
But this will return an instance of Illuminate\Support\Optional with a value property.
Illuminate\Support\Optional {#1416 ▼
#value: {#2410 ▼
+"one": 1
+"two": 2
}
}
Is it possible to return the original $data array if no "sub"parameter (= a child attribute of $user->data) is given? Or is there a possibility to detect a child parameter in the getDataAttribute()?
I hope it's clear what I am trying to achieve.
What you're asking for cannot be achieved.
My suggestion would be to keep things simple and define a getter method and pass the key of the array you want and from there return your data respectively, e.g.:
public function getData($key = null) {
$data = [
'one' => 1,
'two' => 2,
];
if (!$key) {
return $data;
}
return $data[$key] ?? null;
}
Notice also how this method is no longer an attribute, this is because AFAIR, you cannot pass args to attribute methods.
Reading Material
Null coalescing operator
Thanks to lagbox for pushing me in the right direction. I have solved this by using the following macro:
Illuminate\Support\Optional::macro('toArray', function()
{
return (array) $this->value;
});
This way I can access all data by using:
$user->data->toArray();
I'm having an odd error with saving an encrypted array in Laravel. The model never updates even when save() is called.
There are no console or SQL errors.
When the encryption is disabled, there are no errors and the model updates successfully.
In a Controller, I'm calling the model like so:
$userData = UserData::where('user_id', $user_id)->first();
I then pull the array:
$encryptedData = $userData->app_data;
And I want to add to this array e.g.
$encryptedData['new'] = 'axy';
$encryptedData['time'] = time();
I then update the model and save it:
$userData->app_data = $encryptedData;
$userData->save();
However, here is where the problem starts. The model does not update. It remains as if nothing happens. Hence if I refresh(), I get the same data as if I had never added the two new entries. When I log it, it looks like this:
Array
(
[token] => xyz
[access_token] => abc
)
After the addition of two new entries:
Array
(
[token] => xyz
[access_token] => abc
[new] => 'axy'
[time] => 1234
)
And after the save() and refresh():
Array
(
[token] => xyz
[access_token] => abc
)
The model looks like this:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
class UserData extends Model
{
protected $fillable = [
'user_id', 'app_data'
];
protected $casts = [
'user_id' => 'int',
'app_data' => 'array'
];
public function getAppDataAttribute($value)
{
try {
return decrypt($value);
}
catch (DecryptException $e) {
return $value;
}
}
public function setAppDataAttribute($value)
{
$this->attributes['app_data'] = encrypt($value);
}
}
Why are my additions to the array not being saved?
Edit: The strangeness continues
If I call:
UserData::where('id', $userData->id)->update(['app_data' => $encryptedData]);
Then the model does update and does not encrypt, HOWEVER, when I refresh and log the new 'app_data' field, it is returned as a JSON string and not an array as before. I need to cast/decode it to an array each time I want to use it.
Couple of things to look for.
1) The Laravel encrypter uses the app key. Make sure you have one in your .env file. If not, run php artisan key:generate
2) I assume the array is correctly formatted like this:
Array
(
'token' => 'xyz', // You have a = here and no commas after any other value
'access_token' => 'abc'
)
3) Depending on what you are storing this as, you can test by serializing the array before encrypting it:
$arr = serialize($encryptedData); // After you have added new data to the array
$userData->app_data = $arr;
$userData->save();
This is automatic in Laravel, but may give you a help hunting the bug. Test with your mutator using encryptString() and manually unserialize / decryptString() to see if any odd behavior by stepping through the values as they are mutated.
this timezone stuff is a real nightmare. I'm storing all values as UTC in my database. What I would like to do is to build a function that returns the DateTime String in the local timezone. As I'm using Laravel I would like to use Carbon for the job. I have tried multiple times now and failed.
$dateasstring= '2014-01-05 12:00:00' //retrieved from databse
This date is UTC. How do I parse it as UTC into Carbon and then tell Carbon to change the time into the localtimezone? Am I missing something?
$carbon = new Carbon\Carbon($dateasstring);
$local = $carbon->timezone($localTimeZone);
// example from artisan tinker:
[1] > $utc = new Carbon\Carbon('2014-01-05 12:00:00');
// object(Carbon\Carbon)(
// 'date' => '2014-01-05 12:00:00',
// 'timezone_type' => 3,
// 'timezone' => 'UTC'
// )
[2] > $warsaw = $utc->timezone('Europe/Warsaw');
// object(Carbon\Carbon)(
// 'date' => '2014-01-05 13:00:00',
// 'timezone_type' => 3,
// 'timezone' => 'Europe/Warsaw'
// )
This is the solution I use. I use on function to make the date UTC (toutc) and one function to switch it back into local time (tolocal). During login of the user I set the session variable "timezone".
private function totimezone($utc){
$usertz = Session::get('timezone');
$carbon = new Carbon($utc, 'UTC');
$carbon->timezone = new DateTimeZone($usertz);
return $carbon;
}
private function toutc($local){
$usertz = Session::get('timezone');
$carbon = new Carbon($local, $usertz);
$carbon->timezone = new DateTimeZone('UTC');
return $carbon;
}
I'm creating a payment form with symfony 1.4 , and my form has a date widget defined like this, so that the user can select the expiration date of his credit card:
new sfWidgetFormDate(array(
'format' => '%month%/%year%',
'years' => array_combine(range(date('Y'), date('Y') + 5), range(date('Y'), date('Y') + 5))
Notice the absence of %day% in the format like in most payment forms.
Now my problem is that sfValidatorDate requires the 'day' field not to be empty. To work around this, I created a custom validator using a callback, which works well:
public function validateExpirationDate($validator, $value)
{
$value['day'] = '15';
$dateValidator = new sfValidatorDate(array(
'date_format' => '#(?P<day>\d{2})(?P<month>\d{2})(?P<year>\d{2})#',
'required' => false,
'min' => strtotime('first day of this month')));
$dateValidator->clean($value);
return $value;
}
I feel there might be a simpler way to achieve this. What do you think? Have you already solved this problem in a cleaner way?
How do you store the date? If you just store month and year as integers or strings, then you can just make 2 choice widgets. But if you store it as datetime (timestamp), then you need a valid date anyway. This means that you need to automatically assign values to 'day' (usually first or last day of the month).
class YourForm extends BaseYourForm
{
public function configure()
{
$this->widgetSchema['date'] = new sfWidgetFormDate(array(
'format' => '%month%/%year%'
));
$this->validatorSchema['date'] = new myValidatorDate(array(
'day_default' => 1
));
}
}
class myValidatorDate extends sfValidatorDate
{
protected function configure($options = array(), $messages = array())
{
$this->addOption('day_default', 1);
parent::configure($options, $messages);
}
protected function doClean($value)
{
if (!isset($value['day']))
{
$value['day'] = $this->getOption('day_default');
}
return parent::doClean($value);
}
}
There's no need to use a custom validation class: you can simply override the tainted values passed to your bind() method:
<?php
// in your form class
public function bind(array $taintedValues = null, array $taintedFiles = null)
{
$taintedValues['date']['day'] = 1;
return parent::bind($taintedValues, $taintedFiles);
}
I used simplest way, for validate credit card expiration day:
$post_data = $request->getParameter('my_form');
$post_data['card_exp']['day'] = 1; //sets the first day of the month
$this->form->bind($post_data);
Hope this helps somebody.
I solve this first in the form class
$year = range(date('Y'), date('Y') - 50);
$this->widgetSchema['date'] = new sfWidgetFormDate(array(
'format' => '%year%',
'years' => array_combine($year, $year),
'can_be_empty' => false
));
Next...
public function bind(array $taintedValues = null){
$taintedValues['date']['day'] = '01';
$taintedValues['date']['month'] = '01';
parent::bind($taintedValues);
}
The field in the database is date type DATE.