Declare properties in Yii2 Active Record model - yii2

I need to declare properties inside an Active Record model. I want to use PHP 8 syntax. So I did:
final class Post extends ActiveRecord
{
public ?int $id = null;
public ?int $user_id = 0;
public string $slug;
public ?string $title = null;
public ?string $content = null;
public ?string $created_at = null;
public ?string $updated_at = null;
public ?string $published_at = null;
public static function tableName(): string
{
return 'posts';
}
public function rules(): array
{
return [
[['user_id', 'title', 'content'], 'required'],
[['user_id'], 'integer'],
[['content'], 'string'],
[['slug', 'created_at', 'updated_at', 'published_at'], 'safe'],
[['title'], 'string', 'max' => 512],
];
}
}
But now all the fields become inaccessible. When I remove them as class fields, everything is ok.
As we can see Yii just removes these model attributes since I declared them as class properties:
SQLSTATE[HY000]: General error: 1364 Field 'user_id' doesn't have a
default value The SQL being executed was: INSERT INTO posts (id)
VALUES (DEFAULT)

In most active record models in yii2 you put comments to get IDE-support like autocompletion since yii2 is using magic methods to resolve the fields to columns.
See Yii2 BaseActiveRecord all the columns are stored in a field _attributes.
So i think the best way you could do is add the doc comments as the following example to your ActiveRecord classes:
/**
* #property ?int id
* #property string slug
* #property-read Author[] authors
*/
note the #property-read indicates a relation and there is a subtile difference when accessing $object->authors; vs $object->getAuthors();. In the first case the relation query is executed and the results are returned as array (via magic methods) and in the second case you get back the raw query that is not executed yet.
And to keep with the authors example the relation method would look like the following:
public function getAuthors(): ActiveQuery
{
return $this
->hasMany(Author::class, ['id' => 'author_id'])
->viaTable('post_author', [ 'post_id' => 'id' ]);
}

Related

Laravel API resources, get only the latest occurrence from a collection

Hi I am developing an api in laravel for an online course system. In this scheme I have a standard table for users, a table for courses and a pivot table that relates courses and users according to which they sign up for each course.
This last table also carries the events related to the progress of each user in the course, that is, Subscribed, Progress x%, Completed, Approved, so that each user can have multiple entries in the course_users table.
So far everything is clear and everything is fine, the point is that at a certain moment I need to return a json object with the information of the courses and pointed users, this can be clearly achieved using resource collection in the following way:
CourseCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
use App\Http\Resources\CargoResource;
class CourseCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => CourseResource::collection($this->collection),
'links' => [
'self' => 'link-value',
],
];
}
}
CourseResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CourseResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title'=> $this->title,
'description'=> $this->description,
'price'=> $this->price,
'users' => CourseUserResource::collection($this->whenLoaded('users'))
];
}
}
CourseUserResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CourseUserResource extends JsonResource
{
public function toArray($request)
{
return [
'course_id'=> $this-> course_id,
'user_id'=> $this->user_id,
'event'=> $this->event,
'event_date' => $this->created_at->format('Y-m-d')
];
}
}
The problem to be solved is that with this scheme I obtain a collection of events for each user and course, but what I am needing is only the last event of each user, to know what their status is in relation to the course.
I am analyzing the option to perform the query by sql and then manually build the json object, but I would like to have a "laravel style" solution
Any ideas will be welcome!
Added Models & Controllers for clarification
class Course extends Model
{
protected $fillable = [
'title',
'slug',
'description',
'course_category_id',
'price',
'published'
];
...
public function history()
{
return $this->belongsToMany(CourseUser::class, 'course_id', 'id')->latest();
}
public function scopePublished($query)
{
return $query->where('published', 1);
}
}
class CourseUser extends Model
{
protected $fillable = [
'course_id',
'user_id',
'event'
];
}
class SearchController extends ApiController
{
public function search(Request $request)
{
$results = Course::with('history')
->published
->where('title', 'like', $request->filter['title'])
->where('description', 'like', $request->filter['description'])
->get();
if (! count($results) > 0) {
return $this->sendResponse(
__('No results for your query.'),
[
'code'=>204,
'message'=> __('There are no results for your search criteria.')
],
204
);
}
return new CourseCollection($results);
}
}

Yii2 property mapping to tablename

I use Yii2 2.0.9 basic template and I try to set up my class.
I my class I use references of other classes in my property.
/**
*
*#property Contact contact
*/
class User extends ActiveRecord {
public static function tableName() {
return "user";
}
/**
* This is want I need
*/
public function databaseMapping(){
return [
"contact" => "contact_id"
];
}
}
Is there in Yii2 a solution for my problem?
Thanks Marvin Thör
In Grails I can write this:
class User {
Contact contact
Boolean passwordExpired
static mapping = {
contact(column: 'contact_id')
passwordExpired(column: 'password_expired')
}
}
User user = new User();
user.passwordExpired = true
user.contact = new Contact();
and I want the same
You might want to use the method attributeLabels() inside your model class to define label names to show to the end user.
public function attributeLabels() {
return [
'contact_id' => 'Contact',
];
}
However, there are times like when creating a RESTful API using Yii2 that you need to return a json with fields with specific field names. For these ocasions, you can use the fields() method:
public function fields() {
return [
'contact' => 'contact_id',
];
}
This method returns the list of fields that should be returned by default by toArray(). You can check more about it HERE.
Change your labels and db column remain unchanged.
public function attributeLabels()
{
return [
'contact_id' => Yii::t('app', 'Use your name here'),
];
}

