Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jcc5018's avatar

Getting demographics for models,

So, I have tried getting help on this problem before, and have a good start, but i am having difficulties figuring out how to implement the method to get my required data. I put it off for a while cause it was taking too long to figure out, but now I am trying again. I'm not sure if it should be called in blade, or after the model query, or what.

I am simply trying to parse a users list, with related models and count gender and age groups per model.

In this instance. People who like a hobby. But I'd like to make this a trait that I can use elsewhere to get demographics for people who like a group, live in certain areas, etc.

This is in my Demographics trait:

trait Demographics
{
    public $itemStats  = [];
    public $userCounts = 0;
    public $genders    = ['male', 'female', 'other'];
    public $ages       = ['minors'   => [0, 17],
                          'twenties' => [18, 29],
                          'thirties' => [30, 39],
                          'forties'  => [40, 49],
                          'fifties'  => [50, 59],
                          'sixties'  => [60, 200],];


    public function getAges ()
    {
        foreach ($this->ages as $ageKey => [$min, $max]) {
            $ageCount                        = $data->users->whereBetween('age', [$min, $max])->count();
            $itemStats[$ageKey . '_count']   = $ageCount;
            $itemStats[$ageKey . '_percent'] = round(($ageCount / $userCount) * 100);
        }
    }

    public function getGenders ()
    {
        foreach ($this->genders as $gender) {
            $genderCount                   = $data->users->where('gender', $gender)->count();
            $itemStats[$gender . '_count']   = $genderCount;
            $itemStats[$gender . '_percent'] = round(($genderCount / $userCount) * 100);
        }

    }

I think my main point of confusion is do I reference getGenders/ Ages, or $itemStats.

In the Hobbies livewire component I have

  public function getHobbiesQueryProperty ()
    {
        $status  = $this->selectedStatus;
        $cats    = $this->selectedCategory;
        $hobbies = Hobby::with('equipment', 'categories', 'hobbyAltNames', 'hobbyType')
            ->withCount('users')
            ->when($this->search != '', function ($q) {
                $q->search(trim($this->search));
            })
            ->when($status != '', function ($q) use ($status) {
                $q->status($status);
            })
            ->when($cats != '', function ($q) use ($cats) {
                $q->filterByCategory($cats);
            });
...MORE SORTING  CODE
}
    public function getHobbiesProperty ()
    {
        return $this->hobbiesQuery->paginate($this->paginate);

    }

and in render I have $data = $this->hobbies;

Then in my blade I have a tab divided list of my hobbies. Meaning there is a lot of data displayed for each hobby, so one tab may have categories and other data displayed, another tab has counts of related objects, and my demographics tab will simply display demographics when i get it working. The data itself is paginated. All the other tabs display the appropriate info, but I cannot get the demographics data displayed.

@foreach($data as $hobby)
            <tr>
                <td></td>
                <td><a href="{{route('hobbies.show', $hobby->id)}}">{{$hobby->hobby_name}}</a></td>
                <td>{{$hobby->users_count}}</td>
                <td>{{$hobby->male_percent}}</td>
                <td>{{$hobby->female_percent}}</td>
                <td>{{$hobby->other_percent}}</td>

