1<?php
2
3/*
4 * This file is part of the Prophecy.
5 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
6 *     Marcello Duarte <marcello.duarte@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Prophecy\Argument\Token;
13
14use Prophecy\Exception\InvalidArgumentException;
15
16/**
17 * Array entry token.
18 *
19 * @author Boris Mikhaylov <kaguxmail@gmail.com>
20 */
21class ArrayEntryToken implements TokenInterface
22{
23    /** @var \Prophecy\Argument\Token\TokenInterface */
24    private $key;
25    /** @var \Prophecy\Argument\Token\TokenInterface */
26    private $value;
27
28    /**
29     * @param mixed $key   exact value or token
30     * @param mixed $value exact value or token
31     */
32    public function __construct($key, $value)
33    {
34        $this->key = $this->wrapIntoExactValueToken($key);
35        $this->value = $this->wrapIntoExactValueToken($value);
36    }
37
38    /**
39     * Scores half of combined scores from key and value tokens for same entry. Capped at 8.
40     * If argument implements \ArrayAccess without \Traversable, then key token is restricted to ExactValueToken.
41     *
42     * @param array|\ArrayAccess|\Traversable $argument
43     *
44     * @throws \Prophecy\Exception\InvalidArgumentException
45     * @return bool|int
46     */
47    public function scoreArgument($argument)
48    {
49        if ($argument instanceof \Traversable) {
50            $argument = iterator_to_array($argument);
51        }
52
53        if ($argument instanceof \ArrayAccess) {
54            $argument = $this->convertArrayAccessToEntry($argument);
55        }
56
57        if (!is_array($argument) || empty($argument)) {
58            return false;
59        }
60
61        $keyScores = array_map(array($this->key,'scoreArgument'), array_keys($argument));
62        $valueScores = array_map(array($this->value,'scoreArgument'), $argument);
63        $scoreEntry = function ($value, $key) {
64            return $value && $key ? min(8, ($key + $value) / 2) : false;
65        };
66
67        return max(array_map($scoreEntry, $valueScores, $keyScores));
68    }
69
70    /**
71     * Returns false.
72     *
73     * @return boolean
74     */
75    public function isLast()
76    {
77        return false;
78    }
79
80    /**
81     * Returns string representation for token.
82     *
83     * @return string
84     */
85    public function __toString()
86    {
87        return sprintf('[..., %s => %s, ...]', $this->key, $this->value);
88    }
89
90    /**
91     * Returns key
92     *
93     * @return TokenInterface
94     */
95    public function getKey()
96    {
97        return $this->key;
98    }
99
100    /**
101     * Returns value
102     *
103     * @return TokenInterface
104     */
105    public function getValue()
106    {
107        return $this->value;
108    }
109
110    /**
111     * Wraps non token $value into ExactValueToken
112     *
113     * @param $value
114     * @return TokenInterface
115     */
116    private function wrapIntoExactValueToken($value)
117    {
118        return $value instanceof TokenInterface ? $value : new ExactValueToken($value);
119    }
120
121    /**
122     * Converts instance of \ArrayAccess to key => value array entry
123     *
124     * @param \ArrayAccess $object
125     *
126     * @return array|null
127     * @throws \Prophecy\Exception\InvalidArgumentException
128     */
129    private function convertArrayAccessToEntry(\ArrayAccess $object)
130    {
131        if (!$this->key instanceof ExactValueToken) {
132            throw new InvalidArgumentException(sprintf(
133                'You can only use exact value tokens to match key of ArrayAccess object'.PHP_EOL.
134                'But you used `%s`.',
135                $this->key
136            ));
137        }
138
139        $key = $this->key->getValue();
140
141        return $object->offsetExists($key) ? array($key => $object[$key]) : array();
142    }
143}
144