I've got a number of product classes which extend a parent product class. Each of these implements its own version of a PRODUCT_ID const. A method in a customer object will pull the PRODUCT_ID 's from the DB. I'd then like to instanciate the relevant product object and add it to an array.
Any ideas how I can dynamically find out which product object has the specific PRODUCT_ID const?
Sorry if that is a bit confusing!
The only way to do this would be to first loop through all of the declared classes, then check if they're a subclass of the parent product and finally to check if the PRODUCT_ID constant is defined and equal to the product you're looking for.
Here an example:
<?php
class Product
{
}
class Stack extends Product
{
const PRODUCT_ID = 1;
}
class Overflow extends Product
{
const PRODUCT_ID = 2;
}
function getSubclasses($parentClassName)
{
$classes = array();
foreach (get_declared_classes() as $className)
{
if (is_subclass_of($className, $parentClassName))
$classes[] = $className;
}
return $classes;
}
function find_product($product_id)
{
foreach ( get_declared_classes() as $class )
if ( is_subclass_of($class, 'Product') )
if ( constant($class . '::PRODUCT_ID') == $product_id )
return $class;
}
echo find_product(1); // Outputs "Stack"
Create class with field $product_id and accessors:
class Product
{
protected $product_id;
public function __construct($id)
{
$this->id = $id;
}
public function getProductId()
{
return $this->product_id;
}
public function setProductId($product_id)
{
$this->product_id = $product_id;
}
}
And then:
$t = new Product(1254899);
Each PRODUCT_ID value corresponds to a unique product class name, right ? So you can't have a PRODUCT_ID without a class associated ? So, in this case, you know how many product class you have.
If everything is true, I think the best solution is to create an array like this
$pairs = array(
PRODUCT_ID_VALUE_ONE => 'classNameOne',
PRODUCT_ID_VALUE_TWO => 'classNameTwo',
// [...]
);
Then, when you want to retrieve the class name associated with a specific PRODUCT_ID, you have to do
$className = $pairs[ $myProductId ];
You have two solutions to construct that array:
build it manually,
build it using automatically PHP reflexion (as shown by Francois)
If you have few class and if they don't change a lot, the first solution is better: its faster, easy to read/change values, easy to understand the code, and you have a better control over the array values => you set them manually so you won't have wrong values.
The problem with the first solution is you may have inconsistencies between the array and all the products class: if you change a PRODUCT_ID value into a class, you'll have to change it manually into the array.
The second solution is better if you have a lot of classes or if they change often. However, PHP reflexion may slow down your application. To limit that side effect, you can:
build the array once and only once,
build the array once only when needed,
You can create a method like getProductClassById which will:
check the existence of the array above,
created the array if necessary,
search in that array,
return the value,
Hope this will help,
Cheers
Related
I have the following situation, Im trying to modify the price of products displayed in a platform.
Everything works ok for only 1 product (eg: product view) but I dont know what I have to do in order to modify the price of each product in an eloquent collection.
this is the code in my app:
ProductRepository.php:
public function CalcPrice($product){
$x = $product->price; //eg 5
$y = 4;
$amount= $x + $y;
return $amount;
}
For the details view of each product inside ProductController I have the following code and everything works perfect:
public function details($id){
$product = $this->product->getProductById($id);
$productprice = $this->product->getCalcPrice($product = $product);
return view('products.view',compact('product','productprice'))
}
On the other hand, my idea is to use the code contained in ProductRepository.php function CalcPrice in a collection.
My main doubt is what do I have to do, because in a collection probably I can have a variable $category in order to retrieve all products in a category, but I will not have a variable for each $product (for eg: a $productid like in details).
What can I do in order to eg:
modify each product price contained in a collection of a category
using CalcPrice function code?
eg: of code:
productrepository.php
public function AllProductsInCategory($catid)
{
return App\Product::where('categoryid', $catid)
->get();
}
but each product displaying their ($product->price + 4) as CalcPrice performs. thanks!.
You can achieve this by defining an attribute accessor on model and append it to model. This way it would be available for you on each instance like its other attributes.
As Taylor Otwell mentioned here, "This is intentional and for performance reasons." However there is an easy way to achieve this, say you have model named Product;
class Product extends Eloquent {
protected $appends = array('calc_price');
public function getCalcPriceAttribute()
{
//if you want to call your method for some reason
return $this->getCalcPrice($this);
// Otherwise more clean way would be something like this
// return $this->price + 4 // un-comment if you don't want to call getCalcPrice() method
}
}
Now you can access calculated price on each $product by simply calling $product->calc_price.
To keep things short, the application itself is really simple, having only three tables.
Product:
id
name
Attribute
id
name
slug
Product Attribute
id
attribute_id
product_id
value
As you may guess the attributes may be totally random, with any content and there might be any number of them. Every product has the same set of global attributes assigned. Some are empty, some are filled.
What I want to do is displaying them in a standard Gridview, the same way as if it was a normal model, with dynamic-preassigned columns and values. Basically speaking - the attributes should serve as the columns.
I've tried to extend the main Product Model and use ActiveDataProvider class on it, but no avail, I'm getting the custom attributes values repeated for each row, as if something was missing. Here's the class I've created basing on another question from SO.
namespace common\models;
use Yii;
use common\models\Attribute;
use common\models\Product;
class ProductDynamic extends Product {
public function init() {
parent::init();
$attrExists = ProductAttribute::find()->select(['slug','value'])->joinWith(['attributeModel'])->all();
if ($attrExists) {
foreach ($attrExists as $at) {
$this->dynamicFields[$at['slug']] = $at['value'];
}
}
}
private $dynamicFields = [];
public function __get($name) {
if (array_key_exists($name, $this->dynamicFields))
return $this->dynamicFields[$name];
else
return parent::__get($name);
}
public function __set($name, $value) {
if (array_key_exists($name, $this->dynamicFields))
$this->dynamicFields[$name] = $value;
else
parent::__set($name, $value);
}
public function rules() {
return array_merge(parent::rules, $this->dynamicRules);
}
}
My question is - how would you do it? How to assign properties to a model so they act as a standard database-loaded properties usable by ActiveDataProvider? I think I need to retrieve the ProductAttributes some other way so they are not repeated. See below.
Ok, i think i got your idea...
So, I think you could generate an attribute in the model of Product (we will call it ProductAttributeArr) which would be an array, then retrieve the attributes from the Attribute table in the database according to Product Attribute table and store them in ProductAttributeArr.
Then you could create a function that dynamically generates the parameters for the GridView base on the content of ProductAttributeArr. This way it should work.
Answering it myself since there's no feedback and after some more digging i've found the quickest, but maybe not the nicest solution.
Since the properties in main Product model are added thanks to the ProductDynamic class I have added the following in Product to assign correct column values. For sure, there must be another, easier way, if you know any feel free to comment.
public function afterFind() {
$attrs = ProductAttribute::find()->select(['slug', 'value'])->joinWith(['attributeModel'])->where(['product_id' => $this->id])->all();
foreach ($attrs as $a) {
$this->{$a['slug']} = $a['value'];
}
}
What I am trying to do is write a "search" class that can search for a list of products and store them in an array.
I already have a "product" class that can be used to get the details of a specific product.
Here is my code:
class Product {
public $name;
public $price;
public $description;
public function getProductById ($id) {
$sql = 'SELECT name, price, description FROM product WHERE id = ' . $id;
$row = /* MySQL functions here to execute SQL statement and get a matching row */
$this->name = $row['name'];
$this->price = $row['price'];
$this->description = $row['description'];
return TRUE;
}
}
class Search {
public $results;
public $totalResults;
function __construct() {
$this->results = array ();
$this->totalResults = 0;
}
public function doSearch ($name) {
$sql = 'SELECT id FROM product WHERE name LIKE "%' . $name . '%"';
$rows = /* MySQL functions here to execute SQL statement and get a list of matching product ID's */
foreach ($rows as $row) {
$product = new Product;
$product->getProductById ($row['productid']);
$this->results[] = $product;
}
return TRUE;
}
}
$search = new Search;
$search->doSearch ('Fresh Flowers');
The problem with the above is that every matching record in the doSearch method will execute a query in the getProductById method. If there are 100 matching products, there will be 100 individual queries carried out in the Product class.
However, if I get the products directly in the doSearch method using a single query, this will then bypass the Product class altogether.
When a "product" is an object, what's the most appropriate way to write a search class that can return a list of "product" objects without the overhead of what I'm doing above?
Add a constructor to the Product class which takes name, price and description as parameters (or an assoziative array), to populate the object with the necessary values, decoupled of the database query.
Within doSearch, you can then create a SELECT which not only gets the ID but all relevant fields from the products table, and create the populated product objects immediately with new Product($name, $price, $description) or new Product($row), without calling getProductById for each product.
Create a class that populates instances of Product with data from the database.
The class can then create one or multiple instances of the Product class depending on how much data is being fetched.
Conclusion: Extract the getProductById from your Product class and put it somewhere else. It is a specialised method that only populates one instance.
Just grab what you want in the first place.
public function doSearch ($name) {
$sql = 'SELECT id, name, price, description FROM product
WHERE name LIKE "%' . $name . '%"';
// now just return the array
}
Or use PDO to return result sets as objects.
$result->setFetchMode(PDO::FETCH_INTO, new animals);
as discussed here: How can I simply return objects in PDO?
Your Product class shouldn't know anything about a database. It should contain the values representing a product, nothing more. Extract all stuff dealing with the database out of this class.
Searching for products is one way to access a list of products. Your next class should be a list of products then. Only when accessing one single product you'd not have to deal with a list, but this is probably less often than you think.
Ok, you have the product and the list of products, you now can go one step forward and add database access. You need a class that deals with giving you both one product (when searching by id) and lists of products (when searching by some text or other stuff). Only this class allows you to deal with the queries needed to access the database. The result sets of each query may directly be used inside the "list of products" class, probably by inheriting all the stuff that is defined in the general "list of products" class and adding dealing with database results.
So in the end you'll end up having:
Product -> ListOfProducts -> ProductDatabase -> DatabaseAccessLayer
I'm looking for the most effective way to get configurable product attribute values on product listing pages, for example color values for a product (from assigned simple products)
Currently looking into utilising catalog_product_flat_n table for this purpose, but perhaps there is an easier or more correct approach to do this? I'm trying to avoid using
$product->getTypeInstance()->getConfigurableAttributesAsArray()
on every product, as this will be very slow
Thanks
I had the same issue, so I created my own resource model for getting data from flat tables, look into code below
<?php
class NameSpace_ModuleName_Model_Resource_Colors extends
Mage_Core_Model_Resource_Db_Abstract
{
protected $_storeId;
protected function _construct()
{
$this->_init('catalog/product_flat', 'entity_id');
$this->_storeId = (int)Mage::app()->getStore()->getId();
}
public function getData($entityId)
{
$resource = Mage::getSingleton('core/resource');
$select = $resource->getConnection('core_read')->select();
$select
->from($this->getTable(array('catalog/product_flat', $this->_storeId)), '*')
->where('entity_id = :entity_id');
$result = $resource->getConnection('core_read')->fetchAll($select, array('entity_id' => $entityId));
return $result;
}
}
Hope it will be helpful for you
I have a custom class object in PHP named product:
final class product
{
public $id;
public $Name;
public $ProductType;
public $Category;
public $Description;
public $ProductCode;
}
When passing an object of this class to my Data Access Layer I need to cast the object passed into a type of the product class so I can speak to the properties within that function. Since type casting in PHP works only with basic types what is the best solution to cast that passed object?
final class productDAL
{
public function GetItem($id)
{
$mySqlConnection = mysql_connect('localhost', 'username', 'password');
if (!$mySqlConnection) { trigger_error('Cannot connect to MySql Server!'); return; }
mysql_select_db('databaseName');
$rs = mysql_query("SELECT * FROM tblproduct WHERE ID='$id';");
$returnObject = mysql_fetch_object($rs, 'product');
return $returnObject;
}
public function SaveItem($objectToSave, $newProduct = false)
{
$productObject = new product();
$productObject = $objectToSave;
echo($objectToSave->Name);
$objectToSave->ID;
}
}
Right now I am creating a new object cast as a type of product and then setting it equal to the object passed to the function. Is there a better way of accomplishing this task? Am I going about the wrong way?
EDITED FOR CLARITY - ADD FULL PRODCUTDAL CLASS
You don't need to cast the object, you can just use it as if it was a product.
$name = $objectToSave->Name;
I´m not sure what you are trying to achieve, but if $objectToSave is already of class product:
You can simply call $objectToSave->SaveItem() (assuming SaveItem() is part of the product class) and access it´s properties in the function like $this->Name, etc.;
In your code $productObject and $objectToSave will hold a reference to the same object.
Type casts in PHP are done like this:
$converted = (type) $from;
Note, that this won't work if the object types are not compatible (if for example $form happens to be a string or object of mismatching type).
But usual solution (called Active Record pattern, present for example in Zend Framework) is to have a base class for a database item called Row. Individual items (for example the class product from your sample) then inherit from this class.
Typical ZF scenario:
$table = new Product_Table();
$product = $table->find($productId); // load the product with $productId from DB
$product->someProperty = $newPropertyValue;
$product->Save(); // UPDATE the database
Which is IMO much better than your solution.
EDIT:
You can't cast between two unrelated objects, it is not possible.
If you want to use the DAL like this, skip the "product" object and go for simple associative array. You can enumerate over its members with foreach, unlike object's properties (you could use reflection, but that's overkill).
My recommendation: Go for the Active Record pattern (it is easy to implement with magic methods). It will save you a lot of trouble.
Currently, you are creating a new Product, then discarding it immediately (as its reference is replaced by $objectToSave.) You will need to copy its properties one by one, I regret.
foreach (get_object_vars($objectToSave) as $key => $value)
{
$product->$key = $value;
}
(If the properties of $objectToSave are private, you will need to a expose a method to_array() that calls get_object_vars($this).)