Laravel PIVOT table with extra fields and withCount('table') not working as expected - eloquent

I've the following structure in my database.
users
--
id
...
products
--
id
...
licenses (pivot table with some extra informations these data are grabbed via API)
--
user_id
product_id
license_key
license_type
url
purchased_at (datetime)
supported_until (datetime)
...
And here are codes in my model:
# User has many licenses
# User has many products through licenses
User
// User has many licenses.
public function licenses()
{
return $this->hasMany(License::class);
}
Product Model.
# Product has many licenses
Product
public function licenses()
{
return $this-hasMany(License::class);
}
License Model
# License belongs to an user.
# License belongs to a product.
License
public function user()
{
return $this->belongsTo(User::class);
}
public function product()
{
return $this->belongsTo(Product::class);
}
Route
// These class namespaces are imported and all the necessary middlewares are applied.
Route::get('/my-products', [ProductController::class, 'index']);
And in product controller
ProductController
public function index()
{
$user = Auth::user();
$products = Product::whereHas('licenses', function (Builder $query) use($user) {
$query->where('user_id', $user->id);
})
->withCount(['licenses']) // This is returning all the licenses for this product disrespecting the user.
->paginate(5);
}
I'm not getting any products from the licenses relation when using $user->products
I need to display the licenses count for this product of the user.
What I'm getting is: Product B has all total 15 licenses.
What I'm getting is all the licenses count for this product. https://prnt.sc/xyokil

I believe you can introduce a closure method to filter for a specific user while loading related models as
$products = Product::whereHas('licenses', function (Builder $query) use($user) {
$query->where('user_id', $user->id);
})
->withCount(['licenses' => function ($query) use ($user) {
$query->where('user_id', $user->id);
}])
->paginate(5);

Related

Magento 2: How can I get Customer during collectRates on custom shipping method

