/** * Search for an element. * @param $index to search $skey at $kpos'th position regarding the * the element layout * @param $skey string key value to lookup at position $kpos * @param $kpos position to search for $skey * @param $kval value to compare the last serialized object with * @param $type search type, "native" or "avltree" * @return the first complete element found, otherwise an empty string */ public function search($index = -1, $skey = "", $kpos = 0, $kval = null, $type = "native") { if (strncmp(gettype($type), "string", 6) == 0 && strncmp($type, "native", 6) == 0) { if (strncmp(gettype($index), "integer", 7) == 0 && strncmp(gettype($skey), "string", 6) == 0 && strncmp(gettype($kpos), "integer", 7) == 0) { // get the element stored at $index or all on failure $elems = ""; if ($index > -1) { $elems = $this->get($index); } // find first match for $skey at $kpos in $elems // and compare the last entry with $kval $dl = $this->element->get_shm_seg_var_eleft(); $dr = $this->element->get_shm_seg_var_eright(); foreach ($elems as $k => $v) { // first check if there is an $skey $mix = StringUtil::get_offset_first($dl . $skey . $dr, $v); // if $skey was found if ($mix !== -1) { // substring length for the match $sub = $substr($v, 0, $mix); $sl = strlen($sub); $c = 0; // $sl could only be 0 if it is located at the // first 0th position due to substr($v,0,0) is empty // check if it is the correct $kpos and if left side delimiter(s) // were found $dlb = unpack("C*", $dl)[1]; // 91 for "[" $cc = count_chars($sub, 1); if ($sl > 0 && array_key_exists($dlb, $cc)) { if ($kpos === $cc[$dlb] - 1 && $kval === null) { return $v; } else { if ($kpos === $cc[$dlb] - 1 && $kval === null) { // check the serialized version of $kval passed against the last // term of the current element, this check is optional but when // it is passed it need to be checked //$sv = $this->get_payload($k); $sv = $this->get_payload($v); if (strncmp($sv, serialize($kval), strlen($sv)) == 0) { return $v; } } } } else { $this->log(__METHOD__ . ": %", array(new DelimiterNotFoundException("'" . $dl . "', " . $sub))); throw new DelimiterNotFoundException("'" . $dl . "', " . $sub); } } } return ""; } else { $this->log(__METHOD__ . ": %", array(new ParamNotValidException("index(int)=" . gettype($index) . ", skey(string)=" . gettype($skey) . ", kpos(int)=" . gettype($kpos)))); throw new ParamNotValidExcepton("index(int)=" . gettype($index) . ", skey(string)=" . gettype($skey) . ", kpos(int)=" . gettype($kpos)); } } else { if (strncmp(gettype($type), "string", 6) == 0 && strncmp($type, "avltree", 6) == 0) { // TODO: probably use AVLTreeImplementation, see above $avltree // NOTE: probably just useful if there is a way to keep an already // created avl object in a way restoring is not exhausting } else { $this->log(__METHOD__ . ": %", array(new ParamNotValidException("type(string)=" . gettype($type)))); throw new ParamNotValidExcepton("type(string)=" . gettype($kpos)); } } }
/** * Write $values to the shared memory segment in the form * '[val1][...][valn-1][serialize(valn)];'. Each value need * to match the memory layout related which is currently set * for the segment. * @param $values array containing values * @param $key postion to write to. -1 means simply append. * @throws SegmentException * @return number of bytes written. */ public function write($values = array(), $key = -1) { $written_data = 0; if (!is_null($this->element) && strncmp(gettype($values), "array", 5) == 0) { // build string to put first $pstr = ""; $l = $this->element->get_shm_seg_var_eleft(); $r = $this->element->get_shm_seg_var_eright(); $d = $this->element->get_shm_seg_var_delimiter(); foreach (array_values($values) as $k => $v) { if ($k == count($values) - 1) { $pstr = $pstr . $l . serialize($v) . $r . $d; } else { $pstr = $pstr . $l . $v . $r; } } $this->log(__METHOD__ . ": pstr=%, key=%", array($pstr, $key)); // check it against the memory layout first $has_layout = false; if (!StringUtil::has_layout($this->element->get_shm_seg_layout(), $pstr)) { $this->log(__METHOD__ . ": %", array(new SegmentException("layout=" . $this->element->get_shm_seg_layout() . ", data=" . $pstr, 2))); throw new SegmentException("layout=" . $this->element->get_shm_seg_layout() . ", data=" . $pstr, 2); } else { $has_layout = true; } $woffset = -1; $has_space = false; // NOTE: concatenation of delimiter string needs to be done // for performance reasons when it is necessary $smr = new SharedMemoryReader($this->element); // get the lenght of the current data stored $seglen = $smr->getlen(); // segment type $segtype = $this->element->get_shm_seg_type(); // get an indexable segment version $xseg = $smr->get(); // unsetting the temporary element string $this->telement = ""; $this->log(__METHOD__ . ": slen=%, len=%, type=%, cws=%", array($seglen, strlen($smr->read()), gettype($smr->read()), preg_replace("/[\t ]+/", " ", preg_replace("/[\r\n]/", " ", var_export(count_chars($smr->read()), true))))); // get the offset for simply appending if ($key == -1) { // space required $required = StringUtil::get_offset_last($r . $d, $pstr) + strlen($r . $d); // offset for next entry to put //$woffset = $this->element->get_shm_seg_size() - $seglen; $woffset = $seglen; // check the space left/needed before writing // fifo segment if (strncmp($segtype, "fifo", 4) == 0) { // if the new fully entry does not fit into the segment we free some // space according to the segment strategy but only if the new entry // fits into the segment itself to avoid uneccessary flushing if the entry // is to big for the empty segment if (!($woffsef >= 0 && $woffset + $required <= $this->element->get_shm_seg_size()) && strlen($pstr) <= $this->element->get_shm_seg_size()) { // fifo, free oldest entries first $this->telement = $smr->read(); while (strlen($this->telement) > 0 && $woffset + $required > $this->element->get_shm_seg_size()) { // get the next entry to discard $discardoffset = StringUtil::get_offset_first($r . $d, $this->telement) + strlen($r . $d) - 1; // free $this->telement = substr($this->telement, $discardoffset); // recalc free space $woffset = $this->element->get_shm_seg_size() - strlen($this->telement); } $has_space = true; // otherwise there is something wrong with the space } else { $has_space = false; $this->log(__METHOD__ . ": %", array(new SegmentException("freeing failed", 0))); throw new SegmentException("freeing failed", 0); } // lifo segment } else { if (strncmp($segtype, "lifo", 4) == 0) { // stor segment } else { if (strncmp($segtype, "stor", 4) == 0) { // simply check if there is still place left to push elements if ($seglen + strlen($pstr) <= $this->element->get_shm_seg_size()) { $has_space = true; } else { $this->log(__METHOD__ . ": %", array(new SegmentException("no more space", 1))); throw new SegmentException("no more space", 1); } } } } // writing to a specific index is a bit more complex // get the offset for writing to an index } else { if (strncmp(gettype($key), "integer", 7) == 0 && $key > -1) { // if its a valid key if ($key <= count($xseg) - 1) { // calculate new minimum length for data stored $segnlen = $seglen - strlen($xseg[$key] . $this->element->get_shm_seg_var_eright() . $this->element->get_shm_seg_var_delimiter()) + strlen($pstr); // auto free space for fifo/lifo segment types if there is no more // space left // fifo segment if (strncmp($segtype, "fifo", 4) == 0) { // if the new segment data length is to big if ($segnlen > $this->element->get_shm_seg_size()) { $difflen = $segnlen - $this->element->get_shm_seg_size(); $tmp_key = 0; while ($difflen > 0) { $difflen -= strlen(trim($xseg[$tmp_key]) . $this->element->get_shm_seg_var_eright() . $this->element->get_shm_seg_var_delimiter()); unset($xseg[$tmp_key]); $tmp_key += 1; // increase it as unset doesn't modify indexes } } // after cleaning the fifo queue there should be enough space $has_space = true; // lifo segment } else { if (strncmp($segtype, "lifo", 4) == 0) { // if the new segment data length is to big if ($segnlen > $this->element->get_shm_seg_size()) { $difflen = $segnlen - $this->element->get_shm_seg_size(); $tmp_key = count($xseg) - 1; while ($difflen > 0) { $difflen -= strlen(trim($xseg[$tmp_key]) . $this->element->get_shm_seg_var_eright() . $this->element->get_shm_seg_var_delimiter()); unset($xseg[$tmp_key]); $tmp_key -= 1; // increase it as unset doesn't modify indexes } } // after cleaning the lifo queue there should be enough space $has_space = true; // if the segment type is 'stor' simply try to store it or throw an // exception if the length do not fit } else { if (strncmp($segtype, "stor", 4) == 0) { // if the modified length is less or equal the segment size // simply write it if ($segnlen <= $this->element->get_shm_seg_size()) { $has_space = true; } else { $has_space = false; $this->log(__METHOD__ . ": %", array(new SegmentException("no more space", 1))); throw new SegmentException("no more space", 1); } } } } } } else { $this->log(__METHOD__ . ": %", array(new ParamNotValidException(__METHOD__ . ": key(int)=" . var_export($key, true)))); throw new ParamNotValidException(__METHOD__ . ": key(int)=" . var_export($key, true)) . "\n"; } } // write it using shmop_write if ($has_layout && $has_space && $woffset > -1 && $this->element->get_shm_seg_id() > -1) { // TODO: WriterThread to allow multiple writes on different // but "correctly calced" locations/offsets $this->log(__METHOD__ . ": tel=%, tels=%, pstr=%, pstrs=%, els=%, " . "woff=%, woff+lpstr=%, segs-(woff+lpstr)=%", array($this->telement, strlen($this->telement), $pstr, strlen($pstr), $this->element->get_shm_seg_size(), $woffset, $woffset + strlen($pstr), $this->element->get_shm_seg_size() - ($woffset + strlen($pstr)))); // write modified prefix $written_prefix = shmop_write($this->element->get_shm_seg_id(), $this->telement, 0); // write additional entry from offset $written_data = shmop_write($this->element->get_shm_seg_id(), $pstr, $woffset); // write additional entry from offset // TODO: only write flush/whitespace pad for overlapping bytes at reduction $written_flush = shmop_write($this->element->get_shm_seg_id(), str_pad("", $this->element->get_shm_seg_size() - ($woffset + strlen($pstr)), " "), $woffset + strlen($pstr)); $this->log(__METHOD__ . ": wp=%, wd=%, wf=%", array($written_prefix, $written_data, $written_flush)); if ($written_prefix === false || $written_data === false || $written_flush === false) { $this->log(__METHOD__ . "%", array(new SegmentException($this->element, 4))); throw new SegmentException($this->element, 4); } } } // close if return $written_data; }