Yii2 How to create a globally accessible variable that converts user's id to employee's id

I have two tables that are related directly in a one-to-one relationship. One is the standard Yii2 user table (abbreviated field list here for clarity) and the other is the employee table that contains user_id. How can I create a globally accessible variable (and the actual code to access the employee id) that I can use anywhere in my application that will give me the logged in user's employee id and how would I call that variable? I wish I could say that I've tried a few things, but unfortunately I am relatively new to Yii2 and have no idea where to start with global variables like this. Thanks for any help.
user table:
id
username
password
etc
employee table:
id
user_id (related in a one-to-one relationship to the user table)
The Employee Model:
<?php
namespace frontend\models\base;
use Yii;
/**
* This is the base model class for table "employee".
*
* #property integer $id
* #property integer $user_id
*
* #property \common\models\User $user
*/
class Employee extends \yii\db\ActiveRecord
{
public function rules()
{
return [
[['user_id', 'required'],
[['user_id'], 'integer'],
[['user_id'], 'unique']
];
}
public static function tableName()
{
return 'employee';
}
public function attributeLabels()
{
return [
'id' => Yii::t('app', 'ID'),
'user_id' => Yii::t('app', 'User ID'),
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getUser()
{
return $this->hasOne(\common\models\User::className(), ['id' => 'user_id']);
}
}
A very simple way is the use of $param array
You can initially config the default value in
your_App\config\param.php
and accessing using
\Yii::$app->params['your_param_key']
Looking to your Employee model (for me ) you don't need a global var you could simply use the getUser
$myUser = Employee::user();
but you need the param you can assign using
\Yii::$app->params['my_user'] = Employee::user();
or in user
\Yii::$app->params['my_user'] = Yii::$app->user->id
or for retrive the model related to actual user from table
$myEmpModel = Employee::find()->where['user_id' => Yii::$app->user->id]->one();
I believe proper way is to use relations in your User model. First method is proper relation with activerecord, second one will get id using relation defined above it. so You will add these methods in your User model:
/**
* #return \yii\db\ActiveQuery
*/
public function getEmployee()
{
return $this->hasOne(Employee::className(), ['user_id' => 'id']);
}
public function getEmployeeId()
{
return $this->employee ? $this->employee->id : NULL; // set to NULL or anything you expect to be if record is not found
}
Then you can call it like this from everywhere in your app:
$employee_id = Yii::$app->user->identity->employeeid;
This will only work for User model because it implements Identity, otherwise you would need to instantiate model class first, lets say like this:
$user_id = 5; // 5 is id of user record in DB
$user = User::findOne($user_id);
$employee_id = $user->employeeid;
// or using first of 2 relations ...
$employee_id = $user->employee->id;

Laravel 5.1.* Custom Pivot Class Array Not Casted

I have a Model called User and another Model called Roles and they are linked with each other through a belongsToMany relationship. But I needed to cast certain pivot attributes so I used a custom pivot class RoleUserPivot which basically looks like follows:
...
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUserPivot extends Pivot
{
protected $casts = [
'active' => 'boolean',
'permissions' => 'array',
];
}
...
The relationship definition in User and Role models is as follows:
...
// User Model
public function roles()
{
return $this
->belongsToMany('App\Role')
->withPivot(
'active',
'permissions'
);
}
public function newPivot(Model $parent, array $attributes, $table, $exists)
{
if ($parent instanceof Role) {
return new RoleUserPivot($parent, $attributes, $table, $exists);
}
return parent::newPivot($parent, $attributes, $table, $exists);
}
...
And similarly:
...
// Role Model
public function users()
{
return $this
->belongsToMany('App\User')
->withPivot(
'active',
'permissions'
);
}
public function newPivot(Model $parent, array $attributes, $table, $exists)
{
if ($parent instanceof User) {
return new RoleUserPivot($parent, $attributes, $table, $exists);
}
return parent::newPivot($parent, $attributes, $table, $exists);
}
...
The problem I am having is, while the active field is properly cast to boolean, the permissions field is not cast to array, instead the same string in the database is returned. I assure that the pivot table is properly setup and permissions column is MySQL TEXT column.
Currently I am using Laravel 5.1.16 (LTS).

Yii2, Model find() with custom attribute

I want to pull the model data with custom attribute that assigned in a function in model.
Example)
class Test extends ActiveRecord
{
public static function tableName()
{
return '{{%test}}';
}
public function rules()
{
//....
}
public function attributeLabels()
{
return [
'id' => 'ID',
'first_name' => 'First Name',
'last_name' => 'Last Name',
];
}
public function getFullName()
{
$fullName = $this->first_name.' '.$this->last_name;
return $fullName;
}
}
Test::find().with('fullName') => it doesn't work
How can I get all the data with fullname attribute?
with is for relations. You can get fullname attribute just by calling $model->fullName. Actually fullName is not an attribute, yii2 utilise php's magic method __get() to get it from getFullName() method.
Example:
$model = Test::findOne($id);
echo $model->fullName;
Example 2:
$models = Test::find()->all();
foreach($models as $model)
{
echo $model->fullName;
}
Also consider using of fields/extraFields methods if you want use your models as arrays instead of objects