Completed
Push — master ( 9c7fd0...afc45c )
by Andrii
10:58
created

SaleRepository::joinPlans()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 12
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 4
nop 1
crap 12
1
<?php
2
/**
3
 * API for Billing
4
 *
5
 * @link      https://github.com/hiqdev/billing-hiapi
6
 * @package   billing-hiapi
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2017, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\billing\hiapi\sale;
12
13
use DateTimeImmutable;
14
use hiqdev\yii\DataMapper\components\ConnectionInterface;
15
use hiqdev\yii\DataMapper\models\relations\Bucket;
16
use hiqdev\yii\DataMapper\expressions\CallExpression;
17
use hiqdev\yii\DataMapper\expressions\HstoreExpression;
18
use hiqdev\yii\DataMapper\components\EntityManagerInterface;
19
use hiqdev\yii\DataMapper\query\Specification;
20
use hiqdev\yii\DataMapper\repositories\BaseRepository;
21
use hiqdev\php\billing\action\ActionInterface;
22
use hiqdev\php\billing\customer\Customer;
23
use hiqdev\php\billing\order\OrderInterface;
24
use hiqdev\php\billing\plan\Plan;
25
use hiqdev\php\billing\plan\PlanInterface;
26
use hiqdev\php\billing\price\PriceInterface;
27
use hiqdev\php\billing\sale\SaleInterface;
28
use hiqdev\php\billing\sale\SaleFactoryInterface;
29
use hiqdev\php\billing\sale\SaleRepositoryInterface;
30
use hiqdev\php\billing\sale\Sale;
31
use hiqdev\php\billing\target\Target;
32
use Yii;
33
34
class SaleRepository extends BaseRepository implements SaleRepositoryInterface
35
{
36
    /** {@inheritdoc} */
37
    public $queryClass = SaleQuery::class;
38
39
    public function create(array $row)
40
    {
41
        $row['target']      = $this->createEntity(Target::class, $row['target']);
0 ignored issues
show
Documentation Bug introduced by
The method createEntity does not exist on object<hiqdev\billing\hiapi\sale\SaleRepository>? 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...
42
        $row['customer']    = $this->createEntity(Customer::class, $row['customer']);
0 ignored issues
show
Documentation Bug introduced by
The method createEntity does not exist on object<hiqdev\billing\hiapi\sale\SaleRepository>? 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...
43
        $row['plan']        = $this->createEntity(Plan::class, $row['plan']);
0 ignored issues
show
Documentation Bug introduced by
The method createEntity does not exist on object<hiqdev\billing\hiapi\sale\SaleRepository>? 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...
44
        $row['time']        = new DateTimeImmutable($row['time']);
45
46
        return parent::create($row);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class hiqdev\yii\DataMapper\repositories\BaseRepository as the method create() does only exist in the following sub-classes of hiqdev\yii\DataMapper\repositories\BaseRepository: hiqdev\billing\hiapi\repositories\BillRepository, hiqdev\billing\hiapi\rep...ries\CustomerRepository, hiqdev\billing\hiapi\sale\SaleRepository, hiqdev\billing\hiapi\vo\...TimeImmutableRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
47
    }
48
49
    public function findId(SaleInterface $sale)
50
    {
51
        if ($sale->hasId()) {
52
            return $sale->getId();
53
        }
54
        $hstore = new HstoreExpression(array_filter([
55
            'buyer'     => $sale->getCustomer()->getLogin(),
56
            'buyer_id'  => $sale->getCustomer()->getId(),
57
            'object_id' => $sale->getTarget()->getId(),
58
            'tariff_id' => $sale->getPlan()->getId(),
59
        ]));
60
        $call = new CallExpression('sale_id', [$hstore]);
61
        $command = $this->em->getConnection()->createSelect($call);
0 ignored issues
show
Bug introduced by
The method getConnection() does not seem to exist on object<hiqdev\yii\DataMa...EntityManagerInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
62
63
        return $command->scalar();
64
    }
65
66
    /**
67
     * @param OrderInterface $order
68
     * @return Sale[]|SaleInterface[]
69
     */
70
    public function findByOrder(OrderInterface $order)
71
    {
72
        return array_map([$this, 'findByAction'], $order->getActions());
73
    }
74
75
    /**
76
     * @param ActionInterface $action
77
     * @return SaleInterface
78
     */
79
    public function findByAction(ActionInterface $action)
80
    {
81
        $client_id = $action->getCustomer()->getId();
82
        $seller_id = $action->getCustomer()->getSeller()->getId();
83
        $type = $action->getTarget()->getType();
84
85
        if ($type === 'certificate') {
86
            //// XXX tmp crutch
87
            $class_id = $this->em->db->createCommand("SELECT class_id('certificate')")->queryScalar();
0 ignored issues
show
Bug introduced by
Accessing db on the interface hiqdev\yii\DataMapper\co...\EntityManagerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
88
            $cond = [
89
                'target-id' => $class_id,
90
                'customer-id' => $client_id,
91
            ];
92
            if (empty($client_id)) {
93
                $cond['customer-id'] = $seller_id;
94
                $cond['seller-id'] = $seller_id;
95
            }
96
        } else if ($type === 'server') {
97
            $cond = [
98
                'target-id' => $action->getTarget()->getId(),
99
                'customer-id' => $client_id,
100
            ];
101
        } else {
102
            throw new \Exception('not implemented for: ' . $type);
103
        }
104
105
        $spec = Yii::createObject(Specification::class)
106
            /// XXX how to pass if we want with prices into joinPlans?
107
            ->with('plans')
108
            ->where($cond);
109
110
        return $this->findOne($spec);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->findOne($spec); of type object|false adds false to the return on line 110 which is incompatible with the return type documented by hiqdev\billing\hiapi\sal...epository::findByAction of type hiqdev\php\billing\sale\SaleInterface. It seems like you forgot to handle an error condition.
Loading history...
111
    }
112
113
    protected function joinPlans(&$rows)
114
    {
115
        $bucket = Bucket::fromRows($rows, 'plan-id');
116
        $spec = (new Specification())
117
            ->with('prices')
118
            ->where(['id' => $bucket->getKeys()]);
119
        $raw_plans = $this->getRepository(PlanInterface::class)->queryAll($spec);
120
        /// TODO for SilverFire: try to do with bucket
121
        $plans = [];
122
        foreach ($raw_plans as $plan) {
123
            $plans[$plan['id']] = $plan;
124
        }
125
        foreach ($rows as &$sale) {
126
            $sale['plan'] = $plans[$sale['plan-id']];
127
        }
128
    }
129
}
130