In Yii2 I have blog with articles, each one has 1 category. Rules are so:
'rules' => [
'<category_slug>/<article_slug>' => 'article/view',
],
controller:
public function actionView($category_slug, $article_slug)
{
...
}
Categories are stored in params.php:
<?php
return [
'categories' => [
[
'id' => 1,
'name' => 'First category',
'slug' => 'first_category',
],
...
],
];
Url to article:
Url::to(['article/view', 'category_slug' => $model->category_slug, 'article_slug' => $model->article_slug])
Question: Is it possible to auto prepend category slug to Url::to? I mean, you need only make Url::to with article_slug param. I suppose best solution will be to change url rules somehow, but how exactly?
'rules' => [
'<Yii::$app->MyFunction->GetCategorySlugByArcticleId(..)>/<article_slug>' => 'article/view',
],
This should be an easy one.
Option one: extend the Url-Helper
Simply extend the Url-Helper as follows:
class MyUrl extends \yii\helpers\Url
{
//extending regular `to`-method
public static function to($url = '', $scheme = false)
{
//check if url needs completion and meets preconditions
if (is_array($url) && strcasecmp($url[0], 'article/view') === 0 && isset($url['article_slug']) && !isset($url['category_slug'])) {
//add category slug if missing...assuming you have a relation from article to category and category having a property `slug`
$url['category_slug'] = $article->category->slug;
}
return parent::to($url, $scheme);
}
//...or custom method for your case which is even more convenient
public static function toArticle($article, $scheme=false)
{
//article could be an article model itself or an id
$articleModel = $article instanceof Article ? $article : Article::findOne($article);
return parent::to([
'article/view',
'article_slug'=>$articleModel->slug,
'category_slug'=>$articleModel->category->slug,
], $scheme);
}
}
All you have to do now is use that class instead of the regular Url-helper!
Option two: add a method within your article model
Simply add a method as follows:
class Article extends \yii\db\ActiveRecord
{
//...
public function link($scheme=false)
{
return Url::to([
'article/view',
'article_slug'=>$this->slug,
'category_slug'=>$this->category->slug,
], $scheme);
}
//...
}
Let me know if you need more info!
Related
why dont display relation data in Rest Yii2
i have two tables.
sample:
category , subcategory
<?php
namespace app\controllers;
use app\models\Category;
use yii\web\NotFoundHttpException;
use yii\web\Response;
use yii\rest\Controller;
class ApiController extends Controller
{
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['contentNegotiator']['formats'] = ['application/json' => Response::FORMAT_JSON];
return $behaviors;
}
public function actionGetSk($cId)
{
$result= Category::find()->with('subCategory')->where(['id' => $cId])->all()
return $result;
}
}
i result i have only from Category. (result is json)
but print_r($result) i have data from Category and subCategory.
web.php
[
'class' => 'yii\rest\UrlRule',
'pluralize' => false,
'controller' => 'api',
],
Try this: in your Category model, add this method:
public function extraFields() {
return [
'subcategory' => 'subCategory',
];
}
And now, call your api with the expand get parameter like:
http://yourapi.com/api/get-sk?cID=1&expand=subcategory
Please, sorry for my English.
My problem:
abstract class Entity
{
protected static $fieldNames;
public static function getFieldsNames()
{
if (is_null(static::$fieldNames)) {
foreach (static::$fieldsMap as $name => $map) {
static::$fieldNames[] = $name;
}
}
return static::$fieldNames;
}
}
class User extends Entity
{
protected static $fieldsMap = [
'id' => [
// ...
],
'name' => [
// ...
],
'phone' => [
// ...
]
];
}
class Car extends Entity
{
protected static $fieldsMap = [
'id' => [
// ...
],
'brand' => [
// ...
],
'color' => [
// ...
]
];
}
print_r(User::getFieldsNames());
// ['id', 'name', 'phone'] - On first call it works as expected, but...
print_r(Car::getFieldsNames());
// ['id', 'name', 'phone'] :(
If I declare $fieldNames in User and Car classes work fine, but in real project I has tens of static variables such $fieldNames and hundreds of entity's
Is it possible to best solution?
Maybe create small repository class that will keep these static variables by entity's id? or another elegant way?
Thanks any Help!
$fieldNames is static so it's associated with the class itself and not with a specific object.
The class in this instance is "Entity".
Once you set it it is no longer null.
Okay, let's say that I have a Post model with the attributes name, slug and content. I'd like to generate models with my ModelFactory, but want to set a specific name, which I do by overwriting the value:
factory(App\Post::class)->create(["name" => "Something here"]);
But now I want the slug to auto-generate by using the (new) name and without passing it as argument. Like
"slug" => str_slug($name);
Is this possible or do I need to write the slug manually?
When using the factory below with ->create(['name' => 'anything']); the slug is not created.
My current factory
$factory->define(App\Post::class, function (Faker\Generator $faker) {
static $name;
return [
'name' => $faker->name,
'slug' => $name ?: str_slug($name),
'content' => $faker->sentences(),
];
});
This should do the trick. You can pass a name in manually or let Faker handle it.
$factory->define(App\Post::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'slug' => function (array $post) {
return str_slug($post['name']);
},
'content' => $faker->sentences(),
];
});
Have you tried
$name ="Something here";
factory(App\Post::class)->create(["name" => $name, "slug" => str_slug($name)]);
in index.php :
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'ID_REQUEST',
'NOMOR_SURAT',
[
'label' => 'Nama Depan',
'attribute' => 'ID_KARYAWAN',
'value' => 'iDKARYAWAN.FIRST_NAME'
],
[
'label' => 'Nama Belakang',
'attribute' => 'ID_KARYAWAN',
'value' => 'iDKARYAWAN.LAST_NAME'
],
which is iDKARYAWAN is relation from another table in my model
class Request extends \yii\db\ActiveRecord {
/**
* #inheritdoc
*/
public static function tableName() {
return 'ytms_it.request';
}
public function getIDKARYAWAN() {
return $this->hasOne(Karyawan::className(), ['ID_KARYAWAN' => 'ID_KARYAWAN'])->from(Karyawan::tableName(). ' b');
}
How to merge those two column ?
For the elp, thanks.
Create method called getFullName() in related model and calculate full name using PHP concatenation:
use yii\helpers\Html;
...
/**
* #return string
*/
public function getFullName()
{
return Html::encode($this->name . ' ' . $this->surname);
}
Optionally define a label for it in attributeLabels() method of related model:
`fullName` => 'Label for full name',
Then in GridView it's possible to display full name of related model in one column like so:
1) The shortest form:
'relatedModel.fullName',
2) Overriding the label:
[
'attribute' => 'relatedModel.fullName',
'label' => 'Overrided label',
],
3) Using closure:
[
'attribute' => 'relatedModel.fullName', // The attribute name can be different in this case
'value' => function ($model) {
// You can calculate full name here.
// But it's better to just call according method since view is only for display.
return $model->author->fullName;
},
],
Another way is to calculate full name using SQL and include as a part of query result in separate column.
Use Active Record - Selecting extra fields official docs section as a guide, also see this related issue on Github - JoinWith - assign a column aliases to an attribute of related model.
Add $fullName as public property of related model class. Modify query like so:
use yii\db\Expression;
...
->joinWith(['relatedModel' => function (\yii\db\ActiveQuery $query) {
$query->addSelect('fullName' => new Expression("CONCAT(name, ' ', surname)")]);
}]
Then to display it in GridView column you can use one of the options desribed above, for example:
'relatedModel.fullName'
I am trying to setup the filter for related model in Yii2's GridView widget, but I am keep getting the error like the filter value must be an integer.
I have followed this question. Now, I have a two models Services.php and ServiceCharge.php.
In ServiceCharge.php the relation is setup like:
public function getServiceName()
{
return $this->hasOne(Services::className(),['id'=>'service_name']);
}
In the ServiceChargeSearch.php the code is like this:
<?php
namespace app\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\ServiceCharges;
/**
* ServiceChargesSearch represents the model behind the search form about `app\models\ServiceCharges`.
*/
class ServiceChargesSearch extends ServiceCharges
{
/**
* #inheritdoc
*/
public function attributes()
{
// add related fields to searchable attributes
return array_merge(parent::attributes(), ['serviceName.services']);
}
public function rules()
{
return [
[['id'], 'integer'],
[['charges_cash', 'charges_cashless'], 'number'],
[['id', 'serviceName.services', 'room_category'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$query = ServiceCharges::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->sort->attributes['serviceName.services'] = [
'asc' => ['serviceName.services' => SORT_ASC],
'desc' => ['serviceName.services' => SORT_DESC],
];
$query->joinWith(['serviceName']);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
$query->andFilterWhere([
'id' => $this->id,
// 'service_name' => $this->service_name,
'room_category' => $this->room_category,
'charges_cash' => $this->charges_cash,
'charges_cashless' => $this->charges_cashless,
])
->andFilterWhere(['LIKE', 'serviceName.services', $this->getAttribute('serviceName.services')]);
return $dataProvider;
}
}
and in my Gridview it is setup like this:
[
'attribute'=>'service_name',
'value'=>'serviceName.services',
],
Which is showing the services name from the related model correctly.
I am not able to see what I am doing wrong, but the filter field for the attribute for service is not showing at all.
Actually it is much simpler than it seems.
add the column_name to safe attribute.
Note: this should be relation Name
add the join with query - like - $query->joinWith(['serviceName','roomCategory']);
add the filter condition like:
->andFilterWhere(['like', 'services.services', $this->service_name])
->andFilterWhere(['like', 'room_category.room_category', $this->room_category]);
if like to add sorting add the code like:
$dataProvider->sort->attributes['service_name'] = [
'asc' => ['services.services' => SORT_ASC],
'desc' => ['services.services' => SORT_DESC],
];
$dataProvider->sort->attributes['room_category'] = [
'asc' => ['room_category.room_category' => SORT_ASC],
'desc' => ['room_category.room_category' => SORT_DESC],
];
5 you should also set the relation name say public $roomCategory
That's it. Both sorting and filtering for related table works perfectly.
Note: Remove default validation like integer for related column and default filtering generated by gii otherwise it will generate an error.
Update on Latest version:
Adding Public $attribute is not needed.
Adding safe attribute for relation is also not needed.
but the attribute in your current model, which you want filter is
to added to safe attribute that is a must.
and most importantly in your gridview, the related attribute has to
be in closure format.
that is example
[
'attribute=>'attribute_name',
'value=function($data){
return $data->relationname->related_table_attribute_name
}
],
remember it you are using relation_name.related_table_attribute_name filter somehow doesn't work for me.
There is a fairly comprehensive set of instructions on the Yii Framework website. The only thing to note is that the search model complains about the following lines, but everything appears to work as intended without them:
$this->addCondition(...);
For a model, PaymentEvent (table: subs_payment_event), which has a currency_id field linked to model Currency, this is the complete set of additional code (using the Basic template):
In the main model, PaymentEvent.php:
public function getCurrencyName()
{
return $this->currency->name;
}
In the search model, PaymentEventSearch.php:
public $currencyName;
In its rules:
[['currencyName'], 'safe'],
In the attributes of its setSort statement, include:
'currencyName' => [
'asc' => ['subs_currency.name' => SORT_ASC],
'desc' => ['subs_currency.name' => SORT_DESC],
'label' => 'Currency'
],
Before the grid filtering conditions:
$query->joinWith(['currency' => function ($q) {
$q->where('subs_currency.name LIKE "%' . $this->currencyName . '%"');
}]);
Finally, in the GridView columns array in the view (including my usual link across to the related model records):
[
'attribute' => 'currencyName',
'label' => 'Currency',
'format' => 'raw',
'value' => function ($data) {
return Html::a($data->currency->name, ['/currency/' . $data->currency_id]);
},
],