Symfony2, PHP decrease query weight when counting many objects from database - php

So I have 4 tables in my database. Product, Category, ProductCategory and ProductSubcategory.
These tables are in a ManyToMany relationship.
All products are saved in table: Product.
All Categories and Subcategories are saved in one table: Category.
A Product can have a Subcategory without the Category, can have both, or can have a Category without any Subcategory.
ProductCategory and ProductSubcategory is holding all the relationships.(product_id and category_id/subcategory_id)
Now in my twig I want to count how many products does the category and subcategory hold. For that I am using Twig Extensions.
For Subcategory:
Counting how many products are in a subcategory is easy and it works.
In Twig:
{% for subcategory in categories.children %}
[{{ getProductsBySubcategory(subcategory.id) }}]
{% endfor %}
The function:
public function getProductsBySubcategory($id)
{
$products = 0;
$subcategories = $this->doctrine->getRepository('MpShopBundle:ProductSubcategory')->findBy(array('subcategory' => $id));
foreach ($subcategories as $subcategory) {
$products++;
}
return $products;
}
For Category:
For category its a little bit different. Here I want to count all the products in the Category and all the products in Subcategories that this category has. First I check if the category has products and if it has I add the to the array. Then I check if this category has subcategories. If it does i get all the products from the subcategories and add them in to the same array. Now if the product is in both the category and subcategory i dont want to double count. Thats why I am storing everything in an array(to do array_unique later).
My Twig:
[{{ getProductsByCategory(categories.id)|length }}]
The function:
public function getProductsByCategory($id)
{
$category = $this->doctrine->getRepository('ApplicationSonataClassificationBundle:Category')->find($id);
$productCategories = $this->doctrine->getRepository('MpShopBundle:ProductCategory')->findBy(array('category' => $id ));
$productSubcategories = $this->doctrine->getRepository('MpShopBundle:ProductSubcategory')->findAll();
$products = array();
foreach($productCategories as $productCategory) {
array_push($products, $productCategory->getProduct()->getId());
}
foreach($productSubcategories as $productSubcategory) {
$subcategory = $productSubcategory->getSubcategory();
if($subcategory->getparent() == $category) {
array_push($products, $productSubcategory->getProduct()->getId());
}
}
$result = array_unique($products);
return $result;
}
THE PROBLEM:
Everything works in local. I am getting the correct number. However when I switch to Live I get error:500. I am thinking that this is probably because Live has thousands of products and allot of categories. So the foreach loops are breaking the server. Or maybe its because i am saving everything in array?
What should I do? Is it the server problem and I need to increase something? Or maybe there is a way to count without using arrays and decreasing the loop count? But how to check for duplicates then?
The logs show this:
Allowed memory size of 536870912 bytes exhausted (tried to allocate 77 bytes) in /var/www/kd_cms/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php on line 40

Dql is faster than foreach with large db objects
// .../ProductCatRepository.php
public function findDistinctProductsByCat($catId)
{
$query = $this->createQueryBuilder('entity')
->leftjoin('entity.category', 'cat')
->andWhere('cat.id = :cat')
->setParameter('cat', $catId)
;
return $query->getQuery()->getResult();
}
// .../ProductSubCatRepository.php
public function findDistinctProductsByCatSubCat($catId)
{
$query = $this->createQueryBuilder('entity')
->leftjoin('entity.subcategory', 'subcat')
// maybe missing a leftJoin with cat idk your mapping
// ->leftJoin('subcat.category', 'cat')
// ->andWhere('cat.parent = :cat')
->andWhere('subcat.parent = :cat')
->setParameter('cat', $catId)
;
return $query->getQuery()->getResult();
}
public function getProductsByCategory($id)
{
$categoryId = $id;
$productsCat = $this->doctrine->getRepository('MpShopBundle:ProductCat')->findDistinctProductsByCat($categoryId);
$productsSubCat = $this->doctrine->getRepository('MpShopBundle:ProductSubCat')->findDistinctProductsByCatSubCat($categoryId);
// ArrayCollection::count()
return array('count_cat' => $productsCat->count(), 'product_subcat' => $productsSubCat->count(), 'result' => array_unique(array_merge($productsSubCat, $productsCat)));
}

