What's the best way to approach this in Paris ORM?
I have a set of categories and a set of supplier profiles that havw a column called reatured. Currently my class is as follows:
<?php
namespace {
/**
* Class Category
*/
class Category extends ConfettiModel
{
public static $_table = 'supplier_directory_category';
/**
* Returns only top level categories - they have no parent
*
* #return bool
*/
public static function topLevel()
{
return self::where('parent', 0);
}
public static function marketing()
{
return self::where('marketing', 'Yes');
}
public function getTable() {
return self::$_table;
}
/**
* Is this a top level category - has no parent
*
* #return bool
*/
public function isTopLevel()
{
return ($this->parentId == 0);
}
/**
* Associated DirectoryProfile's
*
* #return ORMWrapper
*/
public function profiles()
{
return $this->has_many_through('DirectoryProfile', 'CategoryDirectoryProfile', 'category', 'supplier');
}
}
I'd like to add a new function, featuredProfiles() that allows me to retrieve the same results as profiles(), but in this case I want to restrict it to suppliers with featured = 'Yes'.
I'm not quite sure how to make this happen.
I took a punt and the answer was easier than I anticipated:
public function featuredProfiles() {
return $this->profiles()->where('featured', 'Yes');
}
The where is added as part of the query on the joined table.
Related
I cannot seem to apply filtering on all children defined in a tree model format with eager loading mechanism
Here is my model definition (works great):
class Section extends Model
{
[...]
/**
* #return HasOne
*/
public function parent()
{
return $this->hasOne(
self::class,
'Id',
'IdParent'
)->with('parent');
}
/**
* #return HasMany
*/
public function children()
{
return $this->hasMany(
self::class,
'IdParent',
'Id'
)->with('children');
}
[...]
}
Now I want to filter out recursive based on a 'criteria object'
public function getMachines(SectionCriteria $sectionCriteria = NULL)
{
/**
* #var $builder Builder|Section
*/
$builder = Section::with([
'children' => function ($query) use ($sectionCriteria) {
if ($sectionCriteria) {
foreach ($sectionCriteria->getFilterFlags() as $flagName => $flagValue) {
if ($flagValue) {
$query->whereFlag($flagName); //Custom implementation
} else {
$query->whereNotFlag($flagName); //Custom implementation
}
}
}
}
]);
This works bot it is applied to the first level of the tree.
My question would be: Is there a way to pass an object to the children() relation so I can apply filters recursive (which would apply to all levels)?
Something like, let's say:
P.S: This is not possible since only a callback is accepted as a parameter
public function children($parameters)
{
return $this->hasMany(
self::class,
'IdParent',
'Id'
)->with('children'=>$parameters);
}
What I wouldn't want to use (with respect to SOLID principles):
Make a static class variable which holds criteria
A global variable of any kind
I also tried to retrieve children recursive (and apply filters) but ended up with more queries so Eloquent is preety well optimized sooo...
I used technique #1 (Make a static class variable) though I do not really like it but it works.
Model
/**
* #var null|SectionCriteria
*/
public static $childrenFilter = NULL; //This can be whatever you need since it's static
/**
* #return HasMany
*/
public function children()
{
return $this->hasMany(
self::class,
'IdParent',
'Id'
)->with(['children'=>self::searchChild()]);
}
/**
* #return \Closure
*/
public function searchChild()
{
return function ($builder) {
if (Section::$childrenFilter) {
foreach ($sectionCriteria->getFilterFlags() as $flagName => $flagValue) {
if ($flagValue) {
$query->whereFlag($flagName); //Custom implementation
} else {
$query->whereNotFlag($flagName); //Custom implementation
}
}
}
};
}
/**
* #param SectionCriteria $criteria
*/
public static function setChildSearch(SectionCriteria $criteria)
{
Section::$childrenFilter = $criteria;
}
/**
* Remove the search criteria filter
*/
public static function clearChildSearch()
{
Section::$childrenFilter = NULL;
}
Repository (the actual usage)
/**
* #param SectionCriteria|NULL $sectionCriteria
* #return Section[]|Collection
*/
public function getMachines(SectionCriteria $sectionCriteria = NULL)
{
/**
* #var $builder Builder|Section
*/
$builder = Section::with(['children']); //Here I do not need the root elements to be filtered, If needed then use: Section::with(['children'=>Section::searchChild()])
Section::setChildSearch($sectionCriteria);
$builder->orderBy('Name');
$results = $builder->get();
Section::clearChildSearch();
return $results;
}
Again...not preety but it gets the job done
New: Another way (will test this out) would be to extend the Builder class
Hello guys i trying to make product can be buyable only once so i make that
productsTable: id,name,desc,price,onlyOnce(Boolean 0-1)
And i make pivot table too: orders: user_id,product_id
I try this code:
public function showProducts(Category $category, User $user) {
$products = $category->products()->where('onlyOnce', 1)->pluck('id')->toArray();
$userOrders = $user->orders()->pluck('user_Id')->toArray();
$missProducts = array_intersect($userOrders, $products);
$extras = $category->products()->whereNotIn('id', $missProducts)->get();
return view('products.category',compact('extras'));
}
But not work, some ideas how to do that?
(If you need here is my models)
Product.php
class Product extends Model
{
protected $fillable = [];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function categories() {
return $this->belongsToMany(Category::class);
}
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function orders(){
return $this->hasMany(Order::class);
}
}
Order.php
class Order extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function product(){
return $this->belongsTo(Product::class);
}
/**
* #return int
*/
public function getTotalPrice() {
$orders = self::with('product')->get();
$total = $orders->pluck('product')->sum('price');
return $total;
}
}
User.php
class User extends Authenticatable
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name'];
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function orders()
{
return $this->hasMany(Order::class);
}
}
With that code it return empty array i don't know why, so please if you have some ideas how i can fix that or what new code to type answear! Thanks in advice, peace! ;)
You set up your database kind of weird, I would have done it differently, however the following code should retrieve all the products not purchased by a user. Let me know if it works.
$productID = 123;
$userID = 10;
$purchased = $orders->select('product_id')->where('user_id', $userID)->toArray();
$notPurchased = $productsTable->whereNotIn('id',$purchased)->get();
You could then pass $notPurchased to a view, and loop over it with a foreach() loop to get each unpurchased product id.
here are the the two classes with the functions involved
section class has many to many relation with student class
class Section
{
/**
* #ORM\ManyTOMany(targetEntity="Student",inversedBy="sections")
*/
private $students;
public function __construct() {
$this->students = new ArrayCollection();
}
/**
* Add students
*
* #param \Blogger\sectionBundle\Entity\Student $students
* #return Section
*/
public function addStudent(\Blogger\sectionBundle\Entity\Student $students)
{
$this->students[] = $students;
return $this;
}
/**
* Remove students
*
* #param \Blogger\sectionBundle\Entity\Student $students
*/
public function removeStudent(\Blogger\sectionBundle\Entity\Student $students)
{
$this->students->removeElement($students);
}
/**
* Get students
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getStudents()
{
return $this->students;
}
}
and
class Student {
/**
* #ORM\ManyToMany(targetEntity="Section", mappedBy="students")
*/
private $sections;
/**
* #ORM\Column(type="string")
*/
protected $studentId;
public function __construct() {
$this->sections = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add sections
*
* #param \Blogger\sectionBundle\Entity\Section $sections
* #return Student
*/
public function addSection(\Blogger\sectionBundle\Entity\Section $sections)
{
$this->sections[] = $sections;
return $this;
}
/**
* Remove sections
*
* #param \Blogger\sectionBundle\Entity\Section $sections
*/
public function removeSection(\Blogger\sectionBundle\Entity\Section $sections)
{
$this->sections->removeElement($sections);
}
/**
* Get sections
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getSections()
{
return $this->sections;
}
}
as in mysql
DELETE from student_section
where student_id = (select student.id from student where student.name="dummy")
And section_id = 1
whats wrong with:
public function removeStudent(Student $student)
{
$this->students->removeElement($student);
}
You can use the generic doctrine command to generate getters and setters.
app/console doctrine:generate:entities NameSpace:Entity
Also you should read about synchronizing ManyToMany bidirectional relationships with Doctrine. Here you can read about the "adders" but the same logic applies to remove methods.
EDIT - After question was updated
If I understood you correctly you want to remove a student from a section when you have the student name. The created student_section is a generated table from Doctrine. You can execute normal PDO statements in your Controllers or Repositories, but I would personaly implement a function in the model to keep it as OOP as possible.
public function removeStudentByName(Student $student)
{
$toRemove = $this->students->filter(function (Student $s) use ($student) {
return ($->getName() == $student->getname());
});
foreach ($toRemove as $student) {
$this->students->remove($student);
}
}
In a controller you can do something like:
//$student, $em is fetched
$section->removeStudentByName($student);
$em->flush();
sorry for my misleading and unclear Question
i found what i was searching for
//in the controller:
$section = $em->getRepository('BloggersectionBundle:Section')->find(2);
$student = $em->getRepository('BloggersectionBundle:Student')->findByStudentId("555555");
$student->removeSections($section);
$em->flush();
and in Student model
public function removeSections(Section $sections)
{
$sections->removeStudent($this);
$this->sections->removeElement($sections);
}
and finally i edited the anotation in both student and section
to cascade remove
* #ORM\ManyToMany(targetEntity="Section", mappedBy="students", cascade={"persist", "remove"})
I have a global container class:
final class Container
{
/**
* #return ForumThread
*/
public static function getForumThread()
{
if (self::$obj1 === null)
{
self::$obj1 = new ForumThread();
}
return self::$obj1;
}
/**
* #return ForumPosts
*/
public static function getForumPosts()
{
if (self::$obj2 === null)
{
self::$obj2 = new ForumPosts();
}
return self::$obj2;
}
}
my models:
class ForumThread
{
/**
* #return bool
*/
public function findBadLanguage ($inWhat)
{
return (bool)rand(0,1);
}
/**
* #return
*/
public function add ($threadName)
{
if (!$this->findBadLanguage ($threadName))
{
INSERT INTO
}
}
}
class ForumPost
{
/**
* #return
*/
public function post ($toThreadId, $comment)
{
// im talking about this:
Container::getForumThread()->findBadLanguage($comment);
}
}
I know findBadLanguage() should be in another class, but lets suppose thats okay. Lets focus on Container::get****() calls. Is it OK to turn to a global container and get objects from it? Doesnt it hury Demeter's law? (those object must be exists only once, and can be DI-ed)
EDIT: you can regard to the Container as a Factory
Long story short: I'm building a "privacy" page where uses can chose what shows up and what does not show up on their profiles.
I am considering having a 1:m table user:privacy and just have entries for the keys they want private. If they don't exist they are public. Hope this makes sense.
Table would be user_privacy and will have 3 columns: id, user_id, privacy_key (string, i.e. email/phone/cell/etc)
Is there a way to simple query by the keys i will define that i can run to determine if the user has a key or not or do i have to go extra lengths to add a function to the user model to do this (trying to avoid, love the magic-ness of eloquent)
Basically i want to have a condition that sounds like "if ($user->privacy->email or $user->privacy->phone)"
Thanks and hope i was clear enough, lol
You could add a function to your user model:
public function isPrivate($attribute){
$privacyAttribute = $this->privacy->first(function($model) use ($attribute){
return $model->key == $attribute; // key being the column in the privacy model
});
return !is_null($privacyAttribute);
}
And then do your if statement this way:
if ($user->isPrivate('email') or $user->isPrivate('phone'))
Or a different implementation (usage is the same)
private $privacyAttributes = null;
public function isPrivate($attribute){
if($this->privacyAttributes == null){
$this->privacyAttributes = $this->privacy()->lists('key');
}
return in_array($attribute, $this->privacyAttributes);
}
User Model header:
/**
* Class User
* #package Interallmas
*/
class User extends Model implements AuthenticatableContract, CanResetPasswordContract {
/**
* #var null|array
*/
protected $privacy_keys = NULL;
Privacy Relationship:
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function privacy() {
return $this->hasMany('Interallmas\Privacy');
}
Privacy functions:
/**
* #return bool
*/
public function privacy_initialized() {
return ($this->privacy_keys !== NULL);
}
/**
* #return void
*/
public function initialize_privacy() {
if (!$this->privacy_initialized()) {
$this->privacy_keys = [];
foreach ($this->privacy as $privacy) {
$this->privacy_keys[] = $privacy->privacy_key;
}
}
}
/**
* #param $key
* #return bool
*/
public function isPrivate($key) {
$this->initialize_privacy();
return (in_array($key,$this->privacy_keys));
}
So: Whenever i access the isPrivate($key) method, i cache the result for the next use so i don't hit the server too hard - the function may be accessed once or more - i just query once, the first time. I believe for my needs, this is the best way to do it.
I think a simple count > 0 check should suffice. This requires you to have defined the relationship with the hasMany method for the User Model.
if (count($user->privacy) > 0) {
...
}