Sonata Admin Bundle: show total count of collection on list view - sonata-admin

Is there any way to show total count of collection on list view? Imagine that there is a user that can have many links. How can I show total links count on list view?

Show field it is quite easy, there is solution for sorting by this virtual field.
Entity/Some.php more about count here Extra Lazy Associations
public function getCommentsCount()
{
return $this->getComments()->count();
}
SomeAdmin.php override createQuery and configure list field
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
if ('list' === $context) {
$rootAlias = $query->getRootAliases()[0];
//...
$parameters = $this->getFilterParameters();
if ('getCommentsCount' === $parameters['_sort_by']) {
$query
->leftJoin($rootAlias.'. comments', 'cm')
->groupBy($rootAlias.'.id')
->orderBy('COUNT(cm.id)', $parameters['_sort_order'])
;
}
//...
}
return $query;
}
/**
* #param ListMapper $listMapper
*/
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('id')
//...
->add(
'getCommentsCount',
null,
[
'sortable' => true,
'sort_field_mapping' => ['fieldName' => 'id'],
'sort_parent_association_mappings' => [],
]
)
//....
}
service.yaml add "simple" paginator (count does not work correctly)
tags:
- { name: sonata.admin, pager_type: "simple", ...
Reasons:
subquery in orm join is not allowed
subquery in orm orderBy is not allowed
HIDDEN field does not work
\Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery::getFixedQueryBuilder
(// for SELECT DISTINCT, ORDER BY expressions must appear in idxSelect
list)

My answer is similar to Khalid (above) but has some key differences.
If you wrap the collection in a count( $entity->getLinks() ) then this will issue a query which returns every link association.
The downside of this is that if you have 1000s of Links associated, the memory resources required will need to be sufficient for hydrate each entity. (Which can be huge if you have thousands of different entities).
Instead, you should mark your Entities as EXTRA_LAZY and then use the --$entity->getLinks()->count()` method which will not do any hydration, instead it will only issue the COUNT queries.
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html
So do the following:
/**
* #ManyToMany(targetEntity="Links", mappedBy="whatever", fetch="EXTRA_LAZY")
*/
public $links;
Then you can call:
public function getTotalLinks(){
return $this->getLinks()->count();
}
And it will be super quick.

with Sonata 3.**
in somwhere in Admin***.php script for listing all fields:
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
//...
->addIdentifier('getCommentsCount', IntegerType::class, [ 'label' => '#Comments'])
;
}
Where in Entity i written something like this:
public function getCommentsCount()
{
return $this->comments->count();
}
that works for me )

Yes you can show the total count of links for each user, i assume you have arraycollection of links defined in your user entity, define a property named as $totalLinks and in getter of that property return count of links something like below
class User{
public $totalLinks;
public function getTotalLinks(){
return count($this->getLinks());
}
}
and then in your configureListFields() you can add $totalLinks property
protected function configureListFields(ListMapper $list)
{
$list
->add('...')
->add('...')
->add('totalLinks');
}

Found answer here:
SonataAdminBundle custom rendering of text fields in list
I'm using Sonata 2.3 so TWIG template should be like:
{% extends admin.getTemplate('base_list_field') %}
{% block field %}
{{ value|length }}
{% endblock %}

Related

compare two arrays Laravel

I have articles that can contain different tags. For example, 5 pieces: (php, html, css, laravel, js) And I have groups that can also contain different tags. For example 4 pieces: (laravel, php, html, css)
I have already defined the relationships and it works. What I still lack is the linkage of article to the group.
In the Articlecontroller i use this so sync:
$article->groups()->sync($group->id);
Article Model
public function groups()
{
return $this->belongsToMany('App\Group');
}
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
Tag Model
public function articles()
{
return $this->morphedByMany('App\Article', 'taggable');
}
public function groups()
{
return $this->morphedByMany('App\Group', 'taggable');
}
Group Model
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
public function articles()
{
return $this->belongsToMany('App\Article');
}
Controller
$groups = $user->groups()->latest()->with('tags')->paginate(20);
$mostvotedarticle = Article::where('type', 4)->whereIn('privacy', [1, 3])->orderByVotes()->first(); //show only article with the same tags
$imagearticle = Article::with('comments')->whereIn('type', [4, 5])->where('status', 1)->latest()->paginate(30); //show only articles with the same tags
I would now like to compare the tags of the groups and the tags of the posts. If the posts have the same tags as the group, I want to display the posts.
simply check for equality
$equal = ($tagsFromPost == $tagsFromGroup) //TRUE if both have the same values but NOT in order
$equal = ($tagsFromPost === $tagsFromGroup) //TRUE if both have same values in SAME order
then something like
if($equal){
<statements>
}
to check for FALSE if(!$equal)...

Laravel - Display Array/String Object in view from database

Data are stored as ["item_1", "item_2"] in database like shown below.
I want to display those data in view blade properly.
Product Model
protected $fillable = ['name', 'prod_id'];
public function models() {
return $this->hasMany(Model::class, 'prod_id');
}
Model Model
protected $fillable = ['model', 'prod_id'];
protected $cat = ['model'=>'array'];
public function model()
{
return $this->belongsTo(Product::class, 'prod_id');
}
Controller - store method
public function create (Request $request, Product $product){
$models = new Model;
{
$model = json_encode(request('models'));
$items->models = $model;
$product->models()->save($models);
}
}
Controller show method
public function show(request $id){
$product = Product::findorfail($id);
$models = Model::with(['model'])->where('prod_id', $product->id)->get();
return view ('show', compact('product', 'models'));
Create View
<input type="checkbox" name="model[]" value="Samsung">
<input type="checkbox" name="model[]" value="Nokia">
<input type="checkbox" name="model[]" value="Apple">
<button>Add Model</button>
I tried show view:
#foreach($models as $model)
{{ json_decode($model->models) }}
#endforeach
It throws
htmlspecialchars() expects parameter 1 to be string, array given
What am I missing.
PS: MySQL does not support json column, so I saved as text column.
you need to do someting like this.
Model Model
protected $fillable = ['models', 'prod_id']; // screenshot says that the field name is "models"
protected $cast = ['models' => 'array']; // the property is $cast no $cat
public function product()
{
return $this->belongsTo(Product::class, 'prod_id');
}
ModelController - store method
public function store (Request $request){
$product = Model::create([
'models' => json_encode($request->models),
'prod_id' => $request->prod_id
]);
return redirect()->back()->with('success', 'created!');
}
public function show(Request $id){
$model = Model::findOrFail($id)->with('product');
return view ('model.show', compact('model'));
}
ProductController show method
public function show(request $id){
$product = Product::findOrFail($id)->with('models'); // the method name is findOrFail() no findorfail
// $models = Model::with(['model'])->where('prod_id', $product->id)->get();
return view ('show', compact('product'));
}
Into the show View
#foreach($product->models as $models)
#foreach(json_decode($models->models) as $model)
{{ $model }}
#endforeach
#endforeach
Your Models Model confuses me a little bit. You seem to have a field name model that's the same as a relationship method name. That means whenever you include that relation, it'd functionally override that property with data from the related table. (I say 'functionally' because you're using dynamic properties, whereas it is actually possible to explicitly tell Eloquent whether you want an attribute or relation without making it guess.)
That said, your $model->models property could be coming back as an array for one of two reasons. The first is that it may be accidentally referring to a relational data-set and not the JSON string you were expecting. The second is you've corrected the protected $cat = ['model'=>'array']; to read protected $cast = ['models'=>'array'];, and it's stepping on your toes now. By casting it to an array, it may be getting automatically get interpreted back into one before you call json_decode on it.
Either way, I'd dd($model->models) to see what it is first.
You need to change your foreach like this:
#foreach($models->models as $model)
{{ json_decode($model) }}
#endforeach
because Your array is like this
{"id":18,"prod_id":22,"models":{"id":22,"user_id":1}}
In here the $models is getting only id and prod_id models is still array so your foreach should be #foreach($models->models as $model)
Sample Code is here:
$arr = '{"id":18,"prod_id":22,"models":{"id":22,"user_id":1}}';
echo '<pre>';
foreach (json_decode($arr->models) as $str){
echo $str;
}

How do you set a scenario when doing a restful call in Yii2 to return certain fields

Im currently making a Yii2 RESTful system with AngularJs.
In my database i've got several columns that i want to be able to return when doing a particular call from a certain point in my system.
The problem i'm having is how do i return only a handful of fields eg(id, title and stub) from the restful call in another part of my system so that it ignores other fields in the table.
I would ideally like it to work in a similar way to how a Models rules work with scenarios in yii.
There are two methods, I think:
1. use params
// returns all fields as declared in fields()
http://localhost/users
// only returns field id and email, provided they are declared in fields()
http://localhost/users?fields=id,email
// returns all fields in fields() and field profile if it is in extraFields()
http://localhost/users?expand=profile
// only returns field id, email and profile, provided they are in fields() and extraFields()
http://localhost/users?fields=id,email&expand=profile
2. overriding model's fields()
// explicitly list every field, best used when you want to make sure the changes
// in your DB table or model attributes do not cause your field changes (to keep API backward compatibility).
public function fields()
{
return [
// field name is the same as the attribute name
'id',
// field name is "email", the corresponding attribute name is "email_address"
'email' => 'email_address',
// field name is "name", its value is defined by a PHP callback
'name' => function () {
return $this->first_name . ' ' . $this->last_name;
},
];
}
// filter out some fields, best used when you want to inherit the parent implementation
// and blacklist some sensitive fields.
public function fields()
{
$fields = parent::fields();
// remove fields that contain sensitive information
unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
return $fields;
}
more detail, refer to https://github.com/yiisoft/yii2/blob/master/docs/guide/rest-resources.md
You may use scenarios method inside your model for this, but you will have to extend a bit toArray method in order to make it work properly:
public function scenarios()
{
return array_merge(parent::scenarios(), [
'simple_info' => [
'email',
'name',
],
'login' => [
'id',
'email',
'name',
'auth_token',
],
]);
}
public function toArray(array $fields = array(), array $expand = array(), $recursive = true)
{
$scenarios = $this->scenarios();
$scenario = $this->getScenario();
if (!empty($scenarios[$scenario])) {
$data = parent::toArray($fields, $expand, $recursive);
return array_intersect_key($data, array_flip($scenarios[$scenario]));
}
return parent::toArray($fields, $expand, $recursive);
}
After this you may simply do something like this:
$model = new LoginForm();
if ($model->load(Yii::$app->request->post(), '') && $model->login()) {
$user = $model->getUser();
// Lets change scenario to login in order to get `auth_token` for authorization
$user->setScenario('login');
$user->generateAuthKey();
$user->save(FALSE);
return $user;
} else {
return $model;
}
As a side note (expanding on the answer from #Ganiks), if you are manually returning the list of Models, you will need to return them as a DataProvider (rather than simply as an array of Models) for the fields parameter to have an effect.
For example, if you do something like this...
class UserController extends yii\rest\Controller
{
public function actionIndex()
{
return User::find()->all(); // Not what you want
}
// ...
}
... then the fields parameter will not have the desired effect. However, if you instead do this...
class UserController extends yii\rest\Controller
{
public function actionIndex()
{
return new ActiveDataProvider([
'query' => User::find(),
'pagination' => false,
]);
}
// ...
}
... then the returned fields will only be those you specified in the fields parameter.

Laravel one-to-many relationship returns null

So here's what i have set up. I have two tables; users and todos. Every user can have multiple "todos".
Here's what the tables look like:
Users:
Todos:
And the models:
class User extends Eloquent
{
public function todo() {
$this->has_many('todo');
}
}
And...
class Todo extends Eloquent
{
public function user() {
$this->belongs_to('user');
}
}
Note that i already have a "todo" attached to the user in the database. So, with that said, should i not be able to do the following:
$user = User::find(1)->todo;
To get the todo's for that user? I'm currently getting Null when dd()-ing it:
array(1) {
["todo"]=>
NULL
}
So, any ideas? I tried removing belongs_to() from the Todo's model, because it shouldn't be needed right? Either way it makes no difference. Am i missing something?
You need to return the relation objects, e.g.
class User extends Eloquent
{
public function todos() {
return $this->has_many('Todo');
}
}
Also note that relations that return an array (e.g. has_many, has_many_and_belongs_to) are typically named plural, so 'todos' versus 'todo.

Zend Framework: Populating DB data to a Zend Form dropdown element

I have the following form:
<?php
class Application_Form_RegistrationForm extends Zend_Form{
public function init(){
$country = $this->createElement('select', 'country');
$country->setLabel('country: ')
->setRequired(true);
$email = $this->createElement('text', 'email_address');
$email->setLabel('Email Address: ')
->setRequired(true);
$register = $this->createElement('submit', 'register');
$register->setLabel('Create new Account')
->setIgnore(true);
$this->addElements(array(
$country, $email, $register
));
}
}
?>
The list of the countries are present in a table country in a database.
Is there anyway I can populate the country dropdown list with the country names from the database?
Any help is appreciated.
Thanks
You sure can.
In the init method you can set the options with something like this, assuming $db is a Zend_Db adapter:
$options = $db->fetchPairs('SELECT id, name FROM country ORDER BY name ASC');
$country->setMultiOptions($options);
In case you haven't seen the fetchPairs method, it builds an array, where the first column return becomes the key, and the second column the value.
You could do it from controller's action (or even in Service Layer, if to be meticulous), if your list's content depends from some conditions.
Usage:
$form->getElement('country')->addMultiOption('1','USA'); //add single value
$form->getElement('country')->addMultiOptions(array('1'=>'USA', '2'=>'Canada')); //add values by array
$form->getElement('country')->setMultiOptions(array('1'=>'USA', '2'=>'Canada')); //set values by array
Of course, to add values from DB you need to fetch them first.
See http://framework.zend.com/manual/en/zend.form.standardElements.html#zend.form.standardElements.select for more methods available.
The best way is to create a new class for the element:
Put this in "/application/form/element/CountySelect.php"
class Application_Form_Element_CountrySelect extends Zend_Form_Element_Select {
public function init() {
$oCountryTb = new Application_Model_Country();
$this->addMultiOption(0, 'Please select...');
foreach ($oCountry->fetchAll() as $oCountry) {
$this->addMultiOption($oCountry['id'], $oCountry['name']);
}
}
}
And then add it to the form this way:
class Application_Form_RegistrationForm extends Zend_Form{
public function init() {
$this->addElement(new Application_Form_Element_CountrySelect('country_id'));
}
}

Resources