Loading corresponding entities while loading a collection - php

Currenty I'm working on a webshop using an auction module. This auction module has its own set of entities representing an auction and their bids. The main entity is the ProductAuction model. This model has an relation to the Catalog_Product model of Magento.
Everywhere the collection is loaded the products have to be loaded after the collection of ProductAuctions has been loaded.
Now I have to write some exotic queries to load a specific sets of auctions in combination with category and search queries. Now I first have to load a collection of products belonging to the given category and search query, then load the active auctions belonging to the set of corresponding products.
In some scenarios I can't reuse the set of loaded products and then have to execute another query to load the products corresponding the auctions.
Worst case I'll have to execute three big queries and process the resultsets which should be possible in one query.
Is it possible in Magento to load relating entities within a collection, just like any decent ORM would do with One-2-One, Many-2-One and other relations?
I haven't found any example of this, but I can't imagine this isn't possible in Magento.
Thanks for any help on this.
== EDIT ==
A bit of example code to show what I'm doing at the moment:
/**
* Build the correct query to load the product collection for
* either the search result page or the catalog overview page
*/
private function buildProductCollectionQuery() {
/**
* #var Mage_CatalogSearch_Model_Query
*/
$searchQuery = Mage::helper('catalogsearch')->getQuery();
$store_id = Mage::app()->getStore()->getId();
$productIds = Mage::helper('auction')->getProductAuctionIds($store_id);
$IDs = array();
$productIds = array();
$collection = Mage::getModel('auction/productauction')
->getCollection()
->addFieldToFilter(
'status', array('in' => array(4))
);
if (count($collection)) {
foreach ($collection as $item) {
$IDs[] = $item->getProductId();
}
}
$collection = Mage::getResourceModel('catalog/product_collection')
->addFieldToFilter('entity_id',array('in'=> $IDs ))
->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes())
->addMinimalPrice()
->addTaxPercents()
->addStoreFilter();
if( $searchQuery != null ) {
$collection->addAttributeToFilter(array(
array('attribute' => 'name', 'like'=>'%' . $searchQuery->getQueryText() . '%'),
array('attribute' => 'description', 'like'=>'%' . $searchQuery->getQueryText() . '%')
)
);
// #TODO This should be done via the Request object, but the object doesn't see the cat parameter
if( isset($_GET['cat']) ) {
$collection->addCategoryFilter(Mage::getModel('catalog/category')->load($_GET['cat']) );
}
}
return $collection;
}

Managed to come up with a solution. I now use the following code to get all the information I need. Still I'm surprised it is so hard to create instances of relating objects like any normal ORM would do. But perhaps I am expecting too much of Magento..
Anyway, this is the code I use now:
/**
* Build the correct query to load the product collection for
* either the search result page or the catalog overview page
*/
private function buildProductCollectionQuery() {
/**
* #var Mage_CatalogSearch_Model_Query
*/
$searchQuery = Mage::helper('catalogsearch')->getQuery();
$collection = Mage::getResourceModel('catalog/product_collection')
->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes())
->addAttributeToSelect(array('product_id' => 'entity_id'))
->joinTable(array('productauction' => 'auction/productauction'), 'product_id=entity_id', array('start_time' => 'productauction.start_time','end_time' => 'productauction.end_time','entity_id' => 'productauction.productauction_id', 'product_id' => 'product_id'))
->joinTable(array('auction' => 'auction/auction'), 'product_id = entity_id', array('last_bid' => 'MAX(auction.price)'), 'auction.productauction_id = productauction.productauction_id', 'inner')
->addMinimalPrice()
->addTaxPercents()
->addStoreFilter()
->setPage((int) $this->getRequest()->getParam('p', 1), 1);
$currentCategory = Mage::registry('current_category');
if( $currentCategory != null ) {
$collection->addCategoryFilter($currentCategory);
}
if( $searchQuery != null ) {
$collection->addAttributeToFilter(array(
array('attribute' => 'name', 'like'=>'%' . $searchQuery->getQueryText() . '%'),
array('attribute' => 'description', 'like'=>'%' . $searchQuery->getQueryText() . '%')
)
);
// #TODO This should be done via the Request object, but the object doesn't see the cat parameter
if( isset($_GET['cat']) ) {
$collection->addCategoryFilter(Mage::getModel('catalog/category')->load($_GET['cat']) );
}
}
$collection->getSelect()->group('productauction.productauction_id');
return $collection;
}