Related

get all category names of articles in index function many to many relationship

I'm trying to get only the category names, of all articles on the index() function on Laravel. So all articles and the related categories of each article. It's a many-to-many relationship.
What i did so far:
foreach($articles as $article) {
if (isset($article)) {
$article['categories'] = $article->category;
}
}
This gets me all articles with the related categories as a collection, it works, but not quite what I want. As I said, I want only the category names.
I tried $article->category->name; obviously did not work
also: $article->category->get('name');
furthermore what is interesting, If I rename $article['categories'] to $article['cat'] I get 2 collections the category with all related category objects and another exact same on just names cat.
I also tried:
...
$article['categories'] = $this->getcat($articles);
return response()->json($articles, 200);
}
public function getcat($query) {
if (isset($query) && is_countable($query)) {
foreach ($query as &$entity) {
if (!isset($entity)) { continue; }
if (isset($entity->categories)) {
$originObjects = $entity->categories;
$competition_all = [];
foreach($originObjects as $object) {
array_push($competition_all, $object->name);
}
return $competition_all;
}
}
}
The function returns what I need, but it does not return it as json, on the frontend.
From what i see.. your response is return response()->json($names, 200); , but your categories names array is saved into the $article['categories'] array. So.. if you return the response()->json($article, 200);, your categories names should be returned as json. The reason that $article->category->name; does not works is because this is array with objects because the type of your relation, but here you call only property.
The better way is to add to the query, the with function and pass the relation as first parameter, and then in second to add anonym function with the query and select only the names.
Eg.
$articles = Article::with('category', function($q) { ... })->get();

Get unique relation rows with distinct in Laravel

Let's say I have Category model with hasMany relation to Product model. Product has color column.
I want to return all categories with all colors that exists in this category, so I tried:
return Category::with(['products' => function ($query) {
$query->distinct('color');
}])->get();
Thanks to this I could later foreach or pluck category->products to get unique colors list. I know I can just get all products in every category, and then filter unique colors, but by doing this I would have to query for example 1000 products per category, instead just 5, which is unnecessary resource heavy. That's why I'm trying to do this on SQL level not PHP.
But this code does not work. There are no errors, it just still returns all products with duplicated colors. Why?
Edit:
Not sure why but my code works if I add select() with used columns before discrinct, and then distinct is making unique rows by all choosed columns. No "color" param required in distinct. Not sure why it works that way, need to dive deeper into SQL docs.
Have you tried this code? Somehow, this will reduce your unnecessary query.
$categories = Category::with([
'products'=> fn($q) => $q->select(['category_id', 'color'])->distinct('color')
])
->select('id') // select required column in categories table
->whereHas('products')
->get();
$colors = $categories->map(function($category) {
return $category->products->pluck('color');
})->toArray();
$color = [];
for ($i=0; $i < count($colors); $i++) {
$color = array_merge($color, $colors[$i]);
}
$uniqueColor = array_unique($color);
return $categories;

Codeigniter add multiple category to a course (Academy-LMS)

I am using Academy LMS Source code is in Github
it's a Learning Management system and it is designed to have one category per course
I want to add multiple categories to one course
In /controllers/API.php I have
// Fetch all the categories
public function categories_get($category_id = "") {
$categories = array();
$categories = $this->api_model->categories_get($category_id);
$this->set_response($categories, REST_Controller::HTTP_OK);
}
// Fetch all the courses belong to a certain category
public function category_wise_course_get() {
$category_id = $_GET['category_id'];
$courses = $this->api_model->category_wise_course_get($category_id);
$this->set_response($courses, REST_Controller::HTTP_OK);
}
then in /models/Api_model.php, I have
// Get categories
public function categories_get($category_id)
{
if ($category_id != "") {
$this->db->where('id', $category_id);
}
$this->db->where('parent', 0);
$categories = $this->db->get('category')->result_array();
foreach ($categories as $key => $category) {
$categories[$key]['thumbnail'] = $this->get_image('category_thumbnail', $category['thumbnail']);
$categories[$key]['number_of_courses'] = $this->crud_model->get_category_wise_courses($category['id'])->num_rows();
}
return $categories;
}
// Get category wise courses
public function category_wise_course_get($category_id)
{
$category_details = $this->crud_model->get_category_details_by_id($category_id)->row_array();
if ($category_details['parent'] > 0) {
$this->db->where('sub_category_id', $category_id);
} else {
$this->db->where('category_id', $category_id);
}
$this->db->where('status', 'active');
$courses = $this->db->get('course')->result_array();
// This block of codes return the required data of courses
$result = array();
$result = $this->course_data($courses);
return $result;
}
then in /model/Crud_model.php, I got
public function get_category_details_by_id($id)
{
return $this->db->get_where('category', array('id' => $id));
}
function get_category_wise_courses($category_id = "")
{
$category_details = $this->get_category_details_by_id($category_id)->row_array();
if ($category_details['parent'] > 0) {
$this->db->where('sub_category_id', $category_id);
} else {
$this->db->where('category_id', $category_id);
}
$this->db->where('status', 'active');
return $this->db->get('course');
}
in SQL course table has a column named category_id int(11) which stores one category per course I've changed it to TEXT format and put comma-separated values like 1,2,3 and used the ways like
$this->db->where_in('category_id',$category_id)
and
$this->db->like('category_id',$category_id)
and
$this->db->where("FIND_IN_SET(".$category_id.",category_id) >", 0);
and got no result I just need courses to have comma-separated values in the category_id column
Simply, I want courses to have multiple categories
DB tables
https://pasteboard.co/JNSxO2kz.png
course table
https://pasteboard.co/JNSy7g0.png
category table
https://pasteboard.co/JNSyhlk.png
category_id table (Mapping Table suggested by #micheal-la-ferla )
https://pasteboard.co/JNSyGGn.png
anybody could help?
The way Academy-LMS is designed, it lets you only add 1 category per course. It limits the user and it can be frustrating.
One workaround which could work is to create a new mapping table with 3 columns as follows:
Field Name | Data Type
------------+------------------
ID | int(11)
Course ID | int(11)
Category ID | int(11)
Obviously you need to have the permission to create new tables to use this workaround. Not sure if this is possible for your implementation of Academy-LMS.
The step above will essentially create a one to many relationship between the course and the category, so that 1 course can then be part of multiple categories. (In reality, this would be a many to many relationship since I am assuming that a category may obviously belong to multiple courses).
Therefore the database design will be similar to the one I created here.
If you implement this change, you would then need to make the following changes to the code you have:
// Get categories
public function categories_get($category_id)
{
if ($category_id != "") {
$this->db->where('category_id', $category_id);
}
$this->db->where('parent', 0);
$categories = $this->db->get('course_id')->result_array();
foreach ($categories as $key => $category) {
$categories[$key]['thumbnail'] = $this->get_image('category_thumbnail', $category['thumbnail']);
$categories[$key]['number_of_courses'] = $this->crud_model->get_category_wise_courses($category['id'])->num_rows();
}
return $categories;
}
Essentially, what I did here was replace the parent in the categories with category_id and category with course_id. Not sure if these are correct. You will need to review these when reading from the database as I never used CodeIgniter.

How to get two arrays of obiects by one column

Hi i have on table with categories with foreign key to herself where parent_id is the same that id in this table. I want to get two arrays of objects. First with categories where
parent_id=0
and second with subcategories. But I dont know how can I catch this subcategories. I have this:
$category= Category::where('parent_id', '=', 0)->get();
dd($category[0]['id']);
$subcategory= Category::where('parent_id', '=', (($category[0]['id']??)));
First $category shouild return me array of categories and second array with subcategories i need adjust id of each object of array $category to each subcategory array. Is it possible or there are other ways?
If you define your model relations correctly you can get categories and their subcategories in a much nicer way.
First define the relations:
class Category extends Model {
public function parent() {
return $this->belongsTo(Category::class);
}
public function subcategories() {
} return $this->hasMany(Category::class, 'parent_id');
}
You can now get all parent categories with their subcategories the following way:
$parents = Category::whereParentId(0)->with('subcategories')->get();
This will give you list of all parent categories, each of them will have subcategories property that will store all subcategories. You can traverse them in the following way:
foreach ($parents as $parent) {
printf("Parent category %s\n", $parent->name);
foreach ($parent->subcategories as $subcategory) {
printf("Subcategory %s\n", $subcategory->name);
}
}
Small suggestion: make your parent_id nullable and store NULL for parent categories instead of 0, as 0 is not a correct category ID.

How To Get Sub Categories in Magento ?`

I am playing with magento's home page where I have created a tab Which Shows all the categories including Root, categories, and Sub Categories (In One Tab). Now I want to Show Only Main Categories( Whose Parent Is Root) in main Tab And under Each Category I want to List Their respective Sub Categories. I wrote The following Code to achieve a part of this,
MODEL CLASS
public function getsubCategory($parent)
{
$subcategoryCollection = Mage::getModel('catalog/category')
->getCollection()
->addAttributeToFilter('parent_id', $parent);
return $subcategoryCollection;
BLOCK CLASS
protected function b4Html_subcategory($parent)
{
$catModel = Mage::getModel('Pragtech_Sweet/category');
$mysubCategory = $catModel->getsubCategory($parent);
$this->mysubCategory = $myCategory;
return $mysubCategory;
}
TEMPLATE FILE
$obj = new Pragtech_Sweet_Block_Category();
$collection = $obj->b4Html();
foreach ($collection as $category)
{
$name = $category->getName();
$parent = $category->getParent_id();
foreach ($obj->b4Html_subcategory($parent) as $subcategory)
{
$subname = $subcategory->getName();
//Here Will Go Ther Code For Sub Categories
}
but it doesn't work.. I am unable to understand where I am doing wrong... Can anyone help me out
Do this instead :
Mage::getModel('catalog/category')->load('23')->getChildrenCategories();
and iterate over the result.
and that's how i found it out:
$object = Mage::getModel('catalog/category');
print_r(get_class_methods($object));
print_r($object->load('23')->getChildrenCategories()->toArray());
Here's another way to do this, if you don't want to mess with the treeModel stuff, or want more control how categories are loaded:
function getCategoryTree($root_category_name)
{
$categories = Mage::getModel('catalog/category')->getCollection()
->addAttributeToSelect("*")
->addFieldToFilter("Name",array("eq"=>$root_category_name));
$stack = array();
$category = $categories->getFirstItem()->getData();
$categories=array();
array_push($stack, $category);
//regular recursion is boring, let's do a stack
while(count($stack) > 0)
{
$category = array_pop($stack);
array_push($categories, $category);
$children = Mage::getModel('catalog/category')->getCollection()
->addAttributeToSelect("*")
->addFieldToFilter("parent_id",array("eq"=>$category['entity_id']))
->addAttributeToSort("position","desc");
foreach ($children as $child)
{
array_push($stack, $child->getData());
}
}
return $categories;
}
This will give you an array of categories representing the full category tree rooted at the top category with the name "Some Category Name", in order, with all of the category data. Tune this to only select the specific fields you need, instead of "*".
This technique can give you finer grained control of the loading of the category tree, and with how you want the data structured afterwards. For example, you could trivially modify this to do a hierarchical tree instead of a flat array and then serialize that to a JSON object to send to the client.
Add whatever filters you like (active, etc...) at whatever level you like to get even more control.
iterate through this object
Mage::getModel('catalog/category')
->getCollection()
->addAttributeToSelect('*')
->getItems();

Categories