I have a model class with start date and end date. I want to make sure that the start date is always less than the end date.
I thought I might achieve this with an #validate annotation in the model class and a custom validator class like in the docs.
How can I get access to the start date value in the validate function?
<?php
namespace XXX\YYY\Domain\Model\Validator;
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
class EventEndDateValidator extends AbstractValidator
{
public function validate($endDate)
{
return;
$this->addError('Validator cant do anything yet.', 42);
}
}
Even Typo3 doesn't provide This type of functionalities,
You can test in start_date and end_date in "Access" in any typo3 default element.
For this, you should use the hook and compare dates while user save record,
and return false with the error message.
Let me know if you need any help related to hook
Thanks!!
Related
I am currently moving over from symfony to laravel, it's quite a bit different when it comes to the database. So i have a basic model, i'm just going to use an example:
class Test extends Model
{
use HasFactory;
}
All good, i have a migration and the table created. However, i don't like this:
$test = new Test();
$test->my_field = 'hello';
$test->save();
I don't like it because it's having to use a magic __set() to create the parameter, if i define the parameter in my model like this:
class Test extends Model
{
use HasFactory;
public ?string $my_field;
}
I get database errors when it tries to insert when i define the params like this. Why is that? It's doing the same thing as __set() but i'm actually physically defining them, which in my opinion is a better way to code it as my IDE can typehint and it's just nicer to follow the program knowing what params are there.
What's the reason for it inserting when i don't define them, and not when i do? From my actual table which is bookings , has a field booking_ref:
General error: 1364 Field 'booking_ref' doesn't have a default value (SQL: insert into booking_reviews (updated_at, created_at) values (2021-12-13 14:13:08, 2021-12-13 14:13:08))
This happens when i define the $booking_ref param on the model, but if i take it out and rely on the __set() method it works fine. Doesn't make any sense to me right now.
I think this is a reasonable enough misunderstanding to be useful to future visitors, so I want to try to explain what's going on with some pseudo-code and some references to the current source code.
You are correct that when setting a property on a Laravel model, that is a column in the DB, internally Laravel is using the PHP magic method __set.
What this does is allow you to 1) set properties directly instead of calling some kind of setter function, and 2) interact with your table columns without needing the boilerplate of column definitions in your model.
Where the assumptions go wrong is with what __set is doing. __set does not have to simply set an actual property with the same name. __set is just a method you may implement to do whatever you want. What you assumption implies is that it's doing something like this:
public function __set($key, $value)
{
$this->{$key} = $value;
}
However, you can do whatever you want with the $key and $value passed to the magic method.
What Laravel does is call another method defined in the HasAttributes trait - setAttribute.
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
setAttribute does a few extra things, but most importantly it adds the key/value pair to Model property $this->attributes[].
To hopefully help this difference make sense, here is what the two __set methods would yield with a basic example:
$model->my_column = 'value';
// 1st example
/**
* {
* public $my_column = 'value';
* }
*/
// Laravel way
/**
* {
* protected $attributes= ['my_column => 'value'];
* }
*/
I won't go through both saving and updating since they're very similar, but to show how this is used, we can look at the save method, which calls performInsert and after a few more calls makes it's way back to the attributes property to determine what to actually insert into the query.
Summary
Laravel does not use custom model properties when deciding what column/values to add to queries.
This is why when you create custom mutators, you interact with the attributes property just like Laravel does internally.
Anytime you introduce "magic" into code, you have some tradeoffs. In this case, that tradeoff is slightly less clarity with what database columns are actually available. However, like I mentioned in comments, there are other solutions to make models more IDE friendly like Laravel IDE helper.
When I fetch data from database using Laravel, I got some timestamp field like 'created_at', 'updated_at',....
What type of class for these fields, what type of class which return true of expression $created_at instanceof DatetimeClass
Thanks
Recent Laravel uses (Carbon library v2)
for that type of data (Illuminate\Support\Carbon).
As for ... instanceof DatetimeClass part of your question, I am not sure what really you refer to as DatetimeClass class, but I assume that you meant built-in DateTime class. If that's so, you can get DateTime from Carbon object by calling toDateTime(), i.e.:
$dt = $model->created_at->toDateTime();
Alternatively, edit your ... instanceof ... uses to test against Carbon.
Please also have a look at date mutators in Laravel. Laravel will automatically cast a timestamp(created_at, updated_at) or when you need to define custom date fields in the db you can define these in the model: https://laravel.com/docs/7.x/eloquent-mutators#date-mutators
If you do not want to automatically cast you can parse a string representation of a date using $carbonObject = Carbon::parse('2020-02-03');
By default, Eloquent will convert the created_at and updated_at columns to instances of Carbon. When retrieving attributes that are listed in the $dates property, they will automatically be cast to Carbon instances, allowing you to use any of Carbon's methods on your attributes.
I have the following in dates property - i've not included the created_at and updated_at columns as these are converted by default as per above:
protected $dates = ['deleted_at'];
Then I have the following accessor on the model:
public function getCreatedAtAttribute($datetime)
{
return $datetime->timezone('Europe/London');
}
However the above throws the following error:
Call to a member function timezone() on string
If I change the method to the following it works:
public function getCreatedAtAttribute($datetime)
{
return Carbon::parse($datetime)->timezone('Europe/London');
}
The question is why do I need to parse it since it's suppose cast it to a carbon instance when it's retrieved according to docs https://laravel.com/docs/6.x/eloquent-mutators#date-mutators ?
That completely depends on what $datetime is and how you're passing it this this function. It's clearly a string, and not a Carbon instance, but you didn't include the definition for $datetime in you question, so I can only speculate.
That being said, I haven't see mutators that use an external variable, as they are generally designed to access properties of the class you're applying them to, via $this:
public function getCreatedAtAttribute(){
return $this->created_at->timezone('Europe/London');
}
The only caveat I could see with this is naming conflict when trying to use $model->created_at. It should handle it, but something like getCreatedAtTzAttribute(), accesses via $model->created_at_tz might be necessary if you come across issues.
If you check the source code (here), you'll see that accessors have priority over date casts.
If Eloquent finds an accessor for your date attribute (getCreatedAtAttribute), date casting will be ignored. So you'll need to cast it manually within your accessor.
I have a code like this:
public function getUpdatedAtAttribute($value) {
setlocale(LC_TIME, config('app.locale'));
return Carbon::parse($value)->formatLocalized(__('DateFormat'));
}
I want to run this accessor for each field specified in $dates array instead of manually specifying it for each date field in each model, just like default Carbon instance convertion works. How could I do this? And is there better ways of specifying default locale-dependant date format for Carbon?
I think you can use $dateFormat variable of a model to apply a common date format on all the model fields:
class Flight extends Model
{
/**
* The storage format of the model's date columns.
*
* #var string
*/
protected $dateFormat = 'U';
}
More info: https://laravel.com/docs/5.4/eloquent-mutators#date-mutators
Found an elegant and simple solution: LocalizedCarbon package. It works as simply as this:
use \Laravelrus\LocalizedCarbon\Traits\LocalizedEloquentTrait;
UPD: It seems that this package actually translating only DateDiff's, but anyway I can see how it works and use that logic in my models.
UPD2: I've dig deeper and found out what there is overloaded formatLocalized method, which allows useage of non-standard "%f" parameter, which represents month name in current application locale. So I ended up with one-liner date formatting into my View instead of Model, which is more correct.
I am trying to extend my Linq-to-Sql entity with a few extra properties. These are "calculated" properties based on data from the underlying SQL View. For example, think of having a Date of Birth field, which is used to calculate an extended Age field.
I tried to extend my entity class by extending the OnLoaded() method.
I get a compile time error however stating that I cannot create it. I checked the designer code for my LTS entity class, and it doesn't have a partial definition for any of the expected extension points.
I checked a few of my other LTS entity classes and they do have these extension points. The only difference I see is that the one without is loaded from a SQL View, rather than a table. Is there a way to hook into a "Loaded" event when loading from a SQL View?
TIA!
I found that I did not have a PrimaryKey specified for my Linq-to-Sql entity class. I believe without a Primary Key specified, no extension methods generated in the entity class. Once I specified a Primary Key on my LTS entity class definition (through the designer), I was able to extend the OnLoaded() event.
You can do this by means of a property. Just create a partial class with the same name as your entity. Any properties or methods that you add will automatically be part of the entity and allow to use any of its members.
Here's an example of the pattern:
public partial class [The Name of the Entity]
{
public int Age
{
get
{
return CalculateAge(this.DateOfBirth);
}
}
}
Here's some logic on how to calculate the Age (Source: Geekpedia)
public static int CalculateAge(DateTime BirthDate)
{
int YearsPassed = DateTime.Now.Year - BirthDate.Year;
// Are we before the birth date this year? If so subtract one year from the mix
if (DateTime.Now.Month < BirthDate.Month ||
(DateTime.Now.Month == BirthDate.Month && DateTime.Now.Day < BirthDate.Day))
{
YearsPassed--;
}
return YearsPassed;
}