Hide API Resource Field / Belongs To Many Through Deep Relationship Problem - laravel

It seems as if the tutorial right here doesn't work as intended https://hackernoon.com/hiding-api-fields-dynamically-laravel-5-5-82744f1dd15a
I am aware of how to load relationships only if necessary by making use of "whenLoaded", but for simple field parameters that do not involve a relationship, I am unable to dynamically load these fields.
Below is a snippet of Agency.php
'expected_num_of_b_positive_donors' => $this->getExpectedNumOfDonors("B+"),
'elligible_to_schedule_mbd' => $this->
'donors' => $this->getDonors(),
'contact_persons' => $this->getContactPersons(),
'last_mbd' => $this->getLastMBD(),
'average_number_of_donors_per_day' => $this->getAverageNumberofDonorsPerDay()
This would all have been extremely easy, if Laravel came out of the box with a Belongs To Many Through Deep Relationship, as I could have easily used:
'donors' => $this->whenLoaded("donors")
instead of:
'donors' => $this->getDonors(),
For context, here is a snippet of my getDonors()
$donors = [];
// MBD -> Schedule -> Donation List -> Donor
foreach($this->mbds as $mbd){
foreach($mbd->mbd_schedules as $schedule){
foreach($schedule->donation_list as $donation_list){
if(!in_array($donation_list->donation->donor, $donors)){
array_push($donors, new UserResource($donation_list->donation->donor));
}
}
}
}
return $donors;
Since Belongs To Many Through does not exist, I had to create a custom laravel relationship where I can get All Donors of an Agency. Now, I have 2 Questions.
Is there a way for me to dynamically load the getDonors()?
// I am aware that this is wrong, I'd just like to give context if it's possible to do something like this
'donors' => $this->whenLoaded("donors", $this->getDonors()),
Is there an elegant way for me to make a custom Belongs To Many Relationship? So that I could just simply do
'donors' => $this->whenLoaded("donors")
I am aware that third party packages exist for Belongs To Many Relationships, but I'd like to know which one of these are best in your opinions as I am afraid of using a potentially wrong package and end up screwing my system much more.
Thanks Laracasts!

Related

Adding a custom sorting to listing with an aggregate in shopware 6