Related

My query will not update, but when i refresh in dd it will

When I run the query normally it won't update the status_order from 0 to 1, but when I put a dd() function after the query to check if it will update properly, it will give the same result the first time I run the code, but when I refresh the page it will update to a 1.
Here's how my code usually looks:
public function payment(Request $request){
$total = 0;
$orderInfo = $this->getOrderInfo();
$json2 = array();
foreach($this->getOrderInfo()->products as $product){
$total += ($product->price * $product->pivot->order_quantity);
}
if(Customer::find(Auth::user()->id)->balance >= $total && $orderInfo !== null){
if($orderInfo->order_status !== 1){
$orderInfo->where('customer_id', Auth::user()->id)
->where('order_status', 0)
->update(['order_status' => 1]);
}
foreach($orderInfo->products as $product){
$json = array('order_id' => $product->pivot->order_id,
'product_id' => $product->pivot->product_id,
'product_name' => $product->name,
'price' => $product->price,
'quantity' => $product->pivot->order_quantity);
array_push($json2, $json);
}
Customer::where('id', Auth::user()->id)->decrement('balance', $total);
array_push($json2, array('order_status' => $orderInfo->order_status));
$productInfo = json_encode($json2, JSON_PRETTY_PRINT);
OrderHistory::create([
'customer_id' => Auth::user()->id,
'orderInfo' => $productInfo
]);
$orderInfo->products()
->detach();
$orderInfo->delete();
return back();
}else{
return "Not enough balance";
}
}
}
Here's where I put my dd() function:
if($orderInfo->order_status !== 1){
$orderInfo->where('customer_id', Auth::user()->id)
->where('order_status', 0)
->update(['order_status' => 1]);
dd($orderInfo->where('customer_id', Auth::user()->id)->where('order_status', 0));
}
The if($orderInfo->order_status !== 1) is put in there for me to check if the query would get skipped at all. I have tried to alter the order in which the code is presented, but it didn't make any difference.
this code produce mass update but doesn't affect your $orderInfo model which is loaded when it had order_status == 0
$orderInfo = $this->getOrderInfo();
// ...
$orderInfo->where('customer_id', Auth::user()->id)
->where('order_status', 0)
->update(['order_status' => 1]);
// in database data was updated, but $orderInfo is already in memory, so
// $orderInfo->order_status == 0
in case you want to get immediately impact on $orderInfo try
// if you order can have only one info
$orderInfo->order_status = 1;
$orderInfo->save();
// if order can have multiple info models
$orderInfo->newQuery()->where('customer_id', Auth::user()->id)
->where('order_status', 0)
->update(['order_status' => 1]);
$orderInfo = $orderInfo->fresh();
docs about fresh() method
as a sidenote here you're doing duplicate getOrderInfo() call
$orderInfo = $this->getOrderInfo();
$json2 = array();
//foreach($this->getOrderInfo()->products as $product) {
foreach($orderInfo->products as $product) {
$total += ($product->price * $product->pivot->order_quantity);
}
update to clarify about comment to main post
truth to be told, i'm confused that this code runs at all. i meant
$orderInfo is an object given you checked its property order_status
but then you call where() on it as if it is a collection (or model).
also its not laravel-query-builder but laravel-eloquent or just
eloquent given you have detach() there.. – Bagus Tesa
if we dig into Illuminate\Database\Eloquent\Model class there is 'magic' method __call
/**
* Handle dynamic method calls into the model.
*
* #param string $method
* #param array $parameters
* #return mixed
*/
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
if ($resolver = (static::$relationResolvers[get_class($this)][$method] ?? null)) {
return $resolver($this);
}
// that's why OP code works
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
as you can see if model has no method to call it forwards call to Builder object (result of $this->newQuery()) which is equivalent to ModelName::query()
tbh, i agree that calling eloquent from loaded model is a bit frustrating, but it is 'by design'
In order to do a mass update, what this theoretically is, you need to define all the attributes that you want to mass update in the $fillable array in your Model. (OrderInfo in this case)