How can I get the customer and cart during collectRates() function of my custom shipping method.
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory,
\Psr\Log\LoggerInterface $logger,
\Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory,
\Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory,
\Magento\Checkout\Model\Cart $cart,
\Magento\Customer\Model\Session $customerSession,
array $data = []
) {
$this->_cart = $cart;
$this->_rateResultFactory = $rateResultFactory;
$this->_rateMethodFactory = $rateMethodFactory;
$this->_customerSession = $customerSession;
parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);
}
public function collectRates(RateRequest $request)
{
if (!$this->getConfigFlag('active')) {
return false;
}
if(!$this->_customerSession->isLoggedIn()) {
return false;
}
$customer = $this->_customerSession->getCustomer();
$qty = $this->_cart->getItemsQty();
...
Using customer session and checking if isLoggedIn() only works for frontend but returns false when placing order in the admin.
How can I get customer properly and calculate my price per item for both frontend and admin order placement?
If you can detect when your code runs in Admin, you can use
\Magento\Backend\Model\Session\Quote
as your [admin_session] and use [admin_session]->getCustomerId(), along with the customer repository (inject interfaces in constructor and let DI pass the correct objects) to fetch the customer object.
I would suggest to check the contents of the \Magento\Backend\Model\Session\Quote object, as it might contain a pre-loaded customer object already, in which case you can avoid loading it.
You could use \Magento\Framework\App\State::getAreaCode() to check if within admin

Yii2: Populating model field with value from many-to-many relation

We are trying to set-up our REST API in a way that a user can only retrieve data related to his account. Uptill now, We've only had a public API and it all worked like a charm.
The public API will still get used, so we started from scratch.
We have a User model, extending ActiveRecord and implementing IdentityInterface. A User will have a relation to a Customer model (Many:Many, but at least one). A User will have only one MASTER Customer.
The above is saved in the database using 3 tables app_customer_users,app_customers and link_customer_users_customers. The join table contains a boolean field master.
In the User model:
public function getCustomers() {
return $this->hasMany(Customer::className(), ['id' => 'customer_id'])
->viaTable('link_customer_users_customers', ['user_id' => 'id']);
}
In the Customer model:
public function getUsers() {
return $this->hasMany(User::className(), ['id' => 'user_id'])
->viaTable('link_customer_users_customers', ['customer_id' => 'id']);
}
This works great if we would request all customers, they'll have 'users' populated (if we add it in extraFields etc, etc...)
The new API uses Basic user:pass in authentication, and we can get the current user object/identity by calling Yii::$app->user->getIdentity().
NOW THE PROBLEM
We would like to be able to include the Customer ID when a user connects in a way that we can get it by calling Yii::$app->user->getIdentity()->customer_id OR Yii::$app->user->getIdentity()->getCustomerId() The Customer ID should be the ID where master in the join table == true.
We've tried adding it to fields, as we did before with hasOne relations, but in this case it does not seem to work:
$fields['customer_id'] = function($model) {
return $model->getCustomers()->where('link_customer_users_customers.master', true)->one()->customer_id;
}; // probably the where part is our porblem?
And we've tried creating a custom getter like this:
public function getCustomerId() {
return $this->getCustomers()->andFilterWhere(['link_customer_users_customers.master' => true])->one()->customer_id;
}
This last attempt resulted in an error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'link_customer_users_customers.master' in 'where clause'\nThe SQL being executed was: SELECT * FROM `app_customers` WHERE (`link_customer_users_customers`.`master`=TRUE) AND (`id`='93')
We've been searching in the docs and on Google, but did not find an example on how to do this.
SOLUTION
Based on accepted answer we added some code.
In User model:
// Include customer always with user
public static function findIdentity($id) {
return static::findOne($id)->with('customer');
}
// Next to `getCustomers()`, added this `hasOne` relation to get only the customer from
// which the current user is the master user
public function getCustomer() {
return $this->hasOne(Customer::className(), ['id' => 'customer_id'])
->viaTable('link_customer_users_customers', ['user_id' => 'id'], function ($query) {
$query->andWhere(['master' => 1]);
});
}
// Getter, just because
public function getCustomerId() {
return $this->customer->id;
}
And now we can get the ID from the customer by calling Yii::$app->user->getIdentity()->customer->id OR Yii::$app->user->identity->customer->id etc, etc.. in the project..
You should add a relation like below and use an anonymous function as the third parameter to the viaTable to add another condition to check master field as true.
public function getMasterCustomerId() {
return $this->hasOne(Customer::className(), ['id' => 'customer_id'])
->viaTable('link_customer_users_customers', ['user_id' => 'id'],function($query){
$query->andWhere(['master' => 1]);
});
}
and then you can use it for the logged in user as below.
Yii::$app->user->identity->masterCustomerId->id;

Magento 2: Get Product Stock Quantity and Other Stock Information

Magento 2: Get Product Stock Quantity and Other Stock Information
How to get the product stock quantity and information in magento 2
If we look at the StockItemRepository class the get method wants parameter $stockItemId, not $productId. Reference:
https://github.com/magento/magento2/blob/develop/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php#L202
I've seen many sites where stock item id IS NOT the same as product id and we should not assume it's the same ID.
To get this working you could use \Magento\CatalogInventory\Model\Stock\Item class instead and load the model by product_id field instead. I am also aware of the website_id and stock_id fields, but as far as I know it's not used (yet) and also existed in M1.
It should look something like this (code not tested):
<?php
namespace Vendor\Module\Model;
use \Magento\CatalogInventory\Model\Stock\Item;
class Mymodel
{
/**
* #var Item
*/
protected $stockItem;
/**
* Mymodel constructor.
*
* #param Item $stockItem
*/
public function __construct(Item $stockItem)
{
$this->stockItem = $stockItem;
}
/**
* Description
*
* #param $productModel
*/
public function getStockQtyByProductId($productModel)
{
try {
$stockItem = $this->stockItem->load($productModel->getId(), 'product_id');
return $stockItem->getQty();
} catch (\Exception $e) {
echo 'Something went wrong and was not handled: ' . $e->getMessage();
exit;
}
}
}
if you have product object then just use following:
echo $_product->getExtensionAttributes()->getStockItem()->getQty();
conplete object can be find as follow:
var_dump($_product->getExtensionAttributes()->getStockItem()->getData());
Actually this operation should be performed using \Magento\CatalogInventory\Api\StockRegistryInterface and here we can obtain \Magento\CatalogInventory\Api\Data\StockItemInterface, by product id or sku and we can use bunch of usefull methods to get stock information - linked product. For general stock information I recommend explore other service contracts declared in Magento\CatalogInventory\Api
Example of usage:
<?php
namespace Test\Test\Model;
class Test
{
protected $_stockRegistry;
public function __construct(\Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry)
{
$this->_stockRegistry = $stockRegistry;
}
public function getStockItem($productId)
{
return $this->_stockRegistry->getStockItem($productId);
}
}
this code help you to get product quantity
<?php
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$StockState = $objectManager->get('\Magento\CatalogInventory\Api\StockStateInterface');
echo $StockState->getStockQty($productId);
?>
If you have the product object and do not want to use the other classes, You can try in the following way.
// For phtml file
$prodObj = $_product->load($_product->getId()); // $_product object in list.phtml
$stockItem = $prodObj->getExtensionAttributes()->getStockItem();
$stockQty = $stockItem->getQty(); // $stockItemData = $stockItem->getData();
// For php class file
$stockItem = $prodObj->getExtensionAttributes()->getStockItem();
$stockQty = $stockItem->getQty(); // $stockItemData = $stockItem->getData();
Credits:
https://github.com/magento/magento2/issues/7057#issuecomment-256052729
Actually \Magento\CatalogInventory\Api\Data\StockStatusInterface
should answer to all your questions.
Long story short:
Magento has StockItem entity which represents amount (Qty) of specific product (productId) on a concrete stock (stockId).
StockItemInterface should be used when you would like to "write" data into the data storage (like update amount of products to sync up Magento with your ERP system or to make deduction of stock during the checkout process).
StockStatusInterface is opposite to it. It should be used to "read" data for representation (on front-end). Consider StockStatus as an index which contains aggregated stock data for each specific product.
So, if you would like to get product stock status (in stock, out of stock) by product_id.
You need using StockStatusRepositoryInterface::getList(StockStatusCriteriaInterface $searchCriteria);
get StockStatus entity for specified product
/** #var \Magento\CatalogInventory\Api\StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory **/
$criteria = $stockStatusCriteriaFactory->create();
$criteria->setProductsFilter($productId);
/** #var \Magento\CatalogInventory\Api\Data\StockStatusRepositoryInterface $stockStatusRepository **/
$result = $stockStatusRepository->getList($criteria);
$stockStatus = current($result->getItems());
$stockStatus->getProductId(); // product id
$stockStatus->getQty(); // quantity of specified product
$stockStatus->getStockStatus(); // Could be
// Magento\CatalogInventory\Model\Stock\Status::STATUS_OUT_OF_STOCK = 0;
// or
// Magento\CatalogInventory\Model\Stock\Status::STATUS_IN_STOCK = 1;

retrieve values from model in mvc2

I don't know how to create functions to retrieve the values.
*Table 1: OrgVasplans*
-Id
-vasplanId
-OrgId
-CreatedDate
Table-2: vasplans
-Id
-name
-amount
-validity
-vasdurationId
Table-3: VasDuration
Id
Duration.
These are my tables..
I have Controller named Candidatesvas and action method VasDetails....
I already stored the values into vasPlans table.
when I click in view "Details" link it will go to details page..
Then the values are retrieve from "Orgvasplans" table automatically without enter any input..
How to create methods for this....
I created some methods but the method contains only Name "field". I want to retrieve multiple values like "Amount", "validity" like that.....
Repository:
public IQueryable<VasPlan> GetVasPlans()
{
return from vasplan in _db.VasPlans
orderby vasplan.Name ascending
select vasplan;
}
public OrgVasPlan GetOrgVasPlan(int id)
{
return _db.OrgVasPlans.SingleOrDefault(v => v.Id == id);
}
public int AddOrgVasPlan(OrgVasPlan orgvasplan)
{
_db.OrgVasPlans.AddObject(orgvasplan);
Save();
return orgvasplan.Id;
}
public void AddVasPlan(VasPlan vasPlan)
{
_db.VasPlans.AddObject(vasPlan);
}
Controller
public ActionResult VasDetails(FormCollection collection)
{
OrgVasPlan orgvasplan = new OrgVasPlan();
orgvasplan.CreatedDate = DateTime.Now;
orgvasplan.OrgId = LoggedInOrganization.Id;
orgvasplan.vasplanId=??????????????
VasPlan vasplan = new VasPlan();
//if (!string.IsNullOrEmpty(collection["Name"])) ;
_repository.AddOrgVasPlan(orgvasplan);
_repository.Save();
return View();
}
Here i don't know how to put code here for get multiple values form vasplans table like(amount,name,validity etc...,)
this is my problem...
Make your view strongly-typed, make sure you create input elements whose names correspond to the model properties (or use HTML helpers, e.g. Html.TextBoxFor(model => model.Amount). That way MVC will automatically fill in the model for you when the action that should take the model as a argument, is invoked.
For example your action should be:
public ActionResult NewVasPlan(VasPlan vplan)
{
//check model state
//save or return error messages
}
Or you can simply add string and int parameters to the Action like this:
public ActionResult NewVasPlan(string name, int amount /*, etc*/)
{
//MVC will also automatically fill name, amount, from request POST or GET params
//(or cookies??)
}
Hope this helps, tell me if you need more info or if I misunderstood your question.

Zend Framework - ORM relationships and optimization

I've been using ZF for few months and I'm really happy with it however I'm not completely sure about how to work with models relationships and at the same time avoid multiple queries to the db. Many people has this problem and no one seems to find a good solution for it. (and avoiding using a third party ORM) For example I have a list of users, and each user belongs to a group. I want a list of users displaying user info and group name (to tables: users, and groups. Users has a foreign key to the table groups).
I have:
2 mapper classes to handle those tables, UserMapper and GroupMapper.
2 Model Classes User and Group
2 Data Source classes that extends Zend_DB_Table_Abstract
in user mapper I can do findParentRow in order to get the group info of each user, but the problem is i have an extra query for each row, this is not good I think when with a join I can do it in only one. Of course now we have to map that result to an object. so in my abstract Mapper class I attempt to eager load the joining tables for each parent row using column aliasing (similar as Yii does.. i think) so I get in one query a value object like this
//User model object
$userMapper= new UserMapper();
$users= $userMapper->fetchAll(); //Array of user objects
echo $user->id;
echo $user->getGroup()->name // $user->getParentModel('group')->name // this info is already in the object so no extra query is required.
I think you get my point... Is there a native solution, perhaps more academic than mine, in order to do this without avoiding multiple queries? // Zend db table performs extra queries to get the metadata thats ok and can be cached. My problem is in order to get the parent row info... like in yii.... something like that $userModel->with('group')->fetchAll();
Thank you.
Develop your mapper to work with Zend_Db_Select. That should allow for flexibility you need. Whether group table is joined depends on the parameter provided to mapper methods, in this example group object is the critical parameter.
class Model_User {
//other fields id, username etc.
//...
/**
* #var Model_Group
*/
protected $_group;
public function getGroup() {
return $this->_group;
}
public function setGroup(Model_Group $group) {
$this->_group = $group;
}
}
class Model_Mapper_User {
/**
* User db select object, joins with group table if group model provided
* #param Model_Group $group
* #return Zend_Db_Select
*/
public function getQuery(Model_Group $group = NULL) {
$userTable = $this->getDbTable('user'); //mapper is provided with the user table
$userTableName = $userTable->info(Zend_Db_Table::NAME); //needed for aliasing
$adapter = $userTable->getAdapter();
$select = $adapter->select()->from(array('u' => $userTableName));
if (NULL !== $group) {
//group model provided, include group in query
$groupTable = $this->getDbTable('group');
$groupTableName = $groupTable->info(Zend_Db_Table::NAME);
$select->joinLeft(array('g' => $groupTableName),
'g.group_id = u.user_group_id');
}
return $select;
}
/**
* Returns an array of users (user group optional)
* #param Model_User $user
* #param Model_Group $group
* #return array
*/
public function fetchAll(Model_User $user, Model_Group $group = NULL) {
$select = $this->getQuery();
$adapter = $select->getAdapter();
$rows = $adapter->fetchAll($select);
$users = array();
if (NULL === $group) {
foreach ($rows as $row) {
$users[] = $this->_populateUser($row, clone $user);
}
} else {
foreach ($rows as $row) {
$newUser = $this->_populateUser($row, clone $user);
$newGroup = $this->_populateGroup($row, clone $group);
//marrying user and group
$newUser->setGroup($newGroup);
$users[] = $newUser;
}
}
return $users;
}
/**
* Populating user object with data
*/
protected function _populateUser($row, Model_User $user) {
//setting fields like id, username etc
$user->setId($row['user_id']);
return $user;
}
/**
* Populating group object with data
*/
protected function _populateGroup($row, Model_Group $group) {
//setting fields like id, name etc
$group->setId($row['group_id']);
$group->setName($row['group_name']);
return $group;
}
/**
* This method also fits nicely
* #param int $id
* #param Model_User $user
* #param Model_Group $group
*/
public function fetchById($id, Model_User $user, Model_Group $group = NULL) {
$select = $this->getQuery($group)->where('user_id = ?', $id);
$adapter = $select->getAdapter();
$row = $adapter->fetchRow($select);
$this->_populateUser($row, $user);
if (NULL !== $group) {
$this->_populateGroup($row, $group);
$user->setGroup($group);
}
return $user;
}
}
use scenarios
/**
* This method needs users with their group names
*/
public function indexAction() {
$userFactory = new Model_Factory_User();
$groupFactory = new Model_Factory_Group();
$userMapper = $userFactory->createMapper();
$users = $userMapper->fetchAll($userFactory->createUser(),
$groupFactory->createGroup());
}
/**
* This method needs no user group
*/
public function otherAction() {
$userFactory = new Model_Factory_User();
$userMapper = $userFactory->createMapper();
$users = $userMapper->fetchAll($userFactory->createUser());
}
Cheers
I have written a solution by subclassing Zend_Db_Table_Rowset_Abstract and Zend_Db_Table_Row_Abstract. I'll try to summarise it briefly and if it is of interest to any one I can expand on it.
I created an abstract model class - My_Db_Table_Row - that contains an array (keyed on child classname) of rowsets of children.
I created an abstract Rowset class - My_Db_Table_Rowset - that extracts the data from a query based on column names and creates rowsets stored in My_Db_Table_Row_children.
The My_Db_Table_Rowset class uses _dependantTables and _referenceMap from Zend_Db_Table_Abstract to create child instances (from joined columns) and add them to the appropriate array within the _children of their parent instance (created from 'primary table' columns).
Accessing a child is done as follows: $car->getDrivers();
public function getDrivers() {
// allow for lazy loading
if (!isset($this->_children['My_Model_Driver'])) {
$this->_children['My_Model_Driver'] = My_Model_Driver::fetch........;
}
return $this->_children('My_Model_Driver');
}
Initially, I coded this for 2 levels, parent and child but I am in the process of extending this to handle more levels, e.g. grandparent-parent-child.