Get product's category ids and names - magento

There is this method getCategoryIds() in Mage_Catalog_Model_Resource_Eav_Mysql4_Product. This method returns all category IDs of requested product. I need to modify the SELECT statement so it will return also category names.
Here is the basic query:
$select = $this->_getReadAdapter()->select()
->from($this->_productCategoryTable, 'category_id')
->where('product_id=?', $product->getId());
I can't use catalog_category_flat table to for some reasons, so I have to use EAV tables. So at this point I have this query:
$select = $this->_getReadAdapter()->select()
->from($this->_productCategoryTable, 'category_id')
->where('catalog_category_product.product_id=?', $product->getId())
->join(
array('a' =>'catalog_category_entity_varchar'),
'a.entity_id = catalog_category_product.category_id',
array('name' => 'value')
)
->join(
array('b' => $this->getTable('eav/attribute')),
'b.attribute_id = a.attribute_id',
array()
)
->where("b.attribut_code = 'name'");
This works, but I'd like to ask if there is a better way of doing it.

Easiest and probably cleanest:
$categories = $product->getCategoryCollection()
->addAttributeToSelect('name');
Then you can simply traverse the collection:
foreach($categories as $category) {
var_dump($category->getName());
}

I found this post: Joining an EAV table and made the function, which will add the category join query, use it from the collection.
Just call this function from the code this way:
// To join the name of category
$this->joinCategoryAttribute('<your table alias>.category_id', 'name');
And this is the function:
public function joinCategoryAttribute($joinOriginId, $code)
{
$attributeId = Mage::getResourceModel('eav/entity_attribute')->getIdByCode('catalog_category', $code);
$entityType = Mage::getModel('eav/entity_type')->loadByCode('catalog_category');
$attribute = Mage::getModel($entityType->getAttributeModel())->load($attributeId);
$entityTable = $this->getTable($entityType->getEntityTable());
$alias = 'table' . $code;
$table = $entityTable . '_' . $attribute->getBackendType();
$field = $alias . '.value';
$this->getSelect()
->joinLeft(array($alias => $table),
"{$joinOriginId} = {$alias}.entity_id AND {$alias}.attribute_id = " . $attribute->getAttributeId(),
array($attribute->getAttributeCode() => $field)
);
return $this;
}

Related

Laravel order by not working on related columns on datatable

Basically, my backend is:
$columns = array(
0 => 'purchased_date',
1 => 'supplier',
2 => 'purchased_code',
3 => 'grand_total',
4 => 'paid_amount',
5 => 'overall_due_amount',
6 => 'payment_status',
7 => 'action',
);
$limit = $request->input('length');
$start = $request->input('start');
$order = $columns[$request->input('order.0.column')];
$dir = $request->input('order.0.dir');
$query = Purchase::with(['supplier:id,name','purchasePayments']);
$purchased_date = $request->columns[0]['search']['value'];
if (!empty($purchased_date)) {
$query->where('purchased_date', 'like', '%' . $purchased_date . '%');
}
$supplier_search = $request->columns[1]['search']['value'];
if (!empty($supplier_search)) {
$query->whereHas('supplier', function($q) use($supplier_search){
$q->where('name', 'like', '%' . $supplier_search . '%');
});
}
$purchased_code = $request->columns[2]['search']['value'];
if (!empty($purchased_code)) {
$query->where('purchased_code', 'like', '%' . $purchased_code . '%');
}
$totalData = $query->count();
//HERE HOW COULD I ORDER USING SUPPLIER NAME
//suppliers is the name of the table
//and supplier is the relation defined inside purchase model
$order = 'suppliers.name';
$query->orderBy($order, $dir);
if ($limit != '-1') {
$query->offset($start)->limit($limit);
}
$records = $query->get();
$totalFiltered = $totalData;
$data = array();
if (isset($records)) {
foreach ($records as $k => $v) {
$nestedData['id'] = $v->id;
$nestedData['purchased_date'] = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $v->purchased_date)->format('Y-m-d');
$nestedData['supplier'] = $v->supplier->name;
$nestedData['purchased_code'] = $v->purchased_code;
$nestedData['grand_total'] = $v->grand_total;
$nestedData['paid_amount'] = $v->purchasePayments->sum('paid_amount');
$nestedData['overall_due_amount'] = $v->overall_due_amount;
$nestedData['payment_status'] = PaymentHelper::getPaymentStatusBadge($v->purchasePayments->last()->payment_status);
$nestedData['action'] = \View::make('admin.purchases.action')->with('r', $v)->render();
$data[] = $nestedData;
}
}
Here, how could I order the full results based on the supplier name. Here, suppliers is the name of the table and supplier is the name of the relation defined on the purchase model. Is there any way to deal with this situation where I need to order based on the related model
It's because eager loading isn't building your query as you anticipate.
// Instead of:
$query->orderBy($order, $dir); ❌
// Use this:
$query->join('suppliers', 'purchases.id', '=', 'suppliers.purchase_id')
->orderBy('suppliers.name', $dir) ✅
To avoid ambiguous column names. i.e: id (Between purchases and suppliers tables.), modify:
// Instead of:
$records = $query->get(); ❌
// Use this:
$records = $query->get(["purchases.*"]); ✅
NOTE: Eager loading still works with the above implementation.