Symfony Doctrine How do i find out where/when Lazy Relations are fetched

I am working on an action which uses layered Relations.
So i have a Player Entity, which has a Relation OwnedCard. Which has a Relation CardLevel which has a Relation card.
So i am using
/**
* #param Player $player
* #Route("/{id}/cards", name="loki.tuo.ownedcard.cards.show", requirements={"id":"\d+"})
*
* #ParamConverter("player", class="LokiTuoResultBundle:Player", options={"repository_method" = "findWithOwnedCards"})
* #return Response
* #Security("is_granted('view.player', player)")
*/
public function showCardsForPlayerAction(Player $player)
{
$allCards = $player->getOwnedCards();
$allCards = Collection::make($allCards)->sortBy(function (OwnedCard $elem) {
//$elem->getCard() calls the getName() method on CardLevel which delegates it to Card
return $elem->getCard()->getName();
});
$deck = $allCards->filter(function (OwnedCard $item) {
return $item->getAmountInDeck() > 0;
});
$combined = $deck->map(function (OwnedCard $item) {
return $item->toDeckString();
});
$formOptions = ['attr' => ['class' => 'data-remote']];
$ownedCardForm = $this->createForm(OwnedCardType::class, null, $formOptions);
$massOwnedCardForm = $this->createForm(MassOwnedCardType::class, null, [
'action' => $this->generateUrl('loki.tuo.ownedcard.card.add.mass', ['id' => $player->getId()]),
'method' => 'POST',
]);
//Render Template
For this I created a method which Joins and Selects these Relations
public function findWithOwnedCards($id)
{
$qb = $this->createQueryBuilder('player')
->join('player.ownedCards', 'ownedCards')
->join('ownedCards.card', 'cardLevel')
->join('cardLevel.card', 'card')
->addSelect(['ownedCards'])
->addSelect(['cardLevel'])
->addSelect(['card'])
->where('player.id = :id')
->setParameter('id', $id);
return $qb->getQuery()->getSingleResult();
}
But Unfortunately the Symfony Profiler tells me, there are a lot calls like
SELECT * FROM card_level WHERE card_id = ?
(I shortened the Query for better readability)
So this means, at some point Symfony/Doctrine doesnt use the Joined Relationships but somehow thinks they are lazy loaded, and needs to fetch them.
So now my Question: How can I find out, where or when the queries are executed? Is there some point in the Code where I could set a breakpoint or throw an Exception to see a stacktrace to see where this comes from?
Try setting 'fetch' property to "EAGER" for your associations
Here's an example from doctrine docs
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#manytoone

Get Category image in with getChildrenCategories->getImageUrl (MAGENTO)

I use this $categories through out the page
$categories = Mage::getModel('catalog/category')
->getCollection()
->addAttributeToSelect('*')
->addAttributeToFilter('level',2)
->addIsActiveFilter()
->addAttributeToSort('position');
foreach($categories as $cat) {$children=$cat->getChildrenCategories();}
Option 1
//option 1
$children = $category->getChildren();
foreach(explode(',', $children) as $child):
$sub = Mage::getModel('catalog/category')->load($child);
$sub->getName() // this return ok, name show up
$sub->getImageUrl() // this return ok, image show up
Option 2 works but can't get image url for some reason.
//option 2
$children = $category->getChildrenCategories();
foreach($children as $sub):
$sub->getName() // this return ok, name show up
$sub->getImageUrl() // this return empty, image NOT show up so is other attribute beside name.
can someone explain the difference? and how would i go about it with opt 2
Basically, When we are using getChildrenCategories() function only few fields has been retrieve from category field collection ->url_key,name,all_children,is_anchor.
you can see that on class Mage_Catalog_Model_Resource_Category.So if want tget image url from function then just add addAttributeToFilter('image')
$collection = $category->getCollection();
$collection->addAttributeToSelect('url_key')
->addAttributeToSelect('name')
->addAttributeToSelect('all_children')
->addAttributeToSelect('is_anchor')
->setOrder('position', Varien_Db_Select::SQL_ASC)
->joinUrlRewrite()
->addAttributeToFilter('is_active', 1)
->addIdFilter($category->getChildren())
->addAttributeToSelect('image');
foreach($category as $eachChildCat){
if ($image = $eachChildCat->getImage()) {
$url = Mage::getBaseUrl('media').'catalog/category/'.$image;
}
}
$eachChildCat->getImage() not work then use $eachChildCat->getResource()->getImage()
Instead of calling getModel() try to call getSingleton().
If you want to get image url in second option, you have to load category:
$subcategory = Mage::getModel('catalog/category')->load($sub->getId());
$imageUrl = $subcategory->getImageUrl();
if using magento 2
I tried following so that it does not affect performance and you need to call the category model seprately
Open in root of magento 2.2
/vendor/magento/module-catalog/Model/ResourceModel/Category.php
On line750ish
Add
->addAttributeToSelect(
'image'
)
or replace function
/**
* Return child categories
*
* #param \Magento\Catalog\Model\Category $category
* #return \Magento\Catalog\Model\ResourceModel\Category\Collection
*/
public function getChildrenCategories($category)
{
$collection = $category->getCollection();
/* #var $collection \Magento\Catalog\Model\ResourceModel\Category\Collection */
$collection->addAttributeToSelect(
'url_key'
)->addAttributeToSelect(
'image'
)->addAttributeToSelect(
'name'
)->addAttributeToSelect(
'all_children'
)->addAttributeToSelect(
'is_anchor'
)->addAttributeToFilter(
'is_active',
1
)->addIdFilter(
$category->getChildren()
)->setOrder(
'position',
\Magento\Framework\DB\Select::SQL_ASC
)->joinUrlRewrite();
return $collection;
}

how to add total number of orders to customer grid in magento

i want to show number of orders in customer grid of magento
i used this as a guide:
How to add customer "total number of orders" and "total spent" to order grid in magento 1.7
but this is a different grid
so far i have created:
app/code/local/Mage/Adminhtml/Block/Customer/Grid.php
_prepareCollection
i added:
$orderTableName = Mage::getSingleton('core/resource')
->getTableName('sales/order');
$collection
->getSelect()
->joinLeft(
array('orders' => $orderTableName),
'orders.customer_id=e.entity_id',
array('order_count' => 'COUNT(customer_id)')
);
$collection->groupByAttribute('entity_id');
before:
$this->setCollection($collection);
_prepareColumns i added:
$this->addColumn('order_count', array(
'header' => Mage::helper('customer')->__('# orders'),
'index' => 'order_count',
'type' => 'number'
));
while it does work in the grid, i have some problems:
the pager shows 1 customer (should be 500+)
sorting on this new column doesn't work
Just remove:
$collection->groupByAttribute('entity_id');
And add this:
$collection->group('e.entity_id');
Overview we have:
$orderTableName = Mage::getSingleton('core/resource')
->getTableName('sales/order');
$collection
->getSelect()
->joinLeft(
array('orders' => $orderTableName),
'orders.customer_id=e.entity_id',
array('order_count' => 'COUNT(customer_id)')
);
$collection->group('e.entity_id');
OR
$orderTableName = Mage::getSingleton('core/resource')
->getTableName('sales/order');
$collection
->getSelect()
->joinLeft(
array('orders' => $orderTableName),
'orders.customer_id=e.entity_id',
array('order_count' => 'COUNT(customer_id)')
)
->group('e.entity_id');
You have a GROUP BY clause in your collection, and the grid pager uses $collection->getSize() to determine the number of pages. The problem is that getSize() applies a SELECT COUNT(*) to the collection, and fetches the first column of the first row to get the number of results. With the GROUP BY still applied, the pager then considers that there is only one result.
To prevent this problem, you should either use your own customers collection with a relevant getSize(), or use sub-queries to retrieve the totals you need.
It's working fine over there. just follow the following steps .
add code in the following file
app\code\core\Mage\Adminhtml\Block\Customer\Grid.php
add this code in _prepareCollection() fucntion only
$sql ='SELECT COUNT(*)'
. ' FROM ' . Mage::getSingleton('core/resource')->getTableName('sales/order') . ' AS o'
. ' WHERE o.customer_id = e.entity_id ';
$expr = new Zend_Db_Expr('(' . $sql . ')');
$collection->getSelect()->from(null, array('orders_count'=>$expr));
and also add this code in _prepareColumns() function with same file
$this->addColumn('orders_count', array(
'header' => Mage::helper('customer')->__('Total Orders'),
'align' => 'left',
'width' => '40px',
'index' => 'orders_count',
'type' => 'number',
'sortable' => true,
));
We can create a column of total orders in customer grid table and display in numbers .
Vendor/Module/view/adminhtml/ui_component/customerlisting.xml
<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<columns name="customer_columns" class="Magento\Customer\Ui\Component\Listing\Columns" >
<column name="total_orders" class="Vendor\Module\Ui\Component\Listing\Column\TotalOrders" sortOrder="90">
<settings>
<dataType>text</dataType>
<label translate="true">Total Orders</label>
<sortable>false</sortable>
<filter>false</filter>
</settings>
</column>
</columns>
</listing>
And then create a Ui component to fetch orderdata.
Vendor/Module/Ui/Component/Listing/Column/TotalOrders.php
<?php
namespace Vendor\Module\Ui\Component\Listing\Column;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;
class TotalOrders extends Column
{
protected $orderCollectionFactory;
/**
*
* #param ContextInterface $context
* #param UiComponentFactory $uiComponentFactory
* #param array $components
* #param array $data
*/
public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
array $components = [],
array $data = [],
\Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory
) {
$this->orderCollectionFactory = $orderCollectionFactory;
parent::__construct($context, $uiComponentFactory, $components, $data);
}
/**
* Prepare Data Source
*
* #param array $dataSource
* #return array
*/
public function prepareDataSource(array $dataSource)
{
if (isset($dataSource['data']['items'])) {
foreach ($dataSource['data']['items'] as & $item) {
$customerOrder = $this->orderCollectionFactory->create()->addFieldToFilter('customer_id', $item['entity_id']);
$item[$this->getData('name')] = count($customerOrder);//Value which you want to display**strong text**
}
}
return $dataSource;
}
}