I am trying to build a custom sorting for the product listings in shopware 6.
I want to include a foreign table (entity is: leasingPlanEntity), get the min of one of the fields of that table (period_price) and then order the search result by that value.
I have already built a Subscriber, and try it like that, what seems to work.
public static function getSubscribedEvents(): array
{
return [
//ProductListingCollectFilterEvent::class => 'addFilter'
ProductListingCriteriaEvent::class => ['addCriteria', 5000]
];
}
public function addCriteria(ProductListingCriteriaEvent $event): void
{
$criteria = $event->getCriteria();
$criteria->addAssociation('leasingPlan');
$criteria->addAggregation(new MinAggregation('min_period_price', 'leasingPlan.periodPrice'));
// Sortierung hinzufügen.
$availableSortings = $event->getCriteria()->getExtension('sortings') ?? new ProductSortingCollection();
$myCustomSorting = new ProductSortingEntity();
$myCustomSorting->setId(Uuid::randomHex());
$myCustomSorting->setActive(true);
$myCustomSorting->setTranslated(['label' => 'My Custom Sorting at runtime']);
$myCustomSorting->setKey('my-custom-runtime-sort');
$myCustomSorting->setPriority(5);
$myCustomSorting->setFields([
[
'field' => 'leasingPlan.periodPrice',
'order' => 'asc',
'priority' => 1,
'naturalSorting' => 0,
],
]);
$availableSortings->add($myCustomSorting);
$event->getCriteria()->addExtension('sortings', $availableSortings);
}
Is this already the right way to get the min(periodPrice)? Or is it taking just a random value out of the leasingPlan table to define the sort-order?
I didn't find a way, to define the min_period_price aggregate value in the $myCustomSorting->setFields Methods.
Update 1
Some days later, I asked a less complex question in the shopware community on slack:
Is it possible to use the DAL to define a subquery for an association in the product-listing?
It should generate something like:
FROM
JOIN (
SELECT ... FROM ... WHERE ... GROUP BY ... ORDER BY ...
) AS ...
The answer there was:
Don't think so
Update 2
I also did an in-deep anlysis of the DAL-Query-Builder, and it really seems to be not possible, to perform a subquery with the current version.
Update 3 - Different approach
A different approach might be, to define custom fields in the main entity. Every time a change is made on the main entity, the values of this custom fields should be recalculated.
It is a lot of overhead work, to realize this. Especially when the fields you are adding, are dependend on other data like the availability of a product in the store, for example.
So check, if it is worth the extra work. Would be better, to have a solution for building subqueries.
Unfortunately it seems that in your case there is no easy way to achieve this, if I understand the issue correctly.
Consider the following: for each product you can have multiple leasingPlan entities, and I assume that for a given context (like a specific sales channel or listing) that still holds. This means that you would have to sort the leasingPlan entities by price, then take the one with the lowest price, and then sort the products by their lowest-price leasingPlan's price.
There seems to be no other way to achieve that, and unfortunately for you, sorting is applied at the end, even if it is sort of a subquery.
So, for example, if you have the following snippet
$criteria = $event->getCriteria();
$criteria->addAssociation('leasingPlan');
$criteria->getAssociation('leasingPlan')
->addSorting(new FieldSorting('price', FieldSorting::ASCENDING))
->setLimit(1)
;
The actual price-sorting would be applied AFTER the leasingPlan entities are fetched - essentially the results would be sorted, meaning that you would not get the cheapest leasing plan per product, instead getting the first one.
You can only do something like that with filters, but in this case there is nothing to filter by - I assume you don't have one leasingPlan per SalesChannel or per language, so that you could limit that list to just one entry that could be used for sorting
That is not to mention that this could not be included in a ProductSortingEntity, but you could always work around that by plugging into the appropriate events and modifying the criteria during runtime
I see two ways to resolve your issue
Making another table which would store the cheapest leasingPlan per product and just using that as your association
Storing the information about the cheapest leasingPlans in e.g. cache and using that for filtering (caution: a mistake here would probably break the sorting, for example if you end up with too few or too many leasingPlans per product)
public function applyCustomSorting(ProductListingCriteriaEvent $event): void
{
// One leasingPlan per one product
$cheapestLeasingPlans = $this->myCustomService->getCheapestLeasingPlanIds();
$criteria = $event->getCriteria();
$criteria->addAssociation('leasingPlan');
$criteria->getAssociation('leasingPlan')
->addSorting(new FieldSorting('price', FieldSorting::ASCENDING))
->addFilter(new EqualsAnyFilter('id', $cheapestLeasingPlans))
;
}
And then you could sort by
$criteria->addSorting(new FieldSorting('leasingPlan.periodPrice', FieldSorting::ASCENDING));
There should be no need to add the association manually and to add the aggregation to the criteria, that should happen automatically behind the scenes if your custom sorting is selected in the storefront.
For more information refer to the official docs.

Yii2, few tables with uncomfortable generic field names for 1 Model

