Completed
Push — master ( c63cd8...c4c8fb )
by Claudio
10:23
created

WC_Product_Factory::get_product()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.7283

Importance

Changes 0
Metric Value
cc 5
nc 7
nop 2
dl 0
loc 26
ccs 9
cts 13
cp 0.6923
crap 5.7283
rs 9.1928
c 0
b 0
f 0
1
<?php
2
/**
3
 * Product Factory
4
 *
5
 * The WooCommerce product factory creating the right product object.
6
 *
7
 * @package WooCommerce/Classes
8
 * @version 3.0.0
9
 */
10
11
defined( 'ABSPATH' ) || exit;
12
13
/**
14
 * Product factory class.
15
 */
16
class WC_Product_Factory {
17
18
	/**
19
	 * Get a product.
20
	 *
21
	 * @param mixed $product_id WC_Product|WP_Post|int|bool $product Product instance, post instance, numeric or false to use global $post.
22
	 * @param array $deprecated Previously used to pass arguments to the factory, e.g. to force a type.
23
	 * @return WC_Product|bool Product object or false if the product cannot be loaded.
24
	 */
25 237
	public function get_product( $product_id = false, $deprecated = array() ) {
26 237
		$product_id = $this->get_product_id( $product_id );
27
28 237
		if ( ! $product_id ) {
29
			return false;
30
		}
31
32 237
		$product_type = $this->get_product_type( $product_id );
0 ignored issues
show
Bug introduced by
It seems like $product_id defined by $this->get_product_id($product_id) on line 26 can also be of type boolean; however, WC_Product_Factory::get_product_type() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
33
34
		// Backwards compatibility.
35 237
		if ( ! empty( $deprecated ) ) {
36
			wc_deprecated_argument( 'args', '3.0', 'Passing args to the product factory is deprecated. If you need to force a type, construct the product class directly.' );
37
38
			if ( isset( $deprecated['product_type'] ) ) {
39
				$product_type = $this->get_classname_from_product_type( $deprecated['product_type'] );
40
			}
41
		}
42
43 237
		$classname = $this->get_product_classname( $product_id, $product_type );
0 ignored issues
show
Security Bug introduced by
It seems like $product_type can also be of type false; however, WC_Product_Factory::get_product_classname() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
Bug introduced by
It seems like $product_id defined by $this->get_product_id($product_id) on line 26 can also be of type boolean; however, WC_Product_Factory::get_product_classname() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
44
45
		try {
46 237
			return new $classname( $product_id, $deprecated );
47 2
		} catch ( Exception $e ) {
48 2
			return false;
49
		}
50
	}
51
52
	/**
53
	 * Gets a product classname and allows filtering. Returns WC_Product_Simple if the class does not exist.
54
	 *
55
	 * @since  3.0.0
56
	 * @param  int    $product_id   Product ID.
57
	 * @param  string $product_type Product type.
58
	 * @return string
59
	 */
60 237
	public static function get_product_classname( $product_id, $product_type ) {
61 237
		$classname = apply_filters( 'woocommerce_product_class', self::get_classname_from_product_type( $product_type ), $product_type, 'variation' === $product_type ? 'product_variation' : 'product', $product_id );
62
63 237
		if ( ! $classname || ! class_exists( $classname ) ) {
64 2
			$classname = 'WC_Product_Simple';
65
		}
66
67 237
		return $classname;
68
	}
69
70
	/**
71
	 * Get the product type for a product.
72
	 *
73
	 * @since 3.0.0
74
	 * @param  int $product_id Product ID.
75
	 * @return string|false
76
	 */
77 261
	public static function get_product_type( $product_id ) {
78
		// Allow the overriding of the lookup in this function. Return the product type here.
79 261
		$override = apply_filters( 'woocommerce_product_type_query', false, $product_id );
80 261
		if ( ! $override ) {
81 261
			return WC_Data_Store::load( 'product' )->get_product_type( $product_id );
0 ignored issues
show
Documentation Bug introduced by
The method get_product_type does not exist on object<WC_Data_Store>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
82
		} else {
83
			return $override;
84
		}
85
	}
86
87
	/**
88
	 * Create a WC coding standards compliant class name e.g. WC_Product_Type_Class instead of WC_Product_type-class.
89
	 *
90
	 * @param  string $product_type Product type.
91
	 * @return string|false
92
	 */
93 238
	public static function get_classname_from_product_type( $product_type ) {
94 238
		return $product_type ? 'WC_Product_' . implode( '_', array_map( 'ucfirst', explode( '-', $product_type ) ) ) : false;
95
	}
96
97
	/**
98
	 * Get the product ID depending on what was passed.
99
	 *
100
	 * @since  3.0.0
101
	 * @param  WC_Product|WP_Post|int|bool $product Product instance, post instance, numeric or false to use global $post.
102
	 * @return int|bool false on failure
103
	 */
104 237 View Code Duplication
	private function get_product_id( $product ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105
		global $post;
106
107 237
		if ( false === $product && isset( $post, $post->ID ) && 'product' === get_post_type( $post->ID ) ) {
108
			return absint( $post->ID );
109 237
		} elseif ( is_numeric( $product ) ) {
110 235
			return $product;
111 4
		} elseif ( $product instanceof WC_Product ) {
112
			return $product->get_id();
113 4
		} elseif ( ! empty( $product->ID ) ) {
114 4
			return $product->ID;
115
		} else {
116
			return false;
117
		}
118
	}
119
}
120