                <td>{{$hobby->minors_percent}}</td>
                <td>{{$hobby->twenties_percent}}</td>
                <td>{{$hobby->thirties_percent}}</td>
                <td>{{$hobby->fourties_percent}}</td>
                <td>{{$hobby->fifties_percent}}</td>
                <td>{{$hobby->sixties_percent}}</td>
@endforeach

So again my problem is what and where to reference the created itemstats from the demographics trait. And users count has to be determined for each related model for the calculation. So somehow I need to take the value created by " ->withCount('users')" in the hobbies query and use it for the getAges and get genders methods. I had a getStats function somewhere as well, that I guess would reference the previous two.

I'm not sure if having the itemstats is necessarily appropriate here as I think that would require a separate loop if I am not mistaken. But I need the stats to be included in the $data variable along with the other data.

0 likes
27 replies
geowrgetudor's avatar

I don't think doing it in a tait is a good idea, but you can start from this idea and maybe use scopes if you need the same functionality somwhere else

$minAge =0;
$maxAge = 17;
$gender = 'male';

$hobbies = Hobby::with('equipment', 'categories', 'hobbyAltNames', 'hobbyType')
            ->withCount([
				'users as total_users',
				'users as total_ages' => function($query) use ($minAge, $maxAge) {
						$query->whereBetween('age', [$minAge, $maxAge]);
				},
				'users as total_genders' => function($query) use ($gender) {
						$query->where('gender', $gender);
				}
			])
// rest of the query
jcc5018's avatar

@geowrgetudor I can see where you are headed with this with the as "total_users' and such.

But if my desired output is 'male_percent' female percent etc... where would be the place to put the calculations?

Im assuming it would still be beneficial to create a separate method for this and then call it in this method string. but I'm still a little confused how it would work.

my first thought is to create a getAges() and getGenders() method but how do you use the "total_users' designation you suggested?

    public function getAges ($hobbies, $totalusers)
    {
        $ages       = ['minors'   => [0, 17],
                       'twenties' => [18, 29],
                       'thirties' => [30, 39],
                       'forties'  => [40, 49],
                       'fifties'  => [50, 59],
                       'sixties'  => [60, 200],];
        foreach ($this->ages as $ageKey => [$min, $max]) {
            $ageCount  = $hobbies->users->whereBetween('age', [$min, $max])->count();
          return   $ageKey . '_percent' = round(($ageCount / $totalusers) * 100);
        }
    }

you only showed one age range, but can you loop within a query string? to do something like i have above? or perform calculations to return the $key.percentage??

my ide doesnt like the return $ageKEy.'_percent' designation.

kokoshneta's avatar

If I’m understanding your code correctly, it’s mostly just a matter of missing and incorrect variables:

  • You use $data in your trait (i.e., class) methods, but you don’t define it anywhere except in your Blade template; you have to pass it on as a parameter to the trait methods, otherwise the methods won’t know about them, since class methods don’t have access to global-scope variables (unless explicitly defined – but don’t do that!).
  • Your trait methods don’t return anything or set any of the class properties the trait defines, so running them will do… pretty much nothing. You set $itemStats['xyz_count/xyz_percent'] in the methods, but that’s just a local variable that only exists in the method itself – you need to be setting the class properties $this->itemStats['xyz'] instead.
  • You reference $userCount in both trait methods, but don’t define it anywhere – I’m guessing that should probably be $this->userCounts, which is currently not used for anything, but I’m not sure whether you intend those to be different entities.

Assuming that YourModel::hobbies (= $this->hobbies in render) is a Laravel collection, updating your trait to the following should work:

use Illuminate\Support\Collection;

trait Demographics
{
	public $itemStats  = [];
	public $userCount  = 0;
	public $genders    = ['male', 'female', 'other'];
	public $ages       = ['minors'   => [0, 17],
						  'twenties' => [18, 29],
						  'thirties' => [30, 39],
						  'forties'  => [40, 49],
						  'fifties'  => [50, 59],
						  'sixties'  => [60, 200],];


	public function getAges (Collection $data)
	{
		foreach ($this->ages as $ageKey => [$min, $max]) {
			$ageCount         		               = $data->users->whereBetween('age', [$min, $max])->count();
			$this->itemStats[$ageKey . '_count']   = $ageCount;
			$this->itemStats[$ageKey . '_percent'] = round(($ageCount / $this->userCount) * 100);
		}
	}

