Handling items in the collection pattern by a data mapper - php

My question is related to the update section of #tereško's answer in "Who should handle the conditions in complex queries, the data mapper or the service layer?" Below is the code for reference and convenience.
$category = new Category;
$category->setTitle( 'privacy' );
$list = new ArticleCollection;
$list->setCondition( $category );
$list->setDateRange( mktime( 0, 0, 0, 12, 9, 2001) );
// it would make sense, if unset second value for range of dates
// would default to NOW() in mapper
$mapper = new ArticleCollectionMapper;
$mapper->fetch( $list );
foreach ( $list as $article )
{
$article->setFlag( Article::STATUS_REMOVED );
}
$mapper->store( $list );
In this code ArticleCollection is a collection of Domain Objects, let's call them Articles. The moment the ArticleCollectionMapper fetches data from the database, assigning it to $list, instances of Article need to be made (for each row). Would the instances of Article be added to our collection instance ($list) via a method like $list->addArticle($newArticle), should a Factory Object like ArticleFactory be used for that, or is there another option I haven't considered?

I wouldn't think to actually use a factory object to add the articles. You may see yourself using one to make the instance of Article (in the second example), though. What I went ahead and did was add an addArticles () method to the ArticleCollection instance. This way you can simply call the method on your instance of ArticleCollection from the mapper. ArticleCollectionMapper may look something like:
class ArticleCollectionMapper extends DataMapperAbstract
{
public function fetch ( ArticleCollection $articles )
{
$prepare = $this->connection->prepare( "SELECT ..." );
$prepare->execute();
// filter conditions
$articles->addArticles( $prepare->fetchAll() );
}
}
You'd need to do some filtering by getting the conditions from the ArticleCollection instance, which is excluded from the snippet above. Then our domain object's addArticles() implementation would look similar following:
class ArticleCollection extends DomainObjectAbstract
{
protected $collection = array();
public function addArticles ( Array $articles )
{
foreach ( $articles as $article )
{
$articleCollectionItem = new Article;
$articleCollectionItem->setParams( $article );
// however you prefer filling your list with `Article` instances
$this->collection[] = $articleCollectionItem;
}
}
}
You may also want to add an addArticle() method depending on your needs, and then just replacing what's within the foreach above with a call to addArticle(). Note that the above examples are extremely simplified and code will need to be adapted in order to meet your standards.

Related

How to set doctrine associations?

I know that association property in entity is implements \Doctrine\Common\Collections\Collection. I know that in constructor such properties should be initialized:
$this->collection = new \Doctrine\Common\Collections\ArrayCollection()
I know that I can modify collections using ArrayCollection#add() and ArrayCollection#remove(). However I have a different case.
Suppose I have a new simple array of associative entities. Using existing methods I need to check every element in array: if entity collection has it. If no - add array element to entity collection. In addition to this, I need to check every element in entity collection. If any collection element is absent in new array, then I need to remove it from collection. So much work to do trivial thing.
What I want? To have the setProducts method implemented:
class Entity {
private $products;
// ... constructor
public function setProducts(array $products)
{
// synchronize $products with $this->products
}
}
I tried: $this->products = new ArrayCollection($products). However this makes doctrine remove all products and add those ones from $products parameter. I want similar result but without database queries.
Is there any built in solution in Doctrine for such case?
Edit:
I would like to have a method in ArrayCollection like fromArray which would merge elements in collections removing unneeded. This would just duplicate using add/remove calls for each element in collection argumen manually.
Doctrine collections do not have a "merge"-feature that will add/remove entities from an array or Collection in another Collection.
If you want to "simplify" the manual merge process you describe using add/remove, you could use array_merge assuming both arrays are not numeric, but instead have some kind of unique key, e.g. the entity's spl_object_hash:
public function setProducts(array $products)
{
$this->products = new ArrayCollection(
array_merge(
array_combine(
array_map('spl_object_hash', $this->products->toArray()),
$this->products->toArray()
),
array_combine(
array_map('spl_object_hash', $products),
$products->toArray()
)
)
);
}
You might want to use the product id instead of spl_object_hash as 2 products with the same id, but created as separate entities - e.g. one through findBy() in Doctrine and one manually created with new Product() - will be recognized as 2 distinct products and might cause another insert-attempt.
Since you replace the original PersistentCollection holding your previously fetched products with a new ArrayCollection this might still result in unneeded queries or yield unexpected results when flushing the EntityManager, though. Not to mention, that this approach might be harder to read than explicitly calling addElement/removeElement on the original Collection instead.
I would approach it by creating my own collection class that extends Doctrine array collection class:
use Doctrine\Common\Collections\ArrayCollection;
class ProductCollection extends ArrayCollection
{
}
In the entity itself you would initialise it in the __constructor:
public function __construct()
{
$this->products = new ProductCollection();
}
Here, Doctrine will you use your collection class for product results. After this you could add your own function to deal with your special merge, perhaps something:
public function mergeProducts(ProductCollection $products): ProductCollection
{
$result = new ProductCollection();
foreach($products as $product) {
$add = true;
foreach($this->getIterator() as $p) {
if($product->getId() === $p->getId()) {
$result->add($product);
$add = false;
}
}
if($add) {
$result->add($product);
}
}
return $result;
}
It will return a brand new product collection, that you can replace your other collection in the entity. However, if the entity is attached and under doctrine control, this will render SQL at the other end, if you want to play with the entity without risking database updates you need to detach the entity:
$entityManager->detach($productEntity);
Hopes this helps