kohana 3.2 ORM find_all() with relationships

I have 3 tables:
artists{id,name}
media{id,name,filename}
media_artists{artist_id,media_id}
I created the models with n-n relationships as described in the Kohana guide.
When I do in a controller:
$artist_view = new View('artists/profile');
$artist_id = $this->request->param('id');
$artist_view->artists = $artist_model->where('id', '=', $artist_id)->find();
$artist_view->media = $artist_model->media->find_all();
it works fine, and I can call the media entries related to this specific artist in my view.
Now I want to do a query where I get all the artists, with their related media, all in the same sql result, but I don't find the syntax.
This:
$artist_view->artists = $artist_model->order_by('name' , 'ASC')->find_all();
$artist_view->media = $artist_model->media->find_all();
doesn't work (doesn't throw an error but $artist_view->media is empty)
I've seen on some forums that something like this might work:
$artist_model->order_by('name' , 'ASC')->with('media')->find_all();
but it doesn't work for me.
In my view, at the end, I want to be able to do something like this:
foreach($artists as $a){
echo $a->name."<br />";
foreach($a->media as $m) echo $m->name."<br />";
echo "<br />";
}
If you have the relationhsip in both models defined as follows:
// In artist model.
protected $_has_many = array(
'media' => array('through' => 'media_artists'),
);
// In media model.
protected $_has_many = array(
'artists' => array('through' => 'media_artists'),
);
It should work using (EDIT!):
$artists = ORM::factory('artist')->find_all();
foreach ( $artists as $artist )
{
foreach ( $artist->media->find_all() as $m )
{
echo $m->name;
}
}
I had the same problem in one of my projects and this solution works for me (Kohana 3.2.0).

Categories