	public function getGenders (Collection $data)
	{
		foreach ($this->genders as $gender) {
			$genderCount            		       = $data->users->where('gender', $gender)->count();
			$this->itemStats[$gender . '_count']   = $genderCount;
			$this->itemStats[$gender . '_percent'] = round(($genderCount / $this->userCount) * 100);
		}

	}
}

Like @geowrgetudor, though, I don’t really see a reason for the trait. Are you planning for this demographics functionality to be available and used in different models than just the Hobby model (and any child classes that extend this class)? Should it be available in a Job model, for instance, which doesn’t otherwise have anything to do with the Hobby model?

jcc5018's avatar

@kokoshneta Thanks, I set it as a trait because I want to use it for multiple models. I defined some in my initial post, but Hobbies, groups, Cities, countries, events, etc "What demographics like my group" "What demographics from this city/ country/ state are members of my site." "What demographics clicked on my ad" etc

I did realize some of the things were missing the variable definitons. I think thats partly because i have the code in several places trying to get it to work in different variations.

So if I make your change, the question would then become, where would I then update the userCount This needs to change for every called model.

IE 100 people like XYZ, 47% male 53 Female... etc

jcc5018's avatar

@kokoshneta I altered the code like this:

    public function getAges (Collection $data)
    {
$userCount = $data->userCount;
        $ages = ['minors'   => [0, 17],
                 'twenties' => [18, 29],
                 'thirties' => [30, 39],
                 'forties'  => [40, 49],
                 'fifties'  => [50, 59],
                 'sixties'  => [60, 200],];
        foreach ($this->ages as $ageKey => [$min, $max]) {
            $ageCount = $data->users->whereBetween('age', [$min, $max])->count();
            return $ageKey . '_percent' = round(($ageCount / $userCount) * 100);
        }
    }

My render method

