I'm just getting started with dependency injection and I have immediately hit a problem: I have two classes that depend on each other.
The classes are Basket and Shipping.
In my Basket class I have the following relevant methods:
public function totalShipping()
{
return $this->_shipping->rate();
}
public function grandTotal()
{
return $this->totalProductsPrice() + $this->totalShipping();
}
public function totalWeight()
{
$weight = 0;
$products = $this->listProducts();
foreach ($products as $product) {
$weight += $product['product_weight'];
}
return ($weight == '') ? 0 : $weight;
}
$this->_shipping is an instance of the Shipping class
In my Shipping class I have the following relevant methods:
public function rate()
{
if (isset($_SESSION['shipping']['method_id'])) {
$methodId = $_SESSION['shipping']['method_id'];
return $this->_rates[$methodId]['Shipping Price'];
}
// Method not set
return NULL;
}
// Available Methods depend on country and the total weight of products added to the customer's basket. E.g. USA and over 10kg
public function listAvailableMethods()
{
$rates = array();
if (isset($_SESSION['customer']['shipping_address']['country_code'])) {
foreach ($this->_rates as $method_id => $rate) {
if (($_SESSION['customer']['shipping_address']['country_code'] == $rate['Country']) && ($this->_basket->totalWeight() > $rate['Weight From']) && ($this->_basket->totalWeight() < $rate['Weight To'])) {
$rates[$method_id] = $rate;
}
}
}
return $rates;
}
$this->_basket is an instance of the Basket class.
I am totally clueless as to how to resolve this circular dependency. Thank you for your help in advance.
Update
In my Shipping Class I also have this method:
public function setMethod($method_id)
{
// A check to make sure that the method_id is one of the provided methods
if ( !array_key_exists($method_id, $this->listAvailableMethods()) ) return false;
$_SESSION['shipping'] = array(
'method_id' => $method_id
);
}
I have ended up renaming Shipping to Shipping_Methods and I have created a new class called Customer_Shipping_Methods. Essentially Customer_Shipping_Methods could be part of the Basket class but I'd rather keep it separate.
#RyanLaBarre was totally right. I was essentially mixing methods that should have been in the Basket Class with methods in my Shipping_Methods class. Shipping_Methods should have only contained general shipping data methods which were not specific to the current session.
I think what threw me, was that Shipping_Methods sources its data from a csv file rather than a database table. Once I started seeing Shipping_Methods as just another table, it all clicked in my mind.
#rdlowrey that is very good advice. I will be putting my session globals into my controllers immediately!
Related
I am configuring a Woocommerce store, and I want to allow users from some locations to browse my site, but not allow them to buy: if they try, there should be a message asking them to email us for a custom shipping quote, and the payment methods should be removed.
I have configured two shipping zones: the locations that we do serve and every other one, and I have left the latter without any shipping methods. This way, if an user enters a shipping address we cannot serve, there won’t be any shipping methods available.
Now, I can use these filters to run code when there are no shipping methods available:
woocommerce_cart_no_shipping_available_html
woocommerce_no_shipping_available_html
And I can filter out the available payment gateways, even removing all of them, using the woocommerce_available_payment_gateways filter...
...but how do I call one of them from the other?
From what I've seen, the WC_Payment_Gateways class doesn't seem to have any method to manipulate by hand the available payment methods: all you can do is to load them (get_available_payment_gateways()), but
I get the distinct impression that you are not supposed to touch that object "by hand", and you are supposed to do that in the appropriate filter (woocommerce_available_payment_gateways). So, again, how do I trigger that filter's code from the previous filter?
Okay, found an answer in the Wordpress Stackexchange and followed it:
https://wordpress.stackexchange.com/questions/213219/using-variable-from-one-filter-in-another-filter
Basically, I created a singleton object to keep global state. The code in woocommerce_no_shipping_available_html writes to it saying "there are no shipping methods", and then the code in woocommerce_available_payment_gateways checks it and, if that's the case, it removes all payment gateways.
It's something like this:
if( ! class_exists('Vuelamedia_EstadoGlobal')) {
class Vuelamedia_EstadoGlobal {
private $hayMetodoDeEnvio;
private static $instance;
private function __construct() {
$this->hayMetodoDeEnvio=true;
}
public static function getInstance() {
if(is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
public function guardaValor($valor=TRUE) {
$this->hayMetodoDeEnvio = $valor;
}
public function leeSiHayMetodoDeEnvio() {
return $this->hayMetodoDeEnvio;
}
}
}
add_filter("woocommerce_no_shipping_available_html", "quitar_formas_de_pago", 20);
add_filter("woocommerce_cart_no_shipping_available_html", "quitar_formas_de_pago", 20);
function quitar_formas_de_pago($texto) {
Vuelamedia_EstadoGlobal::getInstance()->guardaValor(false);
return $texto;
}
add_filter("woocommerce_available_payment_gateways", "filtrar_formas_de_pago");
function filtrar_formas_de_pago($available_gateways) {
if( is_admin() ) { return $available_gateways; }
$metodo = Vuelamedia_EstadoGlobal::getInstance()->leeSiHayMetodoDeEnvio();
if (!$metodo) {
return array();
}
else { return $available_gateways; }
}
Framework: Laravel 5.1
Background: I have a Product and a ProductVariation model, which both share a ImplementsProductAttributes trait. If a Product is variable, than the product should contain at least a single purchasable ProductVariation to be purchasable, rest of the logic would be obvious within the code sample from the trait. Functionality works as is.
public function getPurchasableAttribute()
{
if ($this instanceof \App\Product)
{
if ($this->type == 'variable')
{
foreach ($this->variations as $variation)
{
if ($variation->purchasable)
return true;
}
return false;
}
else return $this->isItemPurchasable();
}
else
{
return $this->isItemPurchasable();
}
}
public function isItemPurchasable()
{
if($this->regular_price > 0 AND $this->published)
{
if ($this->manage_stock)
{
return $this->stock_quantity > 0;
}
else return $this->in_stock;
}
else return false;
}
Pros:
I make use of laravels attributes,
Readable/maintainable code,
Cons:
Redundant eager loading on the variations (which could be a problem
if this was supposed to be used on an API)
Redundant SQL queries (I could implement a single query to get a
count in the end, and decide based on that if the product itself is
purchasable, but instead I am loading all variations -- at least
until i get one with purchasable=true --)
Question: Should I keep this implementation or should I improve by getting rid of pulling relationships and making a simple SQL query to get the purchasable variations?
Please elaborate on your answer as much as you can.
Edit: Also please let me know if you think any other pros/cons exist.
First of all, as per the concern of coding standard, you can get rid of this nested if-else structure in getPurchasableAttribute() method like this
public function getPurchasableAttribute()
{
if (!( $this instanceof \App\Product) || $this->type != 'variable')
{
return $this->isItemPurchasable();
}
foreach ($this->variations as $variation)
{
if ($variation->purchasable)
return true;
}
return false;
}
Can you please tell something more about $variation->purchasable
Is purchasable an eloquent relationship defined in $variation or an attribute of $variation.
So that we can talk something more on efficiency.
How to avoid decreasing of a product quantity when it's still in the "Process" and update when the order was shipped or delivered?
I've edited this part of code in orderdetail.php and add this $id_order_state != Configuration::get('PS_OS_PREPARATION') in the if statement. Yes, the quantity doesn't decrease when it's processed but when shipped it also doesn't decrease. please help I'm stuck in here.
protected function checkProductStock($product, $id_order_state)
{
if ($id_order_state != Configuration::get('PS_OS_CANCELED') && $id_order_state != Configuration::get('PS_OS_ERROR') && $id_order_state != Configuration::get('PS_OS_PREPARATION')) {
$update_quantity = true;
if (!StockAvailable::dependsOnStock($product['id_product'])) {
$update_quantity = StockAvailable::updateQuantity($product['id_product'], $product['id_product_attribute'], -(int)$product['cart_quantity']);
}
if ($update_quantity) {
$product['stock_quantity'] -= $product['cart_quantity'];
}
if ($product['stock_quantity'] < 0 && Configuration::get('PS_STOCK_MANAGEMENT')) {
$this->outOfStock = true;
}
Product::updateDefaultAttribute($product['id_product']);
}
}
An OrderDetail object is only created once for each Order and will not be updated after that even when you change the Order State. So when your OrderDetail object is created, with your modification it will not update the stock because it doesn't have the right state. And when you later change the Order State the method checkProductStock will never be called again.
You can create a custom module hooked on actionOrderStatusPostUpdate (witch is triggered inside changeIdOrderState() method of class OrderHistory(). In your module you will copy the checkProductStock() method and call it from the hook if the state is "shipped".
EDIT
If you want to add it directly in core:
Edit classes/order/OrderHistory.php
In method changeIdOrderState() change the last lines:
// executes hook
Hook::exec('actionOrderStatusPostUpdate', array('newOrderStatus' => $new_os, 'id_order' => (int)$order->id, ), null, false, true, false, $order->id_shop);
// Here change 4 to the desired id_order_state
if ($new_order_state == 4)
{
$virtual_products = $order->getVirtualProducts();
foreach ($virtual_products as $virtual_product)
{
$this->checkProductStock($virtual_product['product_id'], $new_order_state);
}
}
ShopUrl::resetMainDomainCache();
}
After that add a new method in this class:
protected function checkProductStock($product, $id_order_state)
{
$update_quantity = true;
if (!StockAvailable::dependsOnStock($product['product_id']))
{
StockAvailable::updateQuantity($product['product_id'], $product['product_attribute_id'], -(int)$product['product_quantity']);
}
Product::updateDefaultAttribute($product['product_id']);
}
This code has not been tested.
I advise you to do it in an override:
Create a new file in /overrides/classes/order/OrderHistory.php witch contains those two methods and change the class definition to class OrderHistory extends OrderHistoryCore {, you will have to delete /cache/class_index.php after adding this file.
for my question on how to use OOP in a beneficial way I assume as an example a BASKET to which its owner (Tom) having a certain ADDRESS (NY) can add ARTICLES (Bike, Car). Finally a BILL is printed containg all these information.
My problem is: How to handle collecting the information desired (here: owner, city, amount of items) from several objects? Because I think it is stupid to do this manually as done below (see 4.), isn't it? (even more since the amount of information increases in reality)
So what is the "clean way" for creating the bill / collecting the information needed in this example?
<?php
$a = new basket('Tom','NY');
$a->add_item("Bike",1.99);
$a->add_item("Car",2.99);
$b = new bill( $a );
$b->do_print();
1.
class basket {
private $owner = "";
private $addr = "";
private $articles = array();
function basket( $name, $city ) {
// Constructor
$this->owner = $name;
$this->addr = new addresse( $city );
}
function add_item( $name, $price ) {
$this->articles[] = new article( $name, $price );
}
function item_count() {
return count($this->articles);
}
function get_owner() {
return $this->owner;
}
function get_addr() {
return $this->addr;
}
}
2.
class addresse {
private $city;
function addresse( $city ) {
// Constructor
$this->city = $city;
}
function get_city() {
return $this->city;
}
}
3.
class article {
private $name = "";
private $price = "";
function article( $n, $p ) {
// Constructor
$this->name = $n;
$this->price = $p;
}
}
4.
class bill {
private $recipient = "";
private $city = "";
private $amount = "";
function bill( $basket_object ) {
$this->recipient = $basket_object->get_owner();
$this->city = $basket_object->get_addr()->get_city();
$this->amount = $basket_object->item_count();
}
function do_print () {
echo "Bill for " . $this->recipient . " living in " . $this->city . " for a total of " . $this->amount . " Items.";
}
}
If you do Tell Dont Ask, you would indeed add a render method to the bill to which you would pass an instance of BillRenderer. Bill would then tell BillRenderer how to render the Bill. This is in accordance with InformationExpert and High Cohesion principles that suggest methods to be on the objects with the most information to fulfill the task.
class Bill
{
…
public function renderAs(BillRenderer $billRenderer)
{
$billRenderer->setRecipient($this->owner);
$billRenderer->setAddress($this->address);
…
return $billRenderer->render();
}
}
BillRenderer (an interface) would then know the output format, e.g. you'd write concrete renderers for PlainText or HTML or PDF:
class TxtBillRenderer implements BillRenderer
{
…
public function render()
{
return sprintf('Invoice for %s, %s', $this->name, $this->address);
}
}
echo $bill->renderAs(new TxtBillRenderer);
If your Bill contains other objects, those would implement a renderAs method as well. The Bill would then pass the renderer down to these objects.
Both basket as well as bill could have a relation to a positions item - an object representing an ordered list of zero or more items with a count and price.
As such a list is an object of it's own it's easy to pass around:
$bill = new Bill($buyer, $address, $basket->getPositions());
However the printing of the bill should be done by the BillPrinter, because it's not the job of the bill to print itself:
$billPrinter = new BillPrinter($bill, $printerDevice);
$billPrinter->print();
First of all , in PHP5 the constructor it public function __construct(). What you are using there is the PHP4 way. And then ther eare other issues with your code:
instead of passing the name of the city to the Basket ( do you mean Cart ?), you should be creating the address object instance and passing it.
do not add items based on name an amount of money to the basket, instead add the whole instance of item, otherwise you will have a lot of problems when switching site language or currency.
the Articles (do you mean Items ?) should be created based on ID, not based on name. The reasons for that are the same as above + you will have issues with uniqueness. And then some of items might have lower price, when bought in combination. You need a way to safely identify them.
As for cleaning up the code there:
you should stop creating instance from given parameters in the constructor. While it is not always a bad thing, in your case you are making a mess there.
Bill should not be responsible for printing itself.
Something like :
class Basket
{
// -- other code
public function handleInvoice( Bill $invoice )
{
$invoice->chargeFor( $this->items );
$invoice->chargeTo( $this->account );
return $invoice->process();
}
}
.. then use it as
$cart = new Basket(..);
// some operation with it
$invoice = new Bill;
$cart->handleInvoice($invoice);
$printer = new PDFPrinter;
// OR new JpegPrinter; OR new FakePrinter OR anything else
$printer->print( $invoice );
This would give you an instance of Bill outside the class which then you can either print or send to someone.
Also , you might benefit from watching the willowing lecture:
Inheritance, Polymorphism, & Testing
Don't Look For Things!
Clean Code I: Arguments
I am trying to get the subtotal amount on checkout success page. It works good for registred users:
$_order = Mage::getModel('sales/order')->loadByIncrementId($this->getOrderId());
$amount = $_order->getData('subtotal');
$total = number_format($amount, 2);
But when the order is processed by a guest, $total is empty.
What can be done?
P.S.: I am using Magento 1.6.1 CE
Taken from Magento's app/code/core/Mage/Checkout/Block/Onepage.php:
if (!$this->isCustomerLoggedIn()) {
return $this->getQuote()->getShippingAddress();
} else {
return Mage::getModel('sales/quote_address');
}
I am 99% sure you can do exactly the same with getOrder() and with Mage_Checkout_Block_Success =)
Note: the isCustomerLoggedIn() method is defined at Mage_Checkout_Block_Onepage_Abstract which is not inherited by Mage_Checkout_Block_Success. So, you could simply use its implementation:
public function isCustomerLoggedIn()
{
return Mage::getSingleton('customer/session')->isLoggedIn();
}
E.g. your code now shall look like this:
if (!Mage::getSingleton('customer/session')->isLoggedIn()) {
$order = Mage::getSingleton('checkout/session')->getOrder();
} else {
$order = Mage::getModel('sales/order')->loadByIncrementId($this->getOrderId());
}
Sorry for saying non-sense things before...