Laravel query groubBy condition

I have a dB query where I would like to groupBy() only when conditions are met without using union because of pagination.
Unfortunately groupBy() seems to only work when called on the entire query outside of the loop.
This was made for dynamic filtering from $filterArr. Depending on the array I need to select from different columns of the table.
When the $key=='pattern' I would need the distinct results from its column.
the query looks something like this
select `col_1`, `col_2`, `col_3`
from `mytable`
where (`color` LIKE ? or `pattern` LIKE ? or `style` LIKE ?)
group by `col_2` //<< i need this only for 'pattern' above and not the entire query
Heres the model:
// $filterArr example
// Array ( [color] => grey [pattern] => stripe )
$query = DB::table('mytable');
$query = $query->select(array('col_1', 'col_2', 'col_3'), DB::raw('count(*) as total'));
$query = $query->where(function($query) use ($filterArr){
$ii = 0;
foreach ($filterArr as $key => $value) {
if ($key=='color'){
$column = 'color';
}else if ($key=='style'){
$column = 'style';
}else if ($key=='pattern'){
$column = 'pattern';
$query = $query->groupBy('col_2'); // << !! does not work
}
if($ii==0){
$query = $query->where($column, 'LIKE', '%'.$value.'%');
}
else{
$query = $query->orWhere($column, 'LIKE', '%'.$value.'%');
}
$ii++;
}
});
$query = $query->orderBy('col_2', 'asc')->simplePaginate(30);
I think you can simplify your code a bit:
$query = DB::table('mytable');
$query = $query->select(array('col_1', 'col_2', 'col_3'), DB::raw('count(*) as total'));
$query = $query->where(
collect($filterArr)
->only(['color','style','pattern'])
->map(function ($value, $key) {
return [ $key, 'like', '%'.$value.'%', 'OR' ];
})->all()
)->when(array_key_exists('pattern', $filterArr), function ($query) {
return $query->groupBy('col_2');
});
$query = $query->orderBy('col_2', 'asc')->simplePaginate(30);

Laravel Database Query Builder error with table name

I'm making a "simple" api for laravel. This api has to handle with filters, pagination and sorting the result. To make this I use laravel query builder. The problem is that it's making a select without a table name, for example:
select * order by `id` asc
My code:
public function index()
{
$request = request();
$query = DB::table('customers')->newQuery();
// Orden
if (request()->has('sort')) {
// Multiorden
$sorts = explode(',', request()->sort);
foreach ($sorts as $sort) {
list($sortCol, $sortDir) = explode('|', $sort);
$query = $query->orderBy($sortCol, $sortDir);
}
} else {
$query = $query->orderBy('id', 'asc');
}
//Filtros
if ($request->exists('filter')) {
$query->where(function($q) use($request) {
$value = "%{$request->filter}%";
$q->where('name', 'like', $value)
->orWhere('address', 'like', $value);
});
}
$perPage = request()->has('per_page') ? (int) request()->per_page : null;
$pagination = $query->get()->paginate($perPage);
$pagination->appends([
'sort' => request()->sort,
'filter' => request()->filter,
'per_page' => request()->per_page
]);
return response()->json(
$pagination
);
}
Error:
Illuminate\Database\QueryException: SQLSTATE[HY000]: General error:
1096 No tables used (SQL: select * order by id asc) in file
C:\xampp\htdocs\iService\vendor\laravel\framework\src\Illuminate\Database\Connection.php
on line 664
UPDATE:
return DB::table('customers')->get();
If i use this, the api works fine, I have more apis working. The problem is that I need Query Builder to handle filters, sort, etc...
The problem was the way I instance a new query.
$query = DB::table('customers')->newQuery();
Correct:
$query = Model::query();
For my example:
$query = Customer::query();

Laravel how do I get the row number of an object using Eloquent?