   public function render ()
    {
        //        $all = Hobby::;

        //Todo refactor queries
        $top10    = Hobby::withCount('users')->where('status', 1)->orderByDesc('users_count')->take(10)->get();
        $new      = Hobby::withCount('users')->where('status', 1)->orderByDesc('created_at')->take(10)->get();
        $active   = Hobby::where('status', 1)->count();
        $pending  = Hobby::where('status', 0)->count();
        $rejected = Hobby::where('status', 2)->count();
        $data     = $this->hobbies;
        //        $stats    = $this->stats;
        //$stats = $this->hobbiesQuery->getStats()->paginate($this->paginate);
        //        dd($data);
        $categories = Tag::where('tag_type', 1)->get();
        $ratings    = Tag::where('tag_type', 13)->get();
        $topEarner  = $this->displayIncome();
        return view('hobby.livewire.hobby-manager',
                    compact(
                        'data',
                        'top10',
                        'new',
                        'ratings',
                        'categories',
                        'topEarner',
                        'active',
                        'pending',
                        'rejected'
                    //'stats'
                    ));
    }

First, there's probably a way to refactor this, but if $data = $this->hobbies; is the main data for the table, (the rest is stuff for quick reference above the table) would the best solution be $ages= getAges (Hobby $data) and same for genders? cause if so, that would require a separate table loop wouldnt it? it would no longer fit in with the $data itself?

I'm sorry if i am missing the basic concept here. That's why its still not done after 3 months

kokoshneta's avatar

@jcc5018 If you do use a trait, then $userCount (and the other properties set in the trait) become properties of the models that use the trait. So if you do use Demographic in your Hobby model, you’ll have Hobby::$userCount, etc., and every instance of your model will have its own user count and so on. Updating the user count would then be done on each instance, preferably in a method defined in the trait.

(If you want to adhere more to Laravel-like naming conventions, you should probably name the trait something like Demographicable or HasDemographics or something like that, since traits should generally describe an adjective-like attribute that can be applied to the class.)

jcc5018's avatar

@kokoshneta ok: renaming would be an easy enough fix. I also added

public function getUserCounts ($value)
    {
        $this->userCounts = $value->withCount('users');

    }

to define the userCount. Though probably not set up correctly either.

So Im not sure if trait is the way to go or something else. (just trying to get that shared logic somewhere else)

and I call use Demographics within livewire, not the model if that makes a difference.

I think the functions themselves would work, i just cant figure out where to call them to get the expected values included in the $data variable.

Can you do loops and calculations from within an eloquent query?

Basically this if i could format it right.

$hobbies = Hobby::with('equipment', 'categories', 'hobbyAltNames', 'hobbyRatings', 'hobbyType')
    ->withCount('users as total_users')
   // AMENDMENT-- not sure what Id change $data to
    with(
     foreach ($this->genders as $gender) {
         $genderCount                     = $data->users->where('gender', $gender)->count();
        
         $itemStats[$gender . '_percent'] = round(($genderCount / $total_users) * 100);
     })
        //End Amendment
    ->when($this->search != '', function ($q) {
        $q->search(trim($this->search));
    })
    ->when($status != '', function ($q) use ($status) {
        $q->status($status);
    })
    ->when($cats != '', function ($q) use ($cats) {
        $q->filterByCategory($cats);
    });
''''

and assuming itemstats needs to become something else to simply add to the $hobbies collection. 
kokoshneta's avatar

First, there's probably a way to refactor this, but if $data = $this->hobbies; is the main data for the table, (the rest is stuff for quick reference above the table) would the best solution be $ages= getAges (Hobby $data) and same for genders? cause if so, that would require a separate table loop wouldnt it? it would no longer fit in with the $data itself?

I'm sorry if i am missing the basic concept here. That's why its still not done after 3 months

It sounds to me like the thing you’re missing is how traits work. I think that’s why I was (and am) a bit unclear on what exactly $data represents. And what exactly the $this in $this->hobbies is.

A trait is essentially just a way to share code between classes. Any properties and methods you define in a trait become an integral part of any class that uses the trait. So if you use your demographics trait in your Hobby model, for example, you can use $hobby->userCount, $hobby->getAges(), etc.

You can’t use a trait without using it in a class, so you can’t just do getAges() on its own. You’d have to define a helper function (= a function in the global scope, not a class method) for that.

So if the $data that you’re using inside the getAges() method is actually itself an instance of the Hobby model, then you don’t need the parameter in the method – you just use $this instead. But of course that’s only if you’ll be calling the getAges() method on a Hobby instance.

In other words, this should work if it fits your flow (leaving out the values):

class Hobby {
	use Demographicable;

	[properties and methods here]
}

trait Demographicable {
	public $itemStats = [], $userCount = 0, $genders = [xyz], $ages = [xyz];

	public function getAges() {
		foreach ($this->ages as $ageKey => [$min, $max]) {
			$ageCount = $this->users->whereBetween(…)->count();
			$this->itemStats[$ageKey . '_count'] = $ageCount;
			$this->itemStats[$ageKey . '_percent'] = round(($ageCount / $this->userCount) * 100);
		}
	}

	// and then getGenders() here
}

// In your renderer or template, wherever it makes sense

$obj = new ContainerThingy(); // <-- whatever $this is in your '$this->hobbies'

foreach ($this->hobbies as $hobby) {
	$hobby->getAges();
}

// In your Blade template

@foreach ([$this?]->hobbies as $hobby)
	<tr>
		<td>{{ $hobby->userCount }}</td>
		<td>{{ $hobby->male-percent }}</td>
		… etc. …
	</tr>
@endforeach

Does that make sense for you?

kokoshneta's avatar

and I call use Demographics within livewire, not the model if that makes a difference.

Can you post an example of what you mean here exactly? I’m not very familiar with Livewire, so I don’t know what exactly you mean by “call use Demographics within livewire, not the model”. Which class includes the use Demographic(s/able) statement?

jcc5018's avatar

@kokoshneta No still confused unfortunately $data = my collection of hobbies or could be my collection of groups or whatever.

And I know traits share code between classes, But im still struggling passing the right parts to the right pieces at the right time. I've got a big mess of code, and honestly im confusing myself.

So just to make sure I am somewhat going in to right direction: I'm going to define how I think the flow should go.

  1. I have my hobbyManager.php defined -- livewire component uses traits use WithPagination, SortFilter, Demographics; My sort filter trait works fine.

  2. I define my $hobbies collection which takes into account my sorting, filtering, and search...

public function getHobbiesQueryProperty ()
    { $hobbies = Hobby::with () 
->withCount ('users' as UserCount)
->when (search)
->when('filters')
etc;

I then do sorting with a long switch statement to account for relations and such. eventually returning $hobbies I have this all separate because i need the full data set for the various lists , total counts, etc.

  1. For pagination I create :
   public function getHobbiesProperty ()
    {
        return $this->hobbiesQuery->paginate($this->paginate);

    }

4 in Render i define $data = $this->hobbies; which then gets looped through to produce my table.

Somewhere I have to add 10 rows of calculated demographic data to the $data property.

Now at one point, I had this all working because i did the calculations within blade itself, which if i can't make this work, ill just go back to. But i really need to figure out how to do this properly.

The $data variable within the getAges/ Genders i know is wrong, but it is supposed to be doing this.

For each row of data (foreach hobby)
$userCount = $hobby->userCount;  //Count users who like model
{foreach gender as $gen}
..code

{foreach Age as $age)
$ageCount = $this->users->whereBetween(…)->count();
	$this->itemStats[$ageKey . '_percent'] = round(($ageCount / $this->userCount) * 100);
// NOTE I do not need ageKey Count  only the percent. 
)

now this should probably be accomplished with the initial DB query, but that is where I am struggling. DO i need to write out each individual calculation separately within the query?

So honestly, im not sure if the itemStats property is needed in this or how I would use it, as it was most likely suggested for looping through the $itemStats itself opposed to being included in an existing collection. So I'm still confused the proper set up if this is the case. You did not include it in your example blade either. But if i am understanding correctly, all the age and gender designations are being stored in a big itemstats array. and not the hobby ($data) collection right? So how are we going from $itemStats to $hobby? Would it simply become a matter of merging these items, or what?

Sorry again if I am being totally blind to the answer.

Desired output https://imgur.com/a/CxlrJZu

jcc5018's avatar

@kokoshneta

Livewire component start:

use App\Traits\Demographics;
use App\Traits\SortFilter;
use Livewire\Component;
use Livewire\WithPagination;


class HobbyManager extends Component
{
    use WithPagination, SortFilter,
        Demographics;

    public $search = '';

    //    public    $categories;
    //    public $sortField = 'hobby_name';
    //    public    $sortDirection   = 'asc';
    protected $paginationTheme  = 'bootstrap';
    public    $paginate         = 25;
    public    $checked          = [];
    public    $selectPage       = false;
    public    $selectAll        = false;
    public    $tab              = 'directory';
    public    $incomeRange      = '';
    public    $selectedCategory = '';
    public    $selectedStatus   = '';
    public    $banned_count     = 0;

kokoshneta's avatar

@jcc5018 Okay, I’m understanding you better now.

But why do you use the demographics trait in the Livewire component? Using the pagination and sorting traits makes sense for the component, because those are properties of the collection (or the manager that manages the collection); but the demographics are specific to each individual hobby, not to the component that is used to manage the whole collection of hobbies. The manager doesn’t have any demographics.

Your getHobbiesQueryProperty() and getHobbiesProperty() accessors are, I presume, defined in the Livewire component HobbyManager, right? So you’re fetching a collection of Hobby objects which are stored in $hobbyManager->hobbies, correct? And in the render() method in your Livewire component, you pass $hobbyManager->hobbies (a collection of Hobby objects) through to the Blade template as $data.

That means when you loop over foreach ($data as $hobby) in the Blade template, each $hobby will be a fully hydrated instance of the Hobby class and have access to all the properties and functions available in that class – including $hobby->users, which will be a collection of User objects.

This is where you should be using the demographics trait, on the Hobby model. If you use it here, the getAges() and getGenders() methods will be methods of each individual hobby object, and you store the stats within each object as well, and you can access them directly in your foreach loop in the Blade template. That is what you want. That is also roughly what I wrote in the sample code in my previous answer, so that should be usable.

jcc5018's avatar

@kokoshneta

"But why do you use the demographics trait in the Livewire component?" <-- cause i dont know what I'm doing, lol

But your explanation makes sense I think. So if I simple move the demographics trait from hobbymanager component to hobby model, i should be able to call ($hobby->getGender->male_percent) in blade and so forth? Or loking at your sample again, I could add ->getAges() to the query itself? LET ME TRY THIS.

again though, do I need ItemStats?

jcc5018's avatar

@kokoshneta

Ok, I'm sorry, still not quite there:

I moved the demographics trait to hobby model:

Not sure if it is still correct. Its posted below: (or above, not sure how this site orders things)

But is the Collection $data supposed to be replaced with something?

I tried this in the query

 $hobbies = Hobby::with('equipment', 'categories', 'hobbyAltNames', 'hobbyRatings', 'hobbyType')
            ->withCount('users', 'childHobby', 'hobbyGroups', 'hobbyArticles', 'hobbyEvents', 'hobbyCourses', 'hobbyForumPosts')
            ->getAges()->getGenders()

but if that's expecting variables, what do i put?

I also tried

{{$hobby->getGender()->female_percent}} in blade, but that presents an error also.

I'm hoping once i can figure out the concepts behind this it will help with some of my other problems I've gotta deal with later, cause this is still confusing me. I know what it needs to do, just cant get it to do it. Seems so simple, but im not getting it. Im most likely not putting the methods or files in the right spots.

kokoshneta's avatar

@jcc5018 Yes, exactly, you can then call $hobby->getGender(), etc.

Whether you need the $itemStats property or not depends on how you structure things, but it probably makes sense to keep it.

If you keep the $itemStats property, you would call $hobby->getGenders() (and $hobby->getAges() of course) only once, most likely in the render() function before passing it to your template. That will populate $hobby->itemStats as an array with all the actual values you need for that particular hobby. In your Blade template, you would then reference {{ $hobby->itemStats['male_percent'] }}, for example. Of course, if you prefer, you can also choose to make it an object with properties instead of an array with keys; then you’d do {{ $hobby->itemStats->male_percent }} instead in your Blade template.

If you do it this way, you don’t need getGenders() and getAges() to return anything, because it saves all the data as a property in the object itself.

If you want to do without storing an $itemStats property in each Hobby object, you’d instead create the stats in each of the two functions and then return that at the end (here as an object, but you can just as well use an array still):

trait Demographicable {
	public function getAges() {
		$ageStats = [make the array here];
		return (object) $ageStats;
	}

	// and similarly with getGenders()
}

In your Blade template, you would then reference $hobby->getAges()->male_percent in your Blade template.

The disadvantage of this is that every time you do $hobby->getAges()->xyz in your template, it would run the function all over again and use (a few) CPU cycles making the same calculations it’s already made once, which is wasteful and inefficient. So I’d recommend keeping the $itemStats property.

kokoshneta's avatar

I tried this in the query

$hobbies = Hobby::with('equipment', 'categories', 'hobbyAltNames', 'hobbyRatings', 'hobbyType')
           ->withCount('users', 'childHobby', 'hobbyGroups', 'hobbyArticles', 'hobbyEvents', 'hobbyCourses', >'hobbyForumPosts')
           ->getAges()->getGenders()

but if that's expecting variables, what do i put?

The database-related methods in model classes, like with() and where() don’t make your actual hobby objects – they all return a query builder object, which is the object used to create the SQL query that gets sent to the database. In order to actually send that query and retrieve the object, you have to use one of the methods that do just that: get(), find(), all(), etc.

So in this code, you’re trying to run your getAges() and getGenders() functions on a database query builder, and that won’t work.

If you do get() on your query, you’ll get a collection of Hobby objects. You can then run the getXyz() methods on each object in turn:

Hobby::with('lots', 'of', 'stuff')
	->withCount('more', 'stuff')
	->get()
	->each(function($hobby) {
		$hobby->getAges();
		$hobby->getGenders();
	});

That should work, I think.

jcc5018's avatar

@kokoshneta ok, calling once sounds like a good solution.

problem now is in render i am not using hobby as one object, but data as the collection of hobbies. Does this still work?

does my method still require (COLLECTION $data?)

 public function getAges (Collection $data)
    {
        foreach ($this->ages as $ageKey => [$min, $max]) {
            $ageCount = $data->users->whereBetween('age', [$min, $max])->count();
        
            $this->itemStats[$ageKey . '_percent'] = round(($ageCount / $this->userCount) * 100);
        }
    }

and how is $this->userCount being determined?

kokoshneta's avatar

@jcc5018 The snippet I just added (about 20 seconds before you added your answer) addresses how you’d do it in the render() call.

You won’t need the $data parameter in the method signature anymore, no.

Remember that $this in the getAges() function is now the Hobby object, so $this->userCount refers to $hobby->userCount, which you should be getting from the ->withCount() call in your render() call.

jcc5018's avatar

@kokoshneta OK I think I am making progress...

I changed $data to $this

render call function looks like this:

  $data     = $this->hobbies->each(function ($user) {
            $user->getAges();
            $user->getGenders();
        });

$data was achieved after the pagination() tag btw so that should provide the collection.

Methods in Trait were fixed to this:

 public function getAges ()
    {
        foreach ($this->ages as $ageKey => [$min, $max]) {
            $ageCount = $this->users()->whereBetween('age', [$min, $max])->count();
    
            $this->itemStats[$ageKey . '_percent'] = round(($ageCount / $this->userCount) * 100);
        }
    }

Current issue is it is not converting birthdays to age, so ill have to figure out where that issue is, as I'm fairly sure i have that as an attribute somewhere.

Column not found: 1054 Unknown column 'age' in 'where clause' (SQL: select count(*) as aggregate from users inner join hobby_user on users.id = hobby_user.user_id where hobby_user.hobby_id = 652 and age between 0 and 17 and users.deleted_at is null) (View: G:\xampp\htdocs\inah\resources\views\hobby\admin\hobby_manager.blade.php)

but if i removed that getAge() tag, i get a division by 0 error on the genders

Every hobby in my test DB has at least 4 likes, so userCount should be at least 4. So i still need to work on that i guess.

kokoshneta's avatar

Current issue is it is not converting birthdays to age, so ill have to figure out where that issue is, as I'm fairly sure i have that as an attribute somewhere.

Note the difference between $this->users->whereBetween(…) and $this->users()->whereBetween(…). Those are two different calls!

When you have Eloquent ‘smart’ functions like users() in your models, the method call (with parentheses) will return an Eloquent query builder (the one that creates an SQL query to send to the database). To get the actual collection of objects, you need to reference the object property (without parentheses).

The confusing thing here is Laravel collections and Laravel query builders both have a function called whereBetween().

Since you added the parentheses here in $ageCount = $this->users()->whereBetween(…), it’s returning a database query object, so it’s trying to add AND [age] BETWEEN ? AND ? to the actual SQL query. This of course fails since your database table doesn’t have a column named age.

If you just remove the brackets and do $ageCount = $this->users->whereBetween(…), it will instead return the collection of users and use that collection’s whereBetween() method to filter the collection itself.

jcc5018's avatar

@kokoshneta

First thanks for taking the time to help by the way, it is much appreciated. I feel like im just about there.

I removed the brackets, but still not getting results, and division by 0 errors even when i manually set the $userCounts to 1

So my trait is not pulling the proper userCounts for this calculation. Though i do see it listed in the attributes after a dd() as "users_count" => 4

but future down the list i see the properties designated by my trait all at their default values, so the calculation still isn't being done.

I tried mounting usercount as

 public function mount ()
    {
        $this->userCounts = $this->users_count;
    // $this->userCounts = $this->withCount('users');
      //  $this->userCounts = $this->loadCount('users');
    }

In case I changed something in my testing here are my methods again. Not when i remove the () from users in phpStorm it took the users part from green to purple. Not sure what purple indicates.

 public function getAges ()
    {
        foreach ($this->ages as $ageKey => [$min, $max]) {
            $ageCount = $this->users->whereBetween(
                'age',
                [$min, $max])->count();
            //            $itemStats[$ageKey . '_count']   = $ageCount;
            $this->itemStats[$ageKey . '_percent'] = round(($ageCount / $this->usersCounts) * 100);
        }
    }

    public function getGenders ()
    {

        foreach ($this->genders as $gender) {
            $genderCount = $this->users->where('gender', $gender)->count();
            //            $itemStats[$gender . '_count']   = $genderCount;
            $this->itemStats[$gender . '_percent'] = round(($genderCount / $this->usersCounts) * 100);
        }

    }
kokoshneta's avatar

@jcc5018 Now it says $this->usersCounts, which doesn’t look right. If it’s called user_count when you get it from the database, why not just keep using that? Is there a particular reason you change it to userCount(s?) in the model?

jcc5018's avatar

@kokoshneta heck i dont know... im tired going brain dead.

lol

I tried all combinations of things, tried to revert back to something that i thought was right.

either way i have it doesn't work. still division by 0. Do i need to mount this or predefine this or no?

AHH. Nevermind, guess sometimes it helps if i fix both things. So no mounting needed.

dd for the ages is actually producing a result, genders still showing as 0 and an error of

Method Illuminate\Database\Eloquent\Collection::total does not exist.

So something going on with my equation or variables i guess.

in my db, they are listed as m,f, o so i changed the array to match that and actually fixed the item states record for gender counts, so but still have this total being called from somewhere... gotta find that then I think we are in business. Thank you so much for your assistance.

jcc5018's avatar

alright so having it set up like this:

            ->each(function ($user) {
                $user->getAges();
                $user->getGenders();
            })

is breaking anything that was using $data->total() or $data->links() for pagination. So should that each be included elsewhere?

$data comes from this

  public function getHobbiesProperty ()
    {
        return $this->hobbiesQuery->paginate($this->paginate);

    }````
jcc5018's avatar

@kokoshneta YEP, posted something right before this as well.

{{$data->total()}} Results

If i remove this, $data->links() doesnt exsit, and so on.

kokoshneta's avatar
Level 27

@jcc5018 Ah, you’ve got a paginator – that throws a spanner in the works, since you’re not actually getting back a collection when fetching from the database, but a Paginator object instead.

I haven’t used pagination in Laravel, so I don’t know exactly how to deal with them, but the API reference indicates that Paginators have an items() method that contains all the elements on the current page, so you should be able to do this:

$this->hobbies->items()->each(function ($hobby) {
	$hobby->getAges();
	$hobby->getGenders();
});

$data = $this->hobbies;

Edit: Misread the docs, items is a method, not a property. According to the API reference, it returns an array, not a collection, so instead of using ->each() as above, you may have to do this:

foreach ($this->hobbies->items() as $hobby) {
	$hobby->getAges();
	$hobby->getGenders();
}

$data = $this->hobbies;
jcc5018's avatar

@kokoshneta Always something causing other things to not work as expected.

Once I got all my spellings and such right and changed my blade file to

<td>{{$hobby->itemStats['m_percent']}}</td>
                <td>{{$hobby->itemStats['f_percent']}}</td>
                <td>{{$hobby->itemStats['o_percent']}}</td>

                <td>{{$hobby->itemStats['minors_percent']}}</td>
...

I finally got the expected data.

Hope I can use this knowledge for the next problem of similar nature.

Thanks so much for your assistance. Off to the next headache!

Now Ive gotta figure out which answer to mark as best

1 like

Please or to participate in this conversation.