Set Query Builder conditions to Model in Laravel 5.1

First I have to say that I tried to find solution, and i didn't.
Basic question:
$Br = new BrandTop;
dd( $Br->limit(10)->get() ); // Will return 10 rows
and
$Br = new BrandTop;
$Br->limit(10);
dd( $Br->get() ); // Will return all rows.
So, the basic question - why? How can I set some limit for Model, but still work with it, for example set (or not set) some where or order depends on other variables.
Advanced question:
I want to use Model like this:
class BrandTop extends Model
{
public function withBrand() {
return $this->leftJoin('brand', 'brand.id' , '=', 'brandtop.brand_id');
}
public function forType($type) // there is much more conditions for type
{
return $this->where(['type' => $type]);
}
// main function
public function forSunglasses($limit = 0, $logo = false)
{
if ($logo)
$this->where(['menu_logo' => 1])->orderBy('total_sales', 'desc');
if ($limit)
$this->limit($limit);
return $this->forType('sunglasses')->withBrand();
// But there goes Error, because forType() return Builder object, and it has no withBrand() method
}
}
So, there is much more conditions, and it's much easier to set all conditions in separate methods. But how?
Model vs Builder
The thing to understand here is the difference between the Model object and the underlying Builder (query builder) object.
The statement $Br = new BrandTop; will create a new instance of a Model, and assign it to the $Br variable. Next, the $Br->limit(10) statement will create a new instance of a Builder object for the brand_tops table, with a limit of 10 applied.
In your first example, by doing $Br->limit(10)->get(), you're calling get() on the Builder that has your limit applied.
In your second example, your individual $Br->limit(10) creates the new Builder instance, but never uses it for anything. The next statement, $Br->get(), creates another new Builder instance without any constraints, so it retrieves all the records.
To be able to build up your query, you need to assign your Builder instance to a variable, and continue to modify that instance before finally calling get(). For example, to get your second example to work:
$query = BrandTop::query();
$query->limit(10);
$query->where(/*conditions*/);
dd($query->get());
Query Scopes
In relation to the second part of your question, you probably want to look into query scopes.
class BrandTop extends Model
{
// renamed to "JoinBrand" instead of "WithBrand", as "with" would imply
// an eager loaded relationship vs a joined table
public function scopeJoinBrand($query)
{
return $query->leftJoin('brand', 'brand.id' , '=', 'brandtop.brand_id');
}
// got rid of "for" prefix
public function scopeType($query, $type)
{
return $query->where('type', $type);
}
// got rid of "for" prefix
public function scopeSunglasses($query, $limit = 0, $logo = false)
{
if ($logo)
$query->where(['menu_logo' => 1])->orderBy('total_sales', 'desc');
if ($limit)
$query->limit($limit);
return $query->type('sunglasses')->joinBrand();
}
}
With the above model, your code would look something like:
dd(BrandTop::sunglasses()->get());
// or, more verbosely:
$query = BrandTop::query();
$query->sunglasses(); // $query already an object, no need to reassign it to itself
dd($query->get());

