1<?php
2
3namespace Elastica;
4
5use Elastica\Bulk\Action;
6use Elastica\Exception\InvalidException;
7
8/**
9 * Single document stored in elastic search.
10 *
11 * @author   Nicolas Ruflin <spam@ruflin.com>
12 */
13class Document extends AbstractUpdateAction
14{
15    const OP_TYPE_CREATE = Action::OP_TYPE_CREATE;
16
17    /**
18     * Document data.
19     *
20     * @var array Document data
21     */
22    protected $_data = [];
23
24    /**
25     * Whether to use this document to upsert if the document does not exist.
26     *
27     * @var bool
28     */
29    protected $_docAsUpsert = false;
30
31    /**
32     * @var bool
33     */
34    protected $_autoPopulate = false;
35
36    /**
37     * Creates a new document.
38     *
39     * @param int|string   $id    OPTIONAL $id Id is create if empty
40     * @param array|string $data  OPTIONAL Data array
41     * @param Type|string  $type  OPTIONAL Type name
42     * @param Index|string $index OPTIONAL Index name
43     */
44    public function __construct($id = '', $data = [], $type = '', $index = '')
45    {
46        $this->setId($id);
47        $this->setData($data);
48        $this->setType($type);
49        $this->setIndex($index);
50    }
51
52    /**
53     * @param string $key
54     *
55     * @return mixed
56     */
57    public function __get($key)
58    {
59        return $this->get($key);
60    }
61
62    /**
63     * @param string $key
64     * @param mixed  $value
65     */
66    public function __set($key, $value)
67    {
68        $this->set($key, $value);
69    }
70
71    /**
72     * @param string $key
73     *
74     * @return bool
75     */
76    public function __isset($key)
77    {
78        return $this->has($key) && null !== $this->get($key);
79    }
80
81    /**
82     * @param string $key
83     */
84    public function __unset($key)
85    {
86        $this->remove($key);
87    }
88
89    /**
90     * @param string $key
91     *
92     * @throws \Elastica\Exception\InvalidException
93     *
94     * @return mixed
95     */
96    public function get($key)
97    {
98        if (!$this->has($key)) {
99            throw new InvalidException("Field {$key} does not exist");
100        }
101
102        return $this->_data[$key];
103    }
104
105    /**
106     * @param string $key
107     * @param mixed  $value
108     *
109     * @throws \Elastica\Exception\InvalidException
110     *
111     * @return $this
112     */
113    public function set($key, $value)
114    {
115        if (!\is_array($this->_data)) {
116            throw new InvalidException('Document data is serialized data. Data creation is forbidden.');
117        }
118        $this->_data[$key] = $value;
119
120        return $this;
121    }
122
123    /**
124     * @param string $key
125     *
126     * @return bool
127     */
128    public function has($key)
129    {
130        return \is_array($this->_data) && \array_key_exists($key, $this->_data);
131    }
132
133    /**
134     * @param string $key
135     *
136     * @throws \Elastica\Exception\InvalidException
137     *
138     * @return $this
139     */
140    public function remove($key)
141    {
142        if (!$this->has($key)) {
143            throw new InvalidException("Field {$key} does not exist");
144        }
145        unset($this->_data[$key]);
146
147        return $this;
148    }
149
150    /**
151     * Adds a file to the index.
152     *
153     * To use this feature you have to call the following command in the
154     * elasticsearch directory:
155     * <code>
156     * ./bin/plugin -install elasticsearch/elasticsearch-mapper-attachments/1.6.0
157     * </code>
158     * This installs the tika file analysis plugin. More infos about supported formats
159     * can be found here: {@link http://tika.apache.org/0.7/formats.html}
160     *
161     * @param string $key      Key to add the file to
162     * @param string $filepath Path to add the file
163     * @param string $mimeType OPTIONAL Header mime type
164     *
165     * @return $this
166     */
167    public function addFile($key, $filepath, $mimeType = '')
168    {
169        $value = \base64_encode(\file_get_contents($filepath));
170
171        if (!empty($mimeType)) {
172            $value = ['_content_type' => $mimeType, '_name' => $filepath, '_content' => $value];
173        }
174
175        $this->set($key, $value);
176
177        return $this;
178    }
179
180    /**
181     * Add file content.
182     *
183     * @param string $key     Document key
184     * @param string $content Raw file content
185     *
186     * @return $this
187     */
188    public function addFileContent($key, $content)
189    {
190        return $this->set($key, \base64_encode($content));
191    }
192
193    /**
194     * Adds a geopoint to the document.
195     *
196     * Geohashes are not yet supported
197     *
198     * @param string $key       Field key
199     * @param float  $latitude  Latitude value
200     * @param float  $longitude Longitude value
201     *
202     * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-geo-point-type.html
203     *
204     * @return $this
205     */
206    public function addGeoPoint($key, $latitude, $longitude)
207    {
208        $value = ['lat' => $latitude, 'lon' => $longitude];
209
210        $this->set($key, $value);
211
212        return $this;
213    }
214
215    /**
216     * Overwrites the current document data with the given data.
217     *
218     * @param array|string $data Data array
219     *
220     * @return $this
221     */
222    public function setData($data)
223    {
224        $this->_data = $data;
225
226        return $this;
227    }
228
229    /**
230     * Returns the document data.
231     *
232     * @return array|string Document data
233     */
234    public function getData()
235    {
236        return $this->_data;
237    }
238
239    /**
240     * @param bool $value
241     *
242     * @return $this
243     */
244    public function setDocAsUpsert($value)
245    {
246        $this->_docAsUpsert = (bool) $value;
247
248        return $this;
249    }
250
251    /**
252     * @return bool
253     */
254    public function getDocAsUpsert()
255    {
256        return $this->_docAsUpsert;
257    }
258
259    /**
260     * @param bool $autoPopulate
261     *
262     * @return $this
263     */
264    public function setAutoPopulate($autoPopulate = true)
265    {
266        $this->_autoPopulate = (bool) $autoPopulate;
267
268        return $this;
269    }
270
271    /**
272     * @return bool
273     */
274    public function isAutoPopulate()
275    {
276        return $this->_autoPopulate;
277    }
278
279    /**
280     * Sets pipeline.
281     *
282     * @param string $pipeline
283     *
284     * @return $this
285     */
286    public function setPipeline($pipeline)
287    {
288        return $this->setParam('pipeline', $pipeline);
289    }
290
291    /**
292     * @return string
293     */
294    public function getPipeline()
295    {
296        return $this->getParam('pipeline');
297    }
298
299    /**
300     * @return bool
301     */
302    public function hasPipeline()
303    {
304        return $this->hasParam('pipeline');
305    }
306
307    /**
308     * Returns the document as an array.
309     *
310     * @return array
311     */
312    public function toArray()
313    {
314        $doc = $this->getParams();
315        $doc['_source'] = $this->getData();
316
317        return $doc;
318    }
319
320    /**
321     * @param array|\Elastica\Document $data
322     *
323     * @throws \Elastica\Exception\InvalidException
324     *
325     * @return self
326     */
327    public static function create($data)
328    {
329        if ($data instanceof self) {
330            return $data;
331        }
332
333        if (\is_array($data)) {
334            return new self('', $data);
335        }
336
337        throw new InvalidException('Failed to create document. Invalid data passed.');
338    }
339}
340