View difference between Paste ID: 6ttf8Dav and KYCmvw60
SHOW: | | - or go back to the newest paste.
1-
<?php
1+
<?php
2-
/**
2+
/**
3-
 * This class layers support for plural specs such as changes, jobs,
3+
 * This class layers support for plural specs such as changes, jobs,
4-
 * users, etc. on top of the singular spec support already present
4+
 * users, etc. on top of the singular spec support already present
5-
 * in P4\Spec\SingularAbstract.
5+
 * in P4\Spec\SingularAbstract.
6-
 *
6+
 *
7-
 * @copyright   2011 Perforce Software. All rights reserved.
7+
 * @copyright   2011 Perforce Software. All rights reserved.
8-
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
8+
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
9-
 * @version     <release>/<patch>
9+
 * @version     <release>/<patch>
10-
 */
10+
 */
11-
11+
12-
namespace P4\Spec;
12+
namespace P4\Spec;
13-
13+
14-
use P4;
14+
use P4;
15-
use P4\Validate;
15+
use P4\Validate;
16-
use P4\Spec\Exception\Exception;
16+
use P4\Spec\Exception\Exception;
17-
use P4\Spec\Exception\NotFoundException;
17+
use P4\Spec\Exception\NotFoundException;
18-
use P4\Connection\ConnectionInterface;
18+
use P4\Connection\ConnectionInterface;
19-
use P4\Model\Fielded\Iterator as FieldedIterator;
19+
use P4\Model\Fielded\Iterator as FieldedIterator;
20-
use P4\OutputHandler\Limit;
20+
use P4\OutputHandler\Limit;
21-
21+
22-
abstract class PluralAbstract extends SingularAbstract
22+
abstract class PluralAbstract extends SingularAbstract
23-
{
23+
{
24-
    const ID_FIELD              = null;
24+
    const ID_FIELD              = null;
25-
    const FETCH_MAXIMUM         = 'maximum';
25+
    const FETCH_MAXIMUM         = 'maximum';
26-
    const FETCH_AFTER           = 'after';
26+
    const FETCH_AFTER           = 'after';
27-
    const TEMP_ID_PREFIX        = '~tmp';
27+
    const TEMP_ID_PREFIX        = '~tmp';
28-
    const TEMP_ID_DELIMITER     = ".";
28+
    const TEMP_ID_DELIMITER     = ".";
29-
29+
30-
    /**
30+
    /**
31-
     * Get the id of this spec entry.
31+
     * Get the id of this spec entry.
32-
     *
32+
     *
33-
     * @return  null|string     the id of this entry.
33+
     * @return  null|string     the id of this entry.
34-
     */
34+
     */
35-
    public function getId()
35+
    public function getId()
36-
    {
36+
    {
37-
        if (array_key_exists(static::ID_FIELD, $this->values)) {
37+
        if (array_key_exists(static::ID_FIELD, $this->values)) {
38-
            return $this->values[static::ID_FIELD];
38+
            return $this->values[static::ID_FIELD];
39-
        } else {
39+
        } else {
40-
            return null;
40+
            return null;
41-
        }
41+
        }
42-
    }
42+
    }
43-
43+
44-
    /**
44+
    /**
45-
     * Set the id of this spec entry. Id must be in a valid format or null.
45+
     * Set the id of this spec entry. Id must be in a valid format or null.
46-
     *
46+
     *
47-
     * @param   null|string     $id     the id of this entry - pass null to clear.
47+
     * @param   null|string     $id     the id of this entry - pass null to clear.
48-
     * @return  PluralAbstract          provides a fluent interface
48+
     * @return  PluralAbstract          provides a fluent interface
49-
     * @throws  \InvalidArgumentException   if id does not pass validation.
49+
     * @throws  \InvalidArgumentException   if id does not pass validation.
50-
     */
50+
     */
51-
    public function setId($id)
51+
    public function setId($id)
52-
    {
52+
    {
53-
        if ($id !== null && !static::isValidId($id)) {
53+
        if ($id !== null && !static::isValidId($id)) {
54-
            throw new \InvalidArgumentException("Cannot set id. Id is invalid.");
54+
            throw new \InvalidArgumentException("Cannot set id. Id is invalid.");
55-
        }
55+
        }
56-
56+
57-
        // if populate was deferred, caller expects it
57+
        // if populate was deferred, caller expects it
58-
        // to have been populated already.
58+
        // to have been populated already.
59-
        $this->populate();
59+
        $this->populate();
60-
60+
61-
        $this->values[static::ID_FIELD] = $id;
61+
        $this->values[static::ID_FIELD] = $id;
62-
62+
63-
        return $this;
63+
        return $this;
64-
    }
64+
    }
65-
65+
66-
    /**
66+
    /**
67-
     * Determine if a spec record with the given id exists.
67+
     * Determine if a spec record with the given id exists.
68-
     * Must be implemented by sub-classes because this test
68+
     * Must be implemented by sub-classes because this test
69-
     * is impractical to generalize.
69+
     * is impractical to generalize.
70-
     *
70+
     *
71-
     * @param   string                  $id             the id to check for.
71+
     * @param   string                  $id             the id to check for.
72-
     * @param   ConnectionInterface     $connection     optional - a specific connection to use.
72+
     * @param   ConnectionInterface     $connection     optional - a specific connection to use.
73-
     * @return  bool    true if the given id matches an existing record.
73+
     * @return  bool    true if the given id matches an existing record.
74-
     */
74+
     */
75-
    abstract public static function exists($id, ConnectionInterface $connection = null);
75+
    abstract public static function exists($id, ConnectionInterface $connection = null);
76-
76+
77-
    /**
77+
    /**
78-
     * Get the requested spec entry from Perforce.
78+
     * Get the requested spec entry from Perforce.
79-
     *
79+
     *
80-
     * @param   string                  $id         the id of the entry to fetch.
80+
     * @param   string                  $id         the id of the entry to fetch.
81-
     * @param   ConnectionInterface     $connection optional - a specific connection to use.
81+
     * @param   ConnectionInterface     $connection optional - a specific connection to use.
82-
     * @return  PluralAbstract          instace of the requested entry.
82+
     * @return  PluralAbstract          instace of the requested entry.
83-
     * @throws  \InvalidArgumentException   if no id is given.
83+
     * @throws  \InvalidArgumentException   if no id is given.
84-
     */
84+
     */
85-
    public static function fetch($id, ConnectionInterface $connection = null)
85+
    public static function fetch($id, ConnectionInterface $connection = null)
86-
    {
86+
    {
87-
        // ensure a valid id is provided.
87+
        // ensure a valid id is provided.
88-
        if (!static::isValidId($id)) {
88+
        if (!static::isValidId($id)) {
89-
            throw new \InvalidArgumentException("Must supply a valid id to fetch.");
89+
            throw new \InvalidArgumentException("Must supply a valid id to fetch.");
90-
        }
90+
        }
91-
91+
92-
        // if no connection given, use default.
92+
        // if no connection given, use default.
93-
        $connection = $connection ?: static::getDefaultConnection();
93+
        $connection = $connection ?: static::getDefaultConnection();
94-
94+
95-
        // ensure id exists.
95+
        // ensure id exists.
96-
        if (!static::exists($id, $connection)) {
96+
        if (!static::exists($id, $connection)) {
97-
            throw new NotFoundException(
97+
            throw new NotFoundException(
98-
                "Cannot fetch " . static::SPEC_TYPE . " $id. Record does not exist."
98+
                "Cannot fetch " . static::SPEC_TYPE . " $id. Record does not exist."
99-
            );
99+
            );
100-
        }
100+
        }
101-
101+
102-
        // construct spec instance.
102+
        // construct spec instance.
103-
        $spec = new static($connection);
103+
        $spec = new static($connection);
104-
        $spec->setId($id)
104+
        $spec->setId($id)
105-
             ->deferPopulate();
105+
             ->deferPopulate();
106-
106+
107-
        return $spec;
107+
        return $spec;
108-
    }
108+
    }
109-
109+
110-
    /**
110+
    /**
111-
     * Get all entries of this type from Perforce.
111+
     * Get all entries of this type from Perforce.
112-
     *
112+
     *
113-
     * @param   array   $options    optional - array of options to augment fetch behavior.
113+
     * @param   array   $options    optional - array of options to augment fetch behavior.
114-
     *                              supported options are:
114+
     *                              supported options are:
115-
     *
115+
     *
116-
     *                                  FETCH_MAXIMUM - set to integer value to limit to the
116+
     *                                  FETCH_MAXIMUM - set to integer value to limit to the
117-
     *                                                  first 'max' number of entries.
117+
     *                                                  first 'max' number of entries.
118-
     *                                    FETCH_AFTER - set to an id _after_ which to start collecting entries
118+
     *                                    FETCH_AFTER - set to an id _after_ which to start collecting entries
119-
     *                                                  note: entries seen before 'after' count towards max.
119+
     *                                                  note: entries seen before 'after' count towards max.
120-
     *
120+
     *
121-
     * @param   ConnectionInterface     $connection optional - a specific connection to use.
121+
     * @param   ConnectionInterface     $connection optional - a specific connection to use.
122-
     * @return  FieldedIterator         all records of this type.
122+
     * @return  FieldedIterator         all records of this type.
123-
     * @todo    make limit work for depot (in a P4\Spec\Depot sub-class)
123+
     * @todo    make limit work for depot (in a P4\Spec\Depot sub-class)
124-
     */
124+
     */
125-
    public static function fetchAll($options = array(), ConnectionInterface $connection = null)
125+
    public static function fetchAll($options = array(), ConnectionInterface $connection = null)
126-
    {
126+
    {
127-
        // if no connection given, use default.
127+
        // if no connection given, use default.
128-
        $connection = $connection ?: static::getDefaultConnection();
128+
        $connection = $connection ?: static::getDefaultConnection();
129-
129+
130-
        // get command to use
130+
        // get command to use
131-
        $command = static::getFetchAllCommand();
131+
        $command = static::getFetchAllCommand();
132-
132+
133-
        // get command flags for given fetch options.
133+
        // get command flags for given fetch options.
134-
        $flags = static::getFetchAllFlags($options);
134+
        $flags = static::getFetchAllFlags($options);
135-
135+
136-
        // fetch all specs.
136+
        // fetch all specs.
137-
        // configure a handler to enforce 'after' (skip entries up to and including 'after')
137+
        // configure a handler to enforce 'after' (skip entries up to and including 'after')
138-
        $after = isset($options[static::FETCH_AFTER]) ? $options[static::FETCH_AFTER] : null;
138+
        $after = isset($options[static::FETCH_AFTER]) ? $options[static::FETCH_AFTER] : null;
139-
        if (strlen($after)) {
139+
        if (strlen($after)) {
140-
            $idField = static::ID_FIELD;
140+
            $idField = static::ID_FIELD;
141-
            $isAfter = false;
141+
            $isAfter = false;
142-
            $handler = new Limit;
142+
            $handler = new Limit;
143-
            $handler->setFilterCallback(
143+
            $handler->setFilterCallback(
144-
                function ($data) use ($after, $idField, &$isAfter) {
144+
                function ($data) use ($after, $idField, &$isAfter) {
145-
                    if ($after && !$isAfter) {
145+
                    if ($after && !$isAfter) {
146-
                        // id field could be upper or lower case in list output.
146+
                        // id field could be upper or lower case in list output.
147-
                        $id      = isset($data[lcfirst($idField)]) ? $data[lcfirst($idField)] : null;
147+
                        $id      = isset($data[lcfirst($idField)]) ? $data[lcfirst($idField)] : null;
148-
                        $id      = !$id && isset($data[$idField])  ? $data[$idField]          : $id;
148+
                        $id      = !$id && isset($data[$idField])  ? $data[$idField]          : $id;
149-
                        $isAfter = ($after == $id);
149+
                        $isAfter = ($after == $id);
150-
                        return false;
150+
                        return false;
151-
                    }
151+
                    }
152-
                    return true;
152+
                    return true;
153-
                }
153+
                }
154-
            );
154+
            );
155-
            $result = $connection->runHandler($handler, $command, $flags);
155+
            $result = $connection->runHandler($handler, $command, $flags);
156-
        } else {
156+
        } else {
157-
            $result = $connection->run($command, $flags);
157+
            $result = $connection->run($command, $flags);
158-
        }
158+
        }
159-
159+
160-
        // expand any sequences present
160+
        // expand any sequences present
161-
        $result->expandSequences();
161+
        $result->expandSequences();
162-
162+
163-
        // convert result data to spec objects.
163+
        // convert result data to spec objects.
164-
        $specs = new FieldedIterator;
164+
        $specs = new FieldedIterator;
165-
        foreach ($result->getData() as $data) {
165+
        foreach ($result->getData() as $data) {
166-
            $spec = static::fromSpecListEntry($data, $flags, $connection);
166+
            $spec = static::fromSpecListEntry($data, $flags, $connection);
167-
            $specs[$spec->getId()] = $spec;
167+
            $specs[$spec->getId()] = $spec;
168-
        }
168+
        }
169-
169+
170-
        return $specs;
170+
        return $specs;
171-
    }
171+
    }
172-
172+
173-
    /**
173+
    /**
174-
     * Create a temporary entry.
174+
     * Create a temporary entry.
175-
     *
175+
     *
176-
     * The passed values can, optionally, specify the id of the temp entry.
176+
     * The passed values can, optionally, specify the id of the temp entry.
177-
     * If no id is passed in values, one will be generated following the
177+
     * If no id is passed in values, one will be generated following the
178-
     * conventions described in makeTempId().
178+
     * conventions described in makeTempId().
179-
     *
179+
     *
180-
     * Temp entries are deleted when the connection is closed.
180+
     * Temp entries are deleted when the connection is closed.
181-
     *
181+
     *
182-
     * @param   array|null              $values             optional - values to set on temp entry,
182+
     * @param   array|null              $values             optional - values to set on temp entry,
183-
     *                                                      can include ID
183+
     *                                                      can include ID
184-
     * @param   function|null           $cleanupCallback    optional - callback to use for cleanup.
184+
     * @param   function|null           $cleanupCallback    optional - callback to use for cleanup.
185-
     *                                                      signature is:
185+
     *                                                      signature is:
186-
     *                                                      function($entry, $defaultCallback)
186+
     *                                                      function($entry, $defaultCallback)
187-
     * @param   ConnectionInterface     $connection optional - a specific connection to use.
187+
     * @param   ConnectionInterface     $connection optional - a specific connection to use.
188-
     * @return  PluralAbstract          instace of the temp entry.
188+
     * @return  PluralAbstract          instace of the temp entry.
189-
     */
189+
     */
190-
    public static function makeTemp(
190+
    public static function makeTemp(
191-
        array $values = null,
191+
        array $values = null,
192-
        $cleanupCallback = null,
192+
        $cleanupCallback = null,
193-
        ConnectionInterface $connection = null
193+
        ConnectionInterface $connection = null
194-
    ) {
194+
    ) {
195-
        // normalize to array
195+
        // normalize to array
196-
        $values = $values ?: array();
196+
        $values = $values ?: array();
197-
197+
198-
        // generate an id if no value for our id field is present
198+
        // generate an id if no value for our id field is present
199-
        if (!isset($values[static::ID_FIELD])) {
199+
        if (!isset($values[static::ID_FIELD])) {
200-
            $values[static::ID_FIELD] = static::makeTempId();
200+
            $values[static::ID_FIELD] = static::makeTempId();
201-
        }
201+
        }
202-
202+
203-
        // create the temporary instance.
203+
        // create the temporary instance.
204-
        $temp = new static($connection);
204+
        $temp = new static($connection);
205-
        $temp->set($values)->save();
205+
        $temp->set($values)->save();
206-
206+
207-
        // remove the temp entry when the connection terminates.
207+
        // remove the temp entry when the connection terminates.
208-
        $defaultCallback = static::getTempCleanupCallback();
208+
        $defaultCallback = static::getTempCleanupCallback();
209-
        $temp->getConnection()->addDisconnectCallback(
209+
        $temp->getConnection()->addDisconnectCallback(
210-
            function ($connection) use ($temp, $cleanupCallback, $defaultCallback) {
210+
            function ($connection) use ($temp, $cleanupCallback, $defaultCallback) {
211-
                try {
211+
                try {
212-
                    // use the passed callback if valid, fallback to the default callback
212+
                    // use the passed callback if valid, fallback to the default callback
213-
                    if (is_callable($cleanupCallback)) {
213+
                    if (is_callable($cleanupCallback)) {
214-
                        $cleanupCallback($temp, $defaultCallback);
214+
                        $cleanupCallback($temp, $defaultCallback);
215-
                    } else {
215+
                    } else {
216-
                        $defaultCallback($temp);
216+
                        $defaultCallback($temp);
217-
                    }
217+
                    }
218-
                } catch (\Exception $e) {
218+
                } catch (\Exception $e) {
219-
                    P4\Log::logException("Failed to delete temporary entry.", $e);
219+
                    P4\Log::logException("Failed to delete temporary entry.", $e);
220-
                }
220+
                }
221-
            }
221+
            }
222-
        );
222+
        );
223-
223+
224-
        return $temp;
224+
        return $temp;
225-
    }
225+
    }
226-
226+
227-
    /**
227+
    /**
228-
     * Generate a temporary id by combining the id prefix
228+
     * Generate a temporary id by combining the id prefix
229-
     * with the current time, pid and a random uniqid():
229+
     * with the current time, pid and a random uniqid():
230-
     *
230+
     *
231-
     *  ~tmp.<unixtime>.<pid>.<uniqid>
231+
     *  ~tmp.<unixtime>.<pid>.<uniqid>
232-
     *
232+
     *
233-
     * The leading tilde ('~') places the temporary id at the end of
233+
     * The leading tilde ('~') places the temporary id at the end of
234-
     * the list.  The unixtime ensures that the oldest ids will
234+
     * the list.  The unixtime ensures that the oldest ids will
235-
     * appear first (among temp ids), while the pid and uniqid provide
235+
     * appear first (among temp ids), while the pid and uniqid provide
236-
     * reasonable assurance that no two ids will collide.
236+
     * reasonable assurance that no two ids will collide.
237-
     *
237+
     *
238-
     * @return  string  an id suitable for use with temporary specs.
238+
     * @return  string  an id suitable for use with temporary specs.
239-
     */
239+
     */
240-
    public static function makeTempId()
240+
    public static function makeTempId()
241-
    {
241+
    {
242-
        return implode(
242+
        return implode(
243-
            static::TEMP_ID_DELIMITER,
243+
            static::TEMP_ID_DELIMITER,
244-
            array(
244+
            array(
245-
                static::TEMP_ID_PREFIX,
245+
                static::TEMP_ID_PREFIX,
246-
                time(),
246+
                time(),
247-
                getmypid(),
247+
                getmypid(),
248-
                uniqid("", true)
248+
                uniqid("", true)
249-
            )
249+
            )
250-
        );
250+
        );
251-
    }
251+
    }
252-
252+
253-
    /**
253+
    /**
254-
     * Delete this spec entry.
254+
     * Delete this spec entry.
255-
     *
255+
     *
256-
     * @param   array   $params     optional - additional flags to pass to delete
256+
     * @param   array   $params     optional - additional flags to pass to delete
257-
     *                              (e.g. some specs support -f to force delete).
257+
     *                              (e.g. some specs support -f to force delete).
258-
     * @return  PluralAbstract      provides a fluent interface
258+
     * @return  PluralAbstract      provides a fluent interface
259-
     * @throws  Exception           if no id has been set.
259+
     * @throws  Exception           if no id has been set.
260-
     */
260+
     */
261-
    public function delete(array $params = null)
261+
    public function delete(array $params = null)
262-
    {
262+
    {
263-
        $id = $this->getId();
263+
        $id = $this->getId();
264-
        if ($id === null) {
264+
        if ($id === null) {
265-
            throw new Exception("Cannot delete. No id has been set.");
265+
            throw new Exception("Cannot delete. No id has been set.");
266-
        }
266+
        }
267-
267+
268-
        // ensure id exists.
268+
        // ensure id exists.
269-
        $connection = $this->getConnection();
269+
        $connection = $this->getConnection();
270-
        if (!static::exists($id, $connection)) {
270+
        if (!static::exists($id, $connection)) {
271-
            throw new NotFoundException(
271+
            throw new NotFoundException(
272-
                "Cannot delete " . static::SPEC_TYPE . " $id. Record does not exist."
272+
                "Cannot delete " . static::SPEC_TYPE . " $id. Record does not exist."
273-
            );
273+
            );
274-
        }
274+
        }
275-
275+
276-
        $params = array_merge((array) $params, array("-d", $id));
276+
        $params = array_merge((array) $params, array("-d", $id));
277-
        $result = $connection->run(static::SPEC_TYPE, $params);
277+
        $result = $connection->run(static::SPEC_TYPE, $params);
278-
278+
279-
        // should re-populate.
279+
        // should re-populate.
280-
        $this->deferPopulate(true);
280+
        $this->deferPopulate(true);
281-
281+
282-
        return $this;
282+
        return $this;
283-
    }
283+
    }
284-
284+
285-
    /**
285+
    /**
286-
     * Get a field's raw value.
286+
     * Get a field's raw value.
287-
     * Extend parent to use getId() for id field.
287+
     * Extend parent to use getId() for id field.
288-
     *
288+
     *
289-
     * @param   string      $field  the name of the field to get the value of.
289+
     * @param   string      $field  the name of the field to get the value of.
290-
     * @return  mixed       the value of the field.
290+
     * @return  mixed       the value of the field.
291-
     * @throws  Exception   if the field does not exist.
291+
     * @throws  Exception   if the field does not exist.
292-
     */
292+
     */
293-
    public function getRawValue($field)
293+
    public function getRawValue($field)
294-
    {
294+
    {
295-
        if ($field === static::ID_FIELD) {
295+
        if ($field === static::ID_FIELD) {
296-
            return $this->getId();
296+
            return $this->getId();
297-
        }
297+
        }
298-
298+
299-
        // call-through.
299+
        // call-through.
300-
        return parent::getRawValue($field);
300+
        return parent::getRawValue($field);
301-
    }
301+
    }
302-
302+
303-
    /**
303+
    /**
304-
     * Set a field's raw value.
304+
     * Set a field's raw value.
305-
     * Extend parent to use setId() for id field.
305+
     * Extend parent to use setId() for id field.
306-
     *
306+
     *
307-
     * @param   string  $field      the name of the field to set the value of.
307+
     * @param   string  $field      the name of the field to set the value of.
308-
     * @param   mixed   $value      the value to set in the field.
308+
     * @param   mixed   $value      the value to set in the field.
309-
     * @return  SingularAbstract    provides a fluent interface
309+
     * @return  SingularAbstract    provides a fluent interface
310-
     * @throws  Exception           if the field does not exist.
310+
     * @throws  Exception           if the field does not exist.
311-
     */
311+
     */
312-
    public function setRawValue($field, $value)
312+
    public function setRawValue($field, $value)
313-
    {
313+
    {
314-
        if ($field === static::ID_FIELD) {
314+
        if ($field === static::ID_FIELD) {
315-
            return $this->setId($value);
315+
            return $this->setId($value);
316-
        }
316+
        }
317-
317+
318-
        // call-through.
318+
        // call-through.
319-
        return parent::setRawValue($field, $value);
319+
        return parent::setRawValue($field, $value);
320-
    }
320+
    }
321-
321+
322-
    /**
322+
    /**
323-
     * Extended to preserve id when values are cleared.
323+
     * Extended to preserve id when values are cleared.
324-
     * Schedule populate to run when data is requested (lazy-load).
324+
     * Schedule populate to run when data is requested (lazy-load).
325-
     *
325+
     *
326-
     * @param   bool    $reset  optionally clear instance values.
326+
     * @param   bool    $reset  optionally clear instance values.
327-
     */
327+
     */
328-
    public function deferPopulate($reset = false)
328+
    public function deferPopulate($reset = false)
329-
    {
329+
    {
330-
        if ($reset) {
330+
        if ($reset) {
331-
            $id = $this->getId();
331+
            $id = $this->getId();
332-
        }
332+
        }
333-
333+
334-
        parent::deferPopulate($reset);
334+
        parent::deferPopulate($reset);
335-
335+
336-
        if ($reset) {
336+
        if ($reset) {
337-
            $this->setId($id);
337+
            $this->setId($id);
338-
        }
338+
        }
339-
    }
339+
    }
340-
340+
341-
    /**
341+
    /**
342-
     * Provide a callback function to be used during cleanup of
342+
     * Provide a callback function to be used during cleanup of
343-
     * temp entries. The callback should expect a single parameter,
343+
     * temp entries. The callback should expect a single parameter,
344-
     * the entry being removed.
344+
     * the entry being removed.
345-
     *
345+
     *
346-
     * @return callable     A callback function with the signature function($entry)
346+
     * @return callable     A callback function with the signature function($entry)
347-
     */
347+
     */
348-
    protected static function getTempCleanupCallback()
348+
    protected static function getTempCleanupCallback()
349-
    {
349+
    {
350-
        return function ($entry) {
350+
        return function ($entry) {
351-
            // remove the temp entry we are responsible for
351+
            // remove the temp entry we are responsible for
352-
            $entry->delete();
352+
            $entry->delete();
353-
        };
353+
        };
354-
    }
354+
    }
355-
355+
356-
    /**
356+
    /**
357-
     * Check if the given id is in a valid format for this spec type.
357+
     * Check if the given id is in a valid format for this spec type.
358-
     *
358+
     *
359-
     * @param   string      $id     the id to check
359+
     * @param   string      $id     the id to check
360-
     * @return  bool        true if id is valid, false otherwise
360+
     * @return  bool        true if id is valid, false otherwise
361-
     */
361+
     */
362-
    protected static function isValidId($id)
362+
    protected static function isValidId($id)
363-
    {
363+
    {
364-
        $validator = new Validate\SpecName;
364+
        $validator = new Validate\SpecName;
365-
        return $validator->isValid($id);
365+
        return $validator->isValid($id);
366-
    }
366+
    }
367-
367+
368-
    /**
368+
    /**
369-
     * Extend parent populate to exit early if id is null.
369+
     * Extend parent populate to exit early if id is null.
370-
     */
370+
     */
371-
    protected function populate()
371+
    protected function populate()
372-
    {
372+
    {
373-
        // early exit if populate not needed.
373+
        // early exit if populate not needed.
374-
        if (!$this->needsPopulate) {
374+
        if (!$this->needsPopulate) {
375-
            return;
375+
            return;
376-
        }
376+
        }
377-
377+
378-
        // don't attempt populate if id null.
378+
        // don't attempt populate if id null.
379-
        if ($this->getId() === null) {
379+
        if ($this->getId() === null) {
380-
            return;
380+
            return;
381-
        }
381+
        }
382-
382+
383-
        parent::populate();
383+
        parent::populate();
384-
    }
384+
    }
385-
385+
386-
    /**
386+
    /**
387-
     * Get raw spec data direct from Perforce. No caching involved.
387+
     * Get raw spec data direct from Perforce. No caching involved.
388-
     * Extends parent to supply an id to the spec -o command.
388+
     * Extends parent to supply an id to the spec -o command.
389-
     *
389+
     *
390-
     * @return  array   $data   the raw spec output from Perforce.
390+
     * @return  array   $data   the raw spec output from Perforce.
391-
     */
391+
     */
392-
    protected function getSpecData()
392+
    protected function getSpecData()
393-
    {
393+
    {
394-
        $result = $this->getConnection()->run(
394+
        $result = $this->getConnection()->run(
395-
            static::SPEC_TYPE,
395+
            static::SPEC_TYPE,
396-
            array("-o", $this->getId())
396+
            array("-o", $this->getId())
397-
        );
397+
        );
398-
        return $result->expandSequences()->getData(-1);
398+
        return $result->expandSequences()->getData(-1);
399-
    }
399+
    }
400-
400+
401-
    /**
401+
    /**
402-
     * Given a spec entry from spec list output (e.g. 'p4 jobs'), produce
402+
     * Given a spec entry from spec list output (e.g. 'p4 jobs'), produce
403-
     * an instance of this spec with field values set where possible.
403+
     * an instance of this spec with field values set where possible.
404-
     *
404+
     *
405-
     * @param   array                   $listEntry      a single spec entry from spec list output.
405+
     * @param   array                   $listEntry      a single spec entry from spec list output.
406-
     * @param   array                   $flags          the flags that were used for this 'fetchAll' run.
406+
     * @param   array                   $flags          the flags that were used for this 'fetchAll' run.
407-
     * @param   ConnectionInterface     $connection     a specific connection to use.
407+
     * @param   ConnectionInterface     $connection     a specific connection to use.
408-
     * @return  PluralAbstract          a (partially) populated instance of this spec class.
408+
     * @return  PluralAbstract          a (partially) populated instance of this spec class.
409-
     */
409+
     */
410-
    protected static function fromSpecListEntry($listEntry, $flags, ConnectionInterface $connection)
410+
    protected static function fromSpecListEntry($listEntry, $flags, ConnectionInterface $connection)
411-
    {
411+
    {
412-
        // most spec list entries have leading lower-case field
412+
        // most spec list entries have leading lower-case field
413-
        // names which is inconsistent with defined field names.
413+
        // names which is inconsistent with defined field names.
414-
        // make all field names lead with an upper-case letter.
414+
        // make all field names lead with an upper-case letter.
415-
        $keys      = array_map('ucfirst', array_keys($listEntry));
415+
        $keys      = array_map('ucfirst', array_keys($listEntry));
416-
        $listEntry = array_combine($keys, $listEntry);
416+
        $listEntry = array_combine($keys, $listEntry);
417-
417+
418-
        // convert common timestamps to dates
418+
        // convert common timestamps to dates
419-
        if (isset($listEntry['Time'])) {
419+
        if (isset($listEntry['Time'])) {
420-
            $listEntry['Date']   = static::timeToDate($listEntry['Time'],   $connection);
420+
            $listEntry['Date']   = static::timeToDate($listEntry['Time'],   $connection);
421-
            unset($listEntry['Time']);
421+
            unset($listEntry['Time']);
422-
        }
422+
        }
423-
        if (isset($listEntry['Update'])) {
423+
        if (isset($listEntry['Update'])) {
424-
            $listEntry['Update'] = static::timeToDate($listEntry['Update'], $connection);
424+
            $listEntry['Update'] = static::timeToDate($listEntry['Update'], $connection);
425-
            unset($listEntry['Update']);
425+
            unset($listEntry['Update']);
426-
        }
426+
        }
427-
        if (isset($listEntry['Access'])) {
427+
        if (isset($listEntry['Access'])) {
428-
            $listEntry['Access'] = static::timeToDate($listEntry['Access'], $connection);
428+
            $listEntry['Access'] = static::timeToDate($listEntry['Access'], $connection);
429-
            unset($listEntry['Access']);
429+
            unset($listEntry['Access']);
430-
        }
430+
        }
431-
431+
432-
        // instantiate new spec object and set raw field values.
432+
        // instantiate new spec object and set raw field values.
433-
        $spec = new static($connection);
433+
        $spec = new static($connection);
434-
        $spec->setRawValues($listEntry)
434+
        $spec->setRawValues($listEntry)
435-
             ->deferPopulate();
435+
             ->deferPopulate();
436-
436+
437-
        return $spec;
437+
        return $spec;
438-
    }
438+
    }
439-
439+
440-
    /**
440+
    /**
441-
     * Convert the given unix timestamp into the server's typical date
441+
     * Convert the given unix timestamp into the server's typical date
442-
     * format accounting for the server's current timezone.
442+
     * format accounting for the server's current timezone.
443-
     *
443+
     *
444-
     * @param   int|string          $time       the timestamp to convert
444+
     * @param   int|string          $time       the timestamp to convert
445-
     * @param   ConnectionInterface $connection the connection to use
445+
     * @param   ConnectionInterface $connection the connection to use
446-
     * @return  string              date in the typical server format
446+
     * @return  string              date in the typical server format
447-
     */
447+
     */
448-
    protected static function timeToDate($time, ConnectionInterface $connection)
448+
    protected static function timeToDate($time, ConnectionInterface $connection)
449-
    {
449+
    {
450-
        $date = new \DateTime('@' . $time);
450+
        $date = new \DateTime('@' . $time);
451-
451+
452-
        // try and use the p4 info timezone, if that fails fall back to our local timezone
452+
        // try and use the p4 info timezone, if that fails fall back to our local timezone
453-
        try {
453+
        try {
454-
            $date->setTimeZone($connection->getTimeZone());
454+
            $date->setTimeZone($connection->getTimeZone());
455-
        } catch (\Exception $e) {
455+
        } catch (\Exception $e) {
456-
            // we tried and failed; just let it use php's default time zone
456+
            // we tried and failed; just let it use php's default time zone
457-
        }
457+
            // note when creating a DateTime from a unix timestamp the timezone will
458-
458+
            // be UTC, we need to explicitly set it to the default time zone.
459-
        return $date->format('Y/m/d H:i:s');
459+
            $date->setTimeZone(new \DateTimeZone(date_default_timezone_get()));
460-
    }
460+
        }
461-
461+
462-
    /**
462+
        return $date->format('Y/m/d H:i:s');
463-
     * Inverse function to timeToDate(), it converts the given date in server's typical
463+
    }
464-
     * format into a unix timestamp accounting for the server's current timezone.
464+
465-
     *
465+
    /**
466-
     * @param  string               $date           date in typical server's format (Y/m/d H:i:s) to convert
466+
     * Inverse function to timeToDate(), it converts the given date in server's typical
467-
     * @param  ConnectionInterface  $connection     the connection to use
467+
     * format into a unix timestamp accounting for the server's current timezone.
468-
     * @return int|false            date in unix timestamp or false if unable to convert
468+
     *
469-
     */
469+
     * @param  string               $date           date in typical server's format (Y/m/d H:i:s) to convert
470-
    protected static function dateToTime($date, ConnectionInterface $connection)
470+
     * @param  ConnectionInterface  $connection     the connection to use
471-
    {
471+
     * @return int|false            date in unix timestamp or false if unable to convert
472-
        // try and use the p4 info timezone, if that fails fall back to our local timezone
472+
     */
473-
        $dateTimeZone = null;
473+
    protected static function dateToTime($date, ConnectionInterface $connection)
474-
        try {
474+
    {
475-
            $dateTimeZone = $connection->getTimeZone();
475+
        // try and use the p4 info timezone, if that fails fall back to our local timezone
476-
        } catch (\Exception $e) {
476+
        $dateTimeZone = null;
477-
            // we tried and failed; just let it use php's default time zone
477+
        try {
478-
            // note when creating a DateTime from a unix timestamp the timezone will
478+
            $dateTimeZone = $connection->getTimeZone();
479-
            // be UTC, we need to explicitly set it to the default time zone.
479+
        } catch (\Exception $e) {
480-
            $date->setTimeZone(new \DateTimeZone(date_default_timezone_get()));
480+
            // we tried and failed; just let it use php's default time zone
481-
        }
481+
        }
482-
482+
483-
        $dateTime = $dateTimeZone
483+
        $dateTime = $dateTimeZone
484-
            ? \DateTime::createFromFormat('Y/m/d H:i:s', $date, $dateTimeZone)
484+
            ? \DateTime::createFromFormat('Y/m/d H:i:s', $date, $dateTimeZone)
485-
            : \DateTime::createFromFormat('Y/m/d H:i:s', $date);
485+
            : \DateTime::createFromFormat('Y/m/d H:i:s', $date);
486-
486+
487-
        return $dateTime ? (int) $dateTime->format('U') : false;
487+
        return $dateTime ? (int) $dateTime->format('U') : false;
488-
    }
488+
    }
489-
489+
490-
    /**
490+
    /**
491-
     * Produce set of flags for the spec list command, given fetch all options array.
491+
     * Produce set of flags for the spec list command, given fetch all options array.
492-
     *
492+
     *
493-
     * @param   array   $options    array of options to augment fetch behavior.
493+
     * @param   array   $options    array of options to augment fetch behavior.
494-
     *                              see fetchAll for documented options.
494+
     *                              see fetchAll for documented options.
495-
     * @return  array   set of flags suitable for passing to spec list command.
495+
     * @return  array   set of flags suitable for passing to spec list command.
496-
     */
496+
     */
497-
    protected static function getFetchAllFlags($options)
497+
    protected static function getFetchAllFlags($options)
498-
    {
498+
    {
499-
        $flags = array();
499+
        $flags = array();
500-
500+
501-
        if (isset($options[self::FETCH_MAXIMUM])) {
501+
        if (isset($options[self::FETCH_MAXIMUM])) {
502-
            $flags[] = "-m";
502+
            $flags[] = "-m";
503-
            $flags[] = (int) $options[self::FETCH_MAXIMUM];
503+
            $flags[] = (int) $options[self::FETCH_MAXIMUM];
504-
        }
504+
        }
505-
505+
506-
        return $flags;
506+
        return $flags;
507-
    }
507+
    }
508-
508+
509-
    /**
509+
    /**
510-
     * Get the fetch all command, generally a plural version of the spec type.
510+
     * Get the fetch all command, generally a plural version of the spec type.
511-
     *
511+
     *
512-
     * @return  string  Perforce command to use for fetchAll
512+
     * @return  string  Perforce command to use for fetchAll
513-
     */
513+
     */
514-
    protected static function getFetchAllCommand()
514+
    protected static function getFetchAllCommand()
515-
    {
515+
    {
516-
        // derive list command from spec type by adding 's'
516+
        // derive list command from spec type by adding 's'
517-
        // this works for most of the known plural specs
517+
        // this works for most of the known plural specs
518-
        return static::SPEC_TYPE . "s";
518+
        return static::SPEC_TYPE . "s";
519-
    }
519+
    }
520
}