I'd like to know the position of a user based on its creation date. How do I do that using Eloquent?
I'd like to be able to do something like this:
User::getRowNumber($user_obj);
I suppose you want MySQL solution, so you can do this:
DB::statement(DB::raw('set #row:=0'));
User::selectRaw('*, #row:=#row+1 as row')->get();
// returns all users with ordinal 'row'
So you could implement something like this:
public function scopeWithRowNumber($query, $column = 'created_at', $order = 'asc')
{
DB::statement(DB::raw('set #row=0'));
$sub = static::selectRaw('*, #row:=#row+1 as row')
->orderBy($column, $order)->toSql();
$query->remember(1)->from(DB::raw("({$sub}) as sub"));
}
public function getRowNumber($column = 'created_at', $order = 'asc')
{
$order = ($order == 'asc') ? 'asc' : 'desc';
$key = "userRow.{$this->id}.{$column}.{$order}";
if (Cache::get($key)) return Cache::get($key);
$row = $this->withRowNumber($column, $order)
->where($column, '<=',$this->$column)
->whereId($this->id)->pluck('row');
Cache::put($key, $row);
return $row;
}
This needs to select all the rows from the table till the one you are looking for is found, then selects only that particular row number.
It will let you do this:
$user = User::find(15);
$user->getRowNumber(); // as default ordered by created_at ascending
$user->getRowNumber('username'); // check order for another column
$user->getRowNumber('updated_at', 'desc'); // different combination of column and order
// and utilizing the scope:
User::withRowNumber()->take(20)->get(); // returns collection with additional property 'row' for each user
As this scope requires raw statement setting #row to 0 everytime, we use caching for 1 minute to avoid unnecessary queries.
$query = \DB::table(\DB::raw('Products, (SELECT #row := 0) r'));
$query = $query->select(
\DB::raw('#row := #row + 1 AS SrNo'),
'ProductID',
'ProductName',
'Description',
\DB::raw('IFNULL(ProductImage,"") AS ProductImage')
);
// where clauses
if(...){
$query = $query->where('ProductID', ...));
}
// orderby clauses
// ...
// $query = $query->orderBy('..','DESC');
// count clause
$TotalRecordCount = $query->count();
$results = $query
->take(...)
->skip(...)
->get();
I believe you could use Raw Expresssions to achieve this:
$users = DB::table('users')
->select(DB::raw('ROW_NUMBER() OVER(ORDER BY ID DESC) AS Row, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
However, looking trough the source code looks like you could achieve the same when using SQLServer and offset. The sources indicates that if you something like the following:
$users = DB::table('users')->skip(10)->take(5)->get();
The generated SQL query will include the row_number over statement.
[For Postgres]
In your model
public function scopeWithRowNumber($query, $column = 'id', $order = 'asc'){
$sub = static::selectRaw('*, row_number() OVER () as row_number')
->orderBy($column, $order)
->toSql();
$query->from(DB::raw("({$sub}) as sub"));
}
In your controller
$user = User::withRowNumber()->get();

Better way to add attribute to collection

I am overriding the Mage/Adminhtml/Sales/Order/Grid.php and adding some data to the prepareCollection. This is how I got the customer EAV Attribute campaign_id to be included in the collection, but it is kind of hacky. I was wondering if there was a better way.
protected function _prepareCollection()
{
$collection = Mage::getResourceModel($this->_getCollectionClass());
foreach ($collection as &$object){
$customer = Mage::getModel('customer/customer')
->setId($object->getCustomerId())
->load();
$object->setCampaignId($customer->getCampaignId());
}
$this->setCollection($collection);
return parent::_prepareCollection();
}
You'll need to join the data from customer records onto the order collection before its loaded.
You can observe the collection before & after load events. For sales/order_grid_collection collection these events are sales_order_grid_collection_load_before and sales_order_grid_collection_load_after - you'll want to use the former. The collection can be accessed in your _before_load event observer via $observer->getOrderGridCollection().
protected function _prepareCollection() {
$collection = Mage::getResourceModel($this->_getCollectionClass());
$class = get_class($collection);
$attribute = Mage::getModel('eav/config')
->getAttribute('customer', 'campaign_id');
$attributeId = $attribute->getAttributeId();
$backendType = $attribute->getBackendType(); //probably varchar
$tableName = Mage::getSingleton('core/resource')
->getTableName('customer_entity_' . $backendType);
$collection->getSelect()
->joinLeft(array('v' => $tableName),
'main_table.customer_id = v.entity_id AND attribute_id = 153',
array('v.value', 'v.attribute_id'));
$this->setCollection($collection);
return parent::_prepareCollection();
}

Resources