Yii2 Gridview case insensitive sortable column - php

I use yii2 and I want to make gridview column 'description' sortable in a case insensitive way. There is my code:
$dataProvider = new ArrayDataProvider([
'allModels' => $query->find(),
'sort' => [
'attributes' => ['name','description],
],
'pagination' => [
'pageSize' => $this->pageSize,
],
]);
When I click on column description to sort, it show like this:
Job Title
Doctor
Teacher
doctor
teacher
As you see it sort case sensitive I want to sort case Insensitive, how I can do that? Any idea?

In order to sort rows of ArrayDataProvider in a case-insensitive way you should extend ArrayDataProvider itself, because internally it uses ArrayHelper::multisort and if you want it sort the way you want you have to pass SORT_STRING | SORT_FLAG_CASE as fourth argument to the method. By default its value equal to SORT_REGULAR constant.
Here the implementation:
<?php
namespace app\dataproviders;
use yii\helpers\ArrayHelper;
/**
* Class ArrayDataProvider
*/
class ArrayDataProvider extends \yii\data\ArrayDataProvider
{
/** #inheritdoc */
protected function sortModels($models, $sort)
{
$orders = $sort->getOrders();
if (!empty($orders)) {
ArrayHelper::multisort(
$models,
array_keys($orders),
array_values($orders),
SORT_STRING | SORT_FLAG_CASE
);
}
return $models;
}
}
And after it use the extended class instead of \yii\data\ArrayDataProvider
Example of usage:
$dataProvider = \app\dataproviders\ArrayDataProvider([
'allModels' => $query->find(),
'sort' => [
'attributes' => ['name','description'],
],
'pagination' => [
'pageSize' => $this->pageSize,
],
]);

That is quite good what Akmal wrote!
Maybe you need to specify key to the ArrayDataProvider, to get right id if your actionColumn not working well!
$dataProvider = \app\dataproviders\ArrayDataProvider([
'allModels' => $query->find(),
'key'=> 'id',
And if you want to filter your data within your gridview, you can use this solution:
Yii2 GridView with ArrrayDataProvider search
I tried this also and it works!!
(And the last tip: if you want to filter case intensive way, you can use stripos instead of strpos).
Good luck!

Related

How to use dynamically created enum type in Lighthouse?

I have custom dynamically created Enum type MyCustomEnum witch I need to use in my ServiceProvider.
For example I call Type string now Type::string():
<?php
namespace App\Providers;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\ServiceProvider;
use Nuwave\Lighthouse\Schema\TypeRegistry;
use GraphQL\Type\Definition\EnumType;
class GraphQLServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #param TypeRegistry $typeRegistry
*
* #return void
*/
public function boot(TypeRegistry $typeRegistry): void
{
$typeRegistry->register(
new ObjectType([
'name' => 'MyOtherCustomType',
'fields' => function () use ($typeRegistry): array{
return [
'my_field' => Type::string()
];
},
])
);
}
}
How I can call this dynamically created type MyCustomEnum on line 'my_field' => ... ?
I have a php enum class named CountryEnum which has a static method called graphEnumType(). This method returns an array with this shape. I register it in AppServiceProvider like this so it can be used with graphql:
$typeRegistry->register(CountryEnum::graphEnumType());
Inside the php i treat it like a php enum and call it like this:
CountryEnum::AT->value;
Enums are not a Type, they do not get implemented like class or struct in any way they are true arrays, when your pass an enum, you don't actually pass the enum you pass one of the values the enum has registered to it, now this can be hidden if you don't supply values. to not be hidden when you define the enum you must supply the value for each option and then the values, therefore, have a type.
E.G
$episodeEnum = new EnumType([
'name' => 'Episode',
'description' => 'One of the films in the Star Wars Trilogy',
'values' => [
'NEWHOPE' => [
'value' => 4,
'description' => 'Released in 1977.'
],
'EMPIRE' => [
'value' => 5,
'description' => 'Released in 1980.'
],
'JEDI' => [
'value' => 6,
'description' => 'Released in 1983.'
],
]
]);
Now the enum Episode always has a type of int because the 3 options of the enum all have values that are ints, so the type of that enum value is an int. Therefore anything that uses that Episode Enum, they have to supply what value of the enum they want to save(E.G Episode.NEWHOPE) and that is enum value's actual value is actually what is saved (so the last E.G would actually save 4 and is there for an int), and that defines the type of what is saved/transferred, it is the type of the value.

How to use Doctrine Criteria to filter out array properties?

I'm adding a virtual property within a Symfony entity class. This property shall be computed based on another table data - specifically on a column that is of the Doctrine array type.
class RelatedEntity
{
/* ... */
/**
* #ORM\Column(type="array")
*/
protected $type;
The point is I would like to use Doctrine Criteria for this as it's supposed to be optimized on SQL level. So I did this:
public function getCreated()
{
$criteria = Criteria::create()->where(Criteria::expr()->contains('type', 'create'));
$relatedEntity = $this->getRelatedEntities()->matching($criteria);
if (!$relatedEntity) {
return null;
}
return $relatedEntity->getTimestamp();
}
But I get an empty result set. Even though Doctrine is building a correct SQL statement, which works when I type it manually into the PostgreSQL database.
...WHERE type LIKE '%create%'
What is wrong with this approach and how can it be solved? Right now I did the trick with the ArrayCollection filter method, but it loads all related entities I don't need.
Thank you for any ideas.
EDIT: This is not a duplicate of the mentioned question as I cannot use EntityManager or EntityRepository inside an entity. I need to use Criteria, so the solution proposed in the question doesn't work for me.
Check the results of getRelatedEntities()
Depending on how this collection was created, any one of several things may be happening. In particular, it may be using entity aliases, or may not be returning any which match your Criteria.
Collection populated from an aliased entity (i.e.: via a QueryBuilder join/select).
If getRelatedEntities is populated by Doctrine via QueryBuilder, you've likely aliased the Entities.
EX.: $queryBuilder->addSelect('thing')->leftJoin('root_alias.entity',
'thing')
In such a case, the Criteria must use the alias:
Criteria::expr()->contains('thing.type', 'create')
No matches for Criteria.
Dump your collection before filtering it, this could be a simple case of your query having already filtered out any potential matches.
Test your Criteria
All things considered, without any clue as to the structure of the collection you're trying to filter, we can only assess your criteria. Thus, test your criteria, and check the contents of the collection you are attempting to filter.
$criteria = Criteria::create()->where(Criteria::expr()->contains('type', 'create'));
$collection = new ArrayCollection([
[
'key' => 1,
'type' => 'somethingcreatesomething',
],
[
'key' => 2,
'type' => 'abra',
],
[
'key' => 3,
'type' => 'cadabra',
],
[
'key' => 4,
'type' => 'alacreate',
],
]);
dump($collection->matching($criteria));
Result
Doctrine\Common\Collections\ArrayCollection {#2536
-elements: array:2 [
0 => array:2 [
"key" => 1
"type" => "somethingcreatesomething"
]
3 => array:2 [
"key" => 4
"type" => "alacreate"
]
]
}

Yii2 - Use dataProvider multiple times with different sorting

So I have a dataprovider which is created in a controller like this:
$modelSearch = new SearchModel();
$data_provider = $modelSearch->search(Yii::$app->request->queryParams); // returns the data provider
Then I use the $data_provider in a view like this:
GridView::widget([
'dataProvider' => $data_provider,
'export' => false,
'columns' => [
...
],
...
But now I'd like to use the same data from the $data_provider but without pagination and other sorting specifications.
Tried this but doesn't work:
$data_provider->sort = ['defaultOrder'=> ['column_a' => SORT_ASC, 'column_b' => SORT_DESC]]
$data_provider->pagination = false;
I think that's because the data is already retrieved with the ->search() method. Do I need to create a whole new search model class? just to get a different sorting?
Thanks in advance!
You should use two dataProvider eg:
$modelSearch = new SearchModel();
$data_provider = $modelSearch->search(Yii::$app->request->queryParams);
$data_provider2 = $data_provider;
$data_provider2->pagination = false;
$data_provider2->sort = ['defaultOrder'=> ['column_a' => SORT_ASC, 'column_b' => SORT_DESC]]
return $this->render('your_view', [
'searchModel' => $searchModel,
'dataProvider' => $data_provider,
'dataProvider2' => $data_provider2,
]);
Before I asked the question I didn't know the dataProvider already retrieved the data on creation. So altering properties of the dataProvider doesn't do another search, it does different sorting but with the same data it already retrieved.
Answer:
I ended up making a new method in the searchModel class like this:
public function searchByCreatedAt() {
$query = Model::find()->orderBy(['created_at' => SORT_ASC]);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false,
]);
...
}

CakePHP 3: TranslateBehavior on a model that works as an alias

In a project I have two models, Products and Packages. Packages can be seen as containers of Products and to define the items in a package I've created a model PackageItem (which is basically a Product so its using the same table). Now Products (and so PackageItems) have translatable fields such as as a title and description.
ProductsTable.php contains:
$this->addBehavior('Translate', [
'fields' => ['title', 'description'],
'translationTable' => 'products_translations'
]);
$this->belongsToMany('PackageItems', [
'foreignKey' => 'package_id',
'joinType' => 'LEFT',
'joinTable'=>'products_package_items'
]);
PackageItemsTable contains:
$this->table('products');
$this->addBehavior('Translate', [
'fields' => ['title', 'description'],
'translationTable' => 'products_translations'
]);
$this->belongsTo('Products', [
'foreignKey' => 'package_item_id',
'joinType' => 'LEFT'
]);
Using TranslateBehavior I'm able return the translations on the Product but I can't figure out how to write the query I need to also return the translation on the PackageItems. This is my current query:
$package = $this->Products->find('translations')
->where(['business_id'=>$q['business_id'], 'id'=>$id, 'type'=>'Package'])
->contain([
'PackageItems'=>[
'Prices'=>function($q) {
return $q->where(['product_id'=>$this->product_id]);
}
]
])
->first();
You need two things
1) Set the proper reference name
The translate behavior on the PackageItemsTable class needs to be configured to use the same reference name (the value that is stored in the model column) as the behavior on the ProductsTable class, otherwise you'd never receive any translations, as it would by default look for PackageItems.
This is what the referenceName option can be used for. The reference name is being derived from the class name (not the alias), or for auto-tables, from the database table name or the alias. So for your ProductsTable class it would be Products.
Either set the name manually
$this->addBehavior('Translate', [
'fields' => ['title', 'description'],
'translationTable' => 'products_translations',
'referenceName' => 'Products' // there it goes
]);
or retrieve it dynamically from the behavior on the ProductsTable, like
$referenceName = $this->Products
->target()
->behaviors()
->get('Translate')
->config('referenceName');
This however would need to be done after adding the corresponding belongsTo association for the Products table!
2) Use the translations finder for the containment
You need to configure the PackageItems containment to use the translations finder, which is as simple as
contain([
'PackageItems' => [
'finder' => 'translations', // there you go
'Prices' => function ($q) {
return $q->where(['product_id' => $this->product_id]);
}
]
])
See also
API > \Cake\ORM\Behavior\TranslateBehavior::_referenceName()
API > \Cake\ORM\Behavior\TranslateBehavior::$_defaultConfig
API > \Cake\ORM\Query::contain()

Yii Dataprovider date sorting

I have ArrayDataProvider with field of type DateTime - birthdate.
I use that dataprovider for a gridview in view section.
Since birthdates include the birthyear the sorting is not working as expected.
Is there a way to somehow tell the sorting mechanism not to account years and only sort by month and day ? A custom sort function perhaps ?
Edit: Something like sort in C++ where you can pass the compare function.
Edited current solution description:
My solution currently is to include a birthyear as a separate field in array and birthdate's years are set to current year. Now dates are from the same year and the sorting works. But separate field for birth year doesn't feel right. I wish i could get all necessary data from one date object.
This separate field grinds my gears. It's not perfect.
Edit:
Oh, and its Yii2
Update
There are also a PHP array sorting functions that take a compare callback - for example uasort that can be used on associative arrays like mine:
uasort($persons, function($a, $b){
return strcasecmp( $b['date']->format('m-d'), $a['date']->format('m-d') );
});
Now i need to find the way to implement it into ArrayDataProvider. Any ideas here ?
If you have an array dataprovider you can not use sorting of the database of course.
Solution using Yii
To allow sorting by some expression you have to calculate the value beforehand and configure the sorting for the attribute to use the calculated value instead of the original one:
// assuming $data contains your data and 'birthdate' is the value you want to use for sorting
foreach($data as $key => $value) {
$data[$key]['sort_birthdate'] = date('m-d', strtotime($value['birthdate']));
}
$dataProvider = new \yii\data\ArrayDataProvider([
'allModels' => $data,
'sort' => [
'attributes' => [
'birthdate' => [
'asc' => [
'sort_birthdate' => SORT_ASC,
],
'desc' => [
'sort_birthdate' => SORT_DESC,
],
'label' => 'Date',
'default' => SORT_ASC
],
// list all other attributes here
],
]
]);
Solution extending Yii
If you want a custom comparison function, you have to extend the Yii class to support this.
You can create a custom ArrayDataProvider class which extends from the one that comes with Yii and override the sortModels()-method.
You should try something like
use yii\db\Expression;
$dataProvider->setSort([
'attributes' => [
................................
'birthday' => [
'asc' => [
new Expression('DATE_FORMAT(birthday, "%m%d")') => SORT_ASC,
],
'desc' => [
new Expression('DATE_FORMAT(birthday, "%m%d")') => SORT_DESC,
],
'label' => 'Birthday',
'default' => SORT_ASC
],
................................
]
]);
I have not tried this.
What works for sure is to do something like this
$query->select([
new Expression('DATE_FORMAT(birthday, "%m%d") as show_date'),
.................................
]);
Then
/**
* Setup your sorting attributes
* Note: This is setup before the $this->load($params)
* statement below
*/
$dataProvider->setSort([
'attributes' => [
'id',
'show_date' => [
'asc' => [
'sort_date' => SORT_ASC,
],
'desc' => [
'sort_date' => SORT_DESC,
],
'label' => 'Date',
'default' => SORT_ASC
],
'price',
'gst',
'cost',
'quantity',
'profit',
]
]);

Categories