I'm building REST++ service with Yii2, which should be able to deal with 2 table schemes, (databases storing ~the~same~ data):
a_new_one, which is perfect to be handled with ActiveRecord;
the_old_one, which has 1 generic table 'Object' with columns like 'field1', 'field2', etc, and few additional tables.
I want my request methods and controllers to be all the same for both schemes (would be very great, really). That means, that I need to have THE SAME MODELS for both cases.
The troubles all relate to the_old_one database:
Let's say data of model User is stored in tables Object and UserProfile. If I make 2 ActiveRecord classes for both tables, will it be possible to make hasOne() relations to them in 3d class User (not sure about inheritance here), so it can be manipulated like this:
$user = User::find()->someFilters()->someOrderingAndPagination()->one();
$user->Name = $your_new_name;
$user->Age = $why_not_90;
$user->save();
// and so on.
Because I really don't want to be forced to write it like this:
$user = User::find()->notSureAboutRelations()
->filters()->ordering()->pagination()->one();
$user->object->field7 = $your_new_name;
$user->userProfile->Age = $why_not_90;
$user->save();
I learned, Doctrine has easy model mapping ($user->Name can be mapped to db's Object.field7), but it's still impossible to map to 2 different tables within 1 Entity. Or am I incorrect ? If so, would it be a good idea to use Doctrine within Yii2 ?
So finished with idea to have the same controllers and models. Only URLs remain the same.
The old db Model data could be gained this way:
$user = User::find()
->select([
'Object.field7 as Name',
'UserProfile.Age as Age',
// ...
])->from('Object')
->join('UserProfile', ['UserProfile.ObjectId = Object.Id'])
->andWhere('User.Id' => $id);
And it's an ugly mess up when saving this models. Cos you need to assign a lot of fields in BEFORE_INSERT, BEFORE_UPDATE events.

Select distinct value from a list in linq to entity

There is a table, it is a poco entity generated by entity framework.
class Log
{
int DoneByEmpId;
string DoneByEmpName
}
I am retrieving a list from the data base. I want distinct values based on donebyempid and order by those values empname.
I have tried lot of ways to do it but it is not working
var lstLogUsers = (context.Logs.GroupBy(logList => logList.DoneByEmpId).Select(item => item.First())).ToList(); // it gives error
this one get all the user.
var lstLogUsers = context.Logs.ToList().OrderBy(logList => logList.DoneByEmpName).Distinct();
Can any one suggest how to achieve this.
Can I just point out that you probably have a problem with your data model here? I would imagine you should just have DoneByEmpId here, and a separate table Employee which has EmpId and Name.
I think this is why you are needing to use Distinct/GroupBy (which doesn't really work for this scenario, as you are finding).
I'm not near a compiler, so i can't test it, but...
Use the other version of Distinct(), the one that takes an IEqualityComparer<TSource> argument, and then use OrderBy().
See here for example.

Saving a model with multiple foreign keys in Laravel 4

I understand that in order to save a foreign key, one should use the related model and the associate() function, but is it really worth the trouble of going through this
$user = new User([
'name' => Input::get('name'),
'email' => Input::get('email')
]);
$language = Language::find(Input::get('language_id');
$gender = Gender::find(Input::get('gender_id');
$city = City::find(Input::get('city_id');
$user->language()->associate($language);
$user->gender()->associate($gender);
$user->city()->associate($city);
$user->save();
when one can simply do this?
User::create(Input::all());
I feel like I'm missing something here, maybe there's an even simpler and cleaner way to handle foreign keys in controllers (and views)?
You can use push() method instead which would allow you to push to related models.
This link should answer your query.
Eloquent push() and save() difference
I really don't see anything wrong at all with doing User::create(Input::all());.
Obviously you'd want some validation, but it's doing the same thing.
I think the associate() method is more useful for the inverse of your situation.
For example, say you had a form which a user could fill out to add their city to your app, and upon doing so, they should automatically be assigned to that city.
$city = City::create(Input::all()); would only achieve the first half of your requirements because the user has not yet been attached as city does not have a user_id column.
You'd then need to do something like $city->user()->associate(User::find(Auth::user()->id));

Magento: Resource Model 'loadByAttribute' with multiple parameters

I need a way to locate a Magento object my multiple attributes. I can look up an object by a single parameter using the 'loadByAttribute' method, as follows.
$mageObj->loadByAttribute('name', 'Test Category');
However, I have been unable to get this to work for multiple parameters. For example, I would like to be able to do the above query using all of the following search parameters. It might look something like the following.
$mageObj->loadByAttribute(array('entity_id' => 128,
'parent_id' => 1,
'name' => 'Test Category'));
Yes, I know that you don't need all of these fields to find a single category record. However, I am writing a module to export and import a whole website, and I need to test if an object, such as a category, already exists on the target system before I create it. To do this, i have to check to see if an object of the same type, with multiple matching attributes already exists, even if it's ID is different.
This may not answer your question, but it may solve your problem.
Magento does not support loadByAttribute for multiple attributes, but instead you can do this.
$collection = $mageObj->getCollection()
->addAttributeToFilter('entity_id', 128)
->addAttributeToFilter('parent_id', 1)
->addAttributeToFilter('name', 'Test Category');
$item = $collection->getFirstItem();
if ($item->getId()){
//the item exists
}
else {
//the item does not exist
}
addAttributeToFilter works for EAV entities (products, categories, customers).
For flat entities use addFieldToFilter.
There is a special case for sales entities (orders, invoices, creditmemos and shipments) that can use both of them.

Resources