How to manually create a new empty Eloquent Collection in Laravel 4

How do we create a new Eloquent Collection in Laravel 4, without using Query Builder?
There is a newCollection() method which can be overridden by that doesn't really do job because that is only being used when we are querying a set result.
I was thinking of building an empty Collection, then fill it with Eloquent objects. The reason I'm not using array is because I like Eloquent Collections methods such as contains.
If there are other alternatives, I would love to hear them out.
It's not really Eloquent, to add an Eloquent model to your collection you have some options:
In Laravel 5 you can benefit from a helper
$c = collect(new Post);
or
$c = collect();
$c->add(new Post);
OLD Laravel 4 ANSWER
$c = new \Illuminate\Database\Eloquent\Collection;
And then you can
$c->add(new Post);
Or you could use make:
$c = Collection::make(new Post);
As of Laravel 5. I use the global function collect()
$collection = collect([]); // initialize an empty array [] inside to start empty collection
this syntax is very clean and you can also add offsets if you don't want the numeric index, like so:
$collection->offsetSet('foo', $foo_data); // similar to add function but with
$collection->offsetSet('bar', $bar_data); // an assigned index
I've actually found that using newCollection() is more future proof....
Example:
$collection = (new Post)->newCollection();
That way, if you decide to create your own collection class for your model (like I have done several times) at a later stage, it's much easier to refactor your code, as you just override the newCollection() function in your model
Laravel >= 5.5
This may not be related to the original question, but since it's one of the first link in google search, i find this helpful for those like me, who are looking for how to create empty collection.
If you want to manually create a new empty collection, you can use the collect helper method like this:
$new_empty_collection = collect();
You can find this helper in Illuminate\Support\helpers.php
snippet:
if (! function_exists('collect')) {
/**
* Create a collection from the given value.
*
* #param mixed $value
* #return \Illuminate\Support\Collection
*/
function collect($value = null)
{
return new Collection($value);
}
}
Just to add on to the accepted answer, you can also create an alias in config/app.php
'aliases' => array(
...
'Collection' => Illuminate\Database\Eloquent\Collection::class,
Then you simply need to do
$c = new Collection;
In Laravel 5 and Laravel 6 you can resolve the Illuminate\Database\Eloquent\Collection class out of the service container and then add models into it.
$eloquentCollection = resolve(Illuminate\Database\Eloquent\Collection::class);
// or app(Illuminate\Database\Eloquent\Collection::class). Whatever you prefer, app() and resolve() do the same thing.
$eloquentCollection->push(User::first());
For more information about understanding resolving objects out of the service container in laravel take a look here:
https://laravel.com/docs/5.7/container#resolving
I am using this way :
$coll = new Collection();
$coll->name = 'name';
$coll->value = 'value';
$coll->description = 'description';
and using it as normal Collection
dd($coll->name);
It is better to use the Injection Pattern and after $this->collection->make([]) than new Collection
use Illuminate\Support\Collection;
...
// Inside of a clase.
...
public function __construct(Collection $collection){
$this->collection = $collection;
}
public function getResults(){
...
$results = $this->collection->make([]);
...
}
What worked for me was to name the use namespace and instantiate it directly:
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
# Usage
$this->latest_posts = new EloquentCollection();
Allowed me to merge two data subsets of eloquent collection results, this maintains the relationships - a regular collection (collect()) loses relationship and probably some more metadata.
$limit = 5;
$this->latest_posts = new EloquentCollection();
$pinned_posts = PinnedPostReference::where('category', $category)->get();
if($pinned_posts->count() > 0) {
foreach($pinned_posts as $ppost) {
$this->latest_posts->push($ppost->post);
}
}
# Another Eloquent result set ($regular_posts)
foreach($regular_posts as $regular_post) {
$this->latest_posts->push($regular_post);
}

Calling model function in view codeigniter

I am new to MVC, I am porting a project written in non-MVC style to MVC, but I am stuck on a problem where it is necessary to call Model function in View.
Scenario:
Table1 - Products:
contains product_id, product_name etc. and for each product there can be multiple versions.
Table2 - Versions:
contains version_id, version_name, ... , product_id etc.
Now in the View I am displaying products and under each product heading I have to display version list of that product, in non-MVC style it was pretty simple, I can use the following code snippet in View:
foreach ($product as $row)
{
echo $row['product_name'];
if ($main->getVersionList($vresult,$row["product_id"]))
{
foreach ($vresult as $vrow)
{
echo $vrow['version_name'];
}
}
}
Now, I can pass Product array from controller to view but what about each Version array which needs to be generated corresponding to each product?
Update:
This is my final working solution (used a map), in controller:
$this->load->model ( 'product_mod' );
$data ['products'] = $this->product_mod->getProductList ();
$data ['versions'] = array ();
foreach ( $data ['products'] as $product )
{
$data ['versions'] [$product['product_id']] = $this->product_mod->getVersionList ( $product['product_id'] );
}
MVC or not to MVC
The first thing I should note is that It is impossible to write classical MVC in PHP. In fact the MVC-like PHP frameworks such as CodeIgniter or Yii implements sort of MVP in which:
view is passive and unaware of model
presenter (controller) changes state of model, reads information and passes it to view
Credits to tereško
CodeIgniter Approach
However, particularly in CodeIgniter, you have 3 steps:
Create a Model to query through the database and return the data (as an array or object)
Create a Controller to load and fetch the result from the Model (a method of the Model), and pass the returned data to the view
Create a View and use PHP loops to echo the result out, build the HTML.
Getting all together
Considering the above approach, you need to fetch the result from the database in your Model:
application/models/product.php
class Product extends CI_Model
{
public function get_product($product_id)
{
$this->db->select('*')->from('products');
$this->db->where('product_id', $product_id);
$this->db->join('versions', 'versions.product_id = products.product_id');
$query=$this->db->get();
return $query->first_row('array');
}
}
Then fetch and pass the result within the Controller:
application/controllers/products.php
class Products extends CI_Controller
{
public function view($product_id)
{
$this->load->model('product');
// Fetch the result from the database
$data['product'] = $this->product->get_product($product_id);
// Pass the result to the view
$this->load->view('product_view', $data);
}
}
Finally, use the returned data in the view, to generate the list:
application/views/product_view.php
// Use $product to display the product.
print_r($product);
You should do mysql query in your model, for example products_model (don't forget to load it)
Example Query : This is just select * from products
public function All_Products()
{
$this->db->select('');
$result = $this->db->get('products')->result_array();
return $result;
}
So as I see , you are foreach guy like me, rather than using mass queries.
In your controller you could load $products my your model.
$products = $this->products_model->All_Products();
My solution is creating a new array then putting new values in it.Also you need to getVersionList function in your model.
$newArray = array ();
foreach ( $products as $values ) {
$version = $this->products_model->getVersionList($values['product_id']);
$tmp = array (
'product_id' => $values ['product_id'],
'product_name' => $values ['product_name'],
'version' => $version
);
array_push ( $newArray, $tmp );
}
So add your new array to $data you could have them in your view file.
$data ['products'] = $newArray;
Sorry I didnt figure out all your queries but I think its better to teach how to catch fish than giving a fish.
Using the below code you can call the model's method in the view file. It's worked for me and is so simple also.
$CI =& get_instance();
$CI->model_name->method_name();

Best way to store this data in a variable?

Some quick background info: I'm coding up a site which matches books to the classes they're required for.
I have two pieces of data that I need to represent in my code-- which books go with which classes, and the data (titles, authors, pricing, etc.) on these books.
Currently I represent this all with two arrays: $classArray, and $Books_data.
The advantage of this approach over a one-variable approach is that I don't repeat myself-- if a Book is required multiple times for different classes, only the ISBN needs to be stored in the $classArray and I can store the data in the $Books_array. This advantage is especially poignant because I have to query the pricing data from API's on the fly. If I only had a $classBooksArray, I'd have to loop the query responses into a big array, repeating myself (seemingly) unnecessarily.
The disadvantage of this approach is that these variables follow each other almost everywhere like Siamese twins. Nearly every function that needs one, needs the other. And my coding spidey sense tells me it might be unnecessary.
So, what would be the best way to store this data? Two arrays, one array, or some other approach I haven't mentioned (e.g. passing by reference).
Why not an associative which has two keys - one pointing to an array of classes, one to store Books
data?
$allData = array("classes" => &$classArray, "books" => &$Books_data);
That way you're only passing around 1 variable (less clutter) but retain all the benefits of separate data stores for books and classes.
Though, to be honest, if it's just TWO sets of data, so IMHO your spidey sense is wrong - passing both as separate parameters is perfectly fine. Once you get into a set of siamise sextuplet variables, then the above approach starts to actually bring benefits.
A multidimensional array.
$collection = array(
'classes' => array( /* Contents of $classArray */),
'books' => array( /* Contents of $Books_data */)
);
function some_function($collection) {
// looping over books
foreach ($collection['books'] as $book) {
// yadda yadda
}
}
Or better yet a class:
/* Define */
class Collection {
private $books;
private $classes;
public function __construct($classes = array(), $books = array()) {
$this->books = $books;
$this->classes = $classes;
}
public function addBook($book) {
$this->books[] = $book;
}
public function addClass($class) {
$this->classes[] = $class;
}
public function get_classes() {
return $this->classes;
}
public function get_books() {
return $this->books;
}
}
function some_function(Collection $col) {
// looping over books
foreach ($col->get_books as $book) {
// yadda yadda
}
}
/* Usage */
$collection = new Collection(); // you also could pass classes and books in the
// constructor.
$collection->addBook($book);
somefunction($collection);
If your datas were a database, your current proposal being a normal form would be canonical. The two variables would just become tables and ISBN a foreign key to books table (with a third table as a class has several books). I would probably stick with the current implementation as it will be very easy to transform to database when that will be necessary (and it usually happens faster than expected).
EDIT: a comment, say it is already in a database... what I do not understand is why you would want to store a full database in memory instead of just keeping what is necessary for the current task.
Let's be OO and put the arrays into an object. Define a class with those properties, load it up, and call its methods. Or, if you must have other functions operating with the object's data, pass the instance around. Disallow direct access to the data, but provide methods for extracting the salient info.
class book_class_association {
protected $books_to_classes = array();
protected $classes_to_books = array();
function __construct() {
$this->books_to_classes = array(
'mathbook1' => array('math'),
'mathbook2' => array('math'),
);
$this->classes_to_books = array(
'math' => array('mathbook1', 'mathbook2'),
);
}
function classes_for_book( $class_name ) {
return $this->books_to_classes[$class_name];
}
function books_for_class( $class_name ) {
return $this->classes_to_books[$class_name];
}
}
Is there a reason you are not storing this data in a database and then querying the database? It is a many to many relationship, and you would need 3 tables - class , book, and class_book_intersection.
So for example, you ui could have "select class" from a list, where the list is derived from the rows in class.
Then if the class id selected is 123. The query would then be something like:
Select book.title, book.cost
from book
inner join class_book_intersection
on
class_book_intersection.classid = 123 and
class_book_intersection.bookid = book.bookid

Categories