vendor/jackalope/jackalope/src/Jackalope/Property.php line 617

Open in your IDE?
  1. <?php
  2. namespace Jackalope;
  3. use Exception;
  4. use LogicException;
  5. use ArrayIterator;
  6. use IteratorAggregate;
  7. use InvalidArgumentException;
  8. use PHPCR\AccessDeniedException;
  9. use PHPCR\Lock\LockException;
  10. use PHPCR\NodeType\ConstraintViolationException;
  11. use PHPCR\NodeType\NodeTypeInterface;
  12. use PHPCR\NoSuchWorkspaceException;
  13. use PHPCR\PropertyInterface;
  14. use PHPCR\PropertyType;
  15. use PHPCR\RepositoryException;
  16. use PHPCR\ValueFormatException;
  17. use PHPCR\InvalidItemStateException;
  18. use PHPCR\ItemNotFoundException;
  19. use PHPCR\NodeType\PropertyDefinitionInterface;
  20. use PHPCR\Util\PathHelper;
  21. use PHPCR\Util\UUIDHelper;
  22. use PHPCR\Version\VersionException;
  23. /**
  24.  * {@inheritDoc}
  25.  *
  26.  * @license http://www.apache.org/licenses Apache License Version 2.0, January 2004
  27.  * @license http://opensource.org/licenses/MIT MIT License
  28.  *
  29.  * @api
  30.  */
  31. class Property extends Item implements IteratorAggregatePropertyInterface
  32. {
  33.     /**
  34.      * flag to know if binary streams should be wrapped or retrieved
  35.      * immediately. this is a per session setting.
  36.      *
  37.      * @var boolean
  38.      * @see Property::__construct()
  39.      */
  40.     protected $wrapBinaryStreams;
  41.     /**
  42.      * All binary stream wrapper instances
  43.      * @var array
  44.      */
  45.     protected $streams = [];
  46.     /**
  47.      * The property value in suitable native format or object
  48.      * @var mixed
  49.      */
  50.     protected $value;
  51.     /**
  52.      * length is only used for binary property, because binary loading is delayed until explicitly requested.
  53.      *
  54.      * @var int
  55.      */
  56.     protected $length;
  57.     /**
  58.      * whether this is a multivalue property
  59.      * @var boolean
  60.      */
  61.     protected $isMultiple false;
  62.     /**
  63.      * the type constant from PropertyType
  64.      * @var int
  65.      */
  66.     protected $type PropertyType::UNDEFINED;
  67.     /**
  68.      * cached instance of the property definition that defines this property
  69.      * @var PropertyDefinitionInterface
  70.      * @see Property::getDefinition()
  71.      */
  72.     protected $definition;
  73.     /**
  74.      * Create a property, either from server data or locally
  75.      *
  76.      * To indicate a property has newly been created locally, make sure to pass
  77.      * true for the $new parameter. In that case, you should pass an empty array
  78.      * for $data and use setValue afterwards to let the type magic be handled.
  79.      * Then multivalue is determined on setValue
  80.      *
  81.      * For binary properties, the value is the length of the data(s), not the
  82.      * data itself.
  83.      *
  84.      * @param FactoryInterface $factory the object factory
  85.      * @param array            $data    array with fields <tt>type</tt>
  86.      *      (integer or string from PropertyType) and <tt>value</tt> (data for
  87.      *      creating the property value - array for multivalue property)
  88.      * @param string        $path          the absolute path of this item
  89.      * @param Session       $session       the session instance
  90.      * @param ObjectManager $objectManager the objectManager instance - the
  91.      *      caller has to take care of registering this item with the object
  92.      *      manager
  93.      * @param boolean $new optional: set to true to make this property aware
  94.      *      its not yet existing on the server. defaults to false
  95.      *
  96.      * @throws RepositoryException
  97.      * @throws \InvalidArgumentException
  98.      */
  99.     public function __construct(FactoryInterface $factory, array $data$pathSession $sessionObjectManager $objectManager$new false)
  100.     {
  101.         parent::__construct($factory$path$session$objectManager$new);
  102.         $this->wrapBinaryStreams $session->getRepository()->getDescriptor(Repository::JACKALOPE_OPTION_STREAM_WRAPPER);
  103.         if (null === $data && $new) {
  104.             return;
  105.         }
  106.         if (! isset($data['value'])) {
  107.             throw new InvalidArgumentException("Can't create property at $path without any data");
  108.         }
  109.         $this->_setValue($data['value'], isset($data['type']) ? $data['type'] : PropertyType::UNDEFINEDtrue);
  110.     }
  111.     /**
  112.      * {@inheritDoc}
  113.      *
  114.      * @throws InvalidItemStateException
  115.      * @throws AccessDeniedException
  116.      * @throws ItemNotFoundException
  117.      *
  118.      * @api
  119.      */
  120.     public function setValue($value$type PropertyType::UNDEFINED)
  121.     {
  122.         $this->checkState();
  123.         // need to determine type to avoid unnecessary modification
  124.         // don't try to determine if the value changed anyway (i.e. null to delete)
  125.         if (PropertyType::UNDEFINED === $type && $this->value === $value) {
  126.             $type $this->valueConverter->determineType($value);
  127.         }
  128.         // Need to check both value and type, as native php type string is used for a number of phpcr types
  129.         if ($this->value !== $value || $this->type !== $type) {
  130.             if ($this->getDefinition()->isProtected()) {
  131.                 $violation true;
  132.                 if ('jcr:mixinTypes' === $this->getDefinition()->getName()) {
  133.                     // check that the jcr:mixinTypes are in sync with the mixin node types
  134.                     $mixins = [];
  135.                     foreach ($this->getParent()->getMixinNodeTypes() as $mixin) {
  136.                         $mixins[] = $mixin->getName();
  137.                     }
  138.                     $violation = (bool) array_diff($mixins$this->value);
  139.                 }
  140.                 if ($violation) {
  141.                     $msg sprintf("Property '%s' of node type '%s' is protected and cannot be modified"$this->name$this->getDefinition()->getDeclaringNodeType()->getName());
  142.                     throw new ConstraintViolationException($msg);
  143.                 }
  144.             }
  145.             $this->setModified();
  146.         }
  147.         // The _setValue call MUST BE after the check to see if the value or type changed
  148.         $this->_setValue($value$type);
  149.     }
  150.     /**
  151.      * {@inheritDoc}
  152.      *
  153.      * @throws RepositoryException
  154.      * @throws \InvalidArgumentException
  155.      *
  156.      * @api
  157.      */
  158.     public function addValue($value)
  159.     {
  160.         $this->checkState();
  161.         if (!$this->isMultiple()) {
  162.             throw new ValueFormatException('You can not add values to non-multiple properties');
  163.         }
  164.         $this->value[] = $this->valueConverter->convertType($value$this->type);
  165.         $this->setModified();
  166.     }
  167.     /**
  168.      * Tell this item that it has been modified.
  169.      *
  170.      * Used to make the parent node aware that this property has changed
  171.      *
  172.      * @throws AccessDeniedException
  173.      * @throws ItemNotFoundException
  174.      * @throws RepositoryException
  175.      *
  176.      * @private
  177.      */
  178.     public function setModified()
  179.     {
  180.         parent::setModified();
  181.         $parent $this->getParent();
  182.         if (!is_null($parent)) {
  183.             $parent->setModified();
  184.         }
  185.     }
  186.     /**
  187.      * {@inheritDoc}
  188.      *
  189.      * @api
  190.      *
  191.      * @throws InvalidItemStateException
  192.      * @throws ItemNotFoundException
  193.      * @throws RepositoryException
  194.      * @throws ValueFormatException
  195.      */
  196.     public function getValue()
  197.     {
  198.         $this->checkState();
  199.         if ($this->type === PropertyType::REFERENCE
  200.             || $this->type === PropertyType::WEAKREFERENCE
  201.         ) {
  202.             return $this->getNode();
  203.         }
  204.         if ($this->type === PropertyType::BINARY) {
  205.             return $this->getBinary();
  206.         }
  207.         return $this->value;
  208.     }
  209.     /**
  210.      * Get the value of this property to store in the storage backend.
  211.      *
  212.      * Path and reference properties are not resolved to the node objects.
  213.      * If this is a binary property, from the moment this method has been
  214.      * called the stream will be read from the transport layer again.
  215.      *
  216.      * @throws InvalidItemStateException
  217.      *
  218.      * @private
  219.      */
  220.     public function getValueForStorage()
  221.     {
  222.         $this->checkState();
  223.         $value $this->value;
  224.         if (PropertyType::BINARY === $this->type) {
  225.             //from now on,
  226.             $this->value null;
  227.         }
  228.         return $value;
  229.     }
  230.     /**
  231.      * {@inheritDoc}
  232.      *
  233.      * @throws InvalidItemStateException
  234.      * @throws InvalidArgumentException
  235.      *
  236.      * @api
  237.      */
  238.     public function getString()
  239.     {
  240.         $this->checkState();
  241.         if ($this->type === PropertyType::BINARY && empty($this->value)) {
  242.             return $this->valueConverter->convertType($this->getBinary(), PropertyType::STRING$this->type);
  243.         }
  244.         if ($this->type !== PropertyType::STRING) {
  245.             return $this->valueConverter->convertType($this->valuePropertyType::STRING$this->type);
  246.         }
  247.         return $this->value;
  248.     }
  249.     /**
  250.      * {@inheritDoc}
  251.      *
  252.      * @throws InvalidArgumentException
  253.      * @throws LogicException
  254.      *
  255.      * @api
  256.      */
  257.     public function getBinary()
  258.     {
  259.         $this->checkState();
  260.         if ($this->type !== PropertyType::BINARY) {
  261.             return $this->valueConverter->convertType($this->valuePropertyType::BINARY$this->type);
  262.         }
  263.         if (! $this->wrapBinaryStreams && null === $this->value) {
  264.             // no caching the stream. we need to fetch the stream and then copy
  265.             // it into a memory stream so it can be accessed more than once.
  266.             $this->value $this->objectManager->getBinaryStream($this->path);
  267.         }
  268.         if ($this->value !== null) {
  269.             // we have the stream locally: no wrapping or new or updated property
  270.             // copy the stream so the original stream stays usable for storing, fetching again...
  271.             $val is_array($this->value) ? $this->value : [$this->value];
  272.             $ret = [];
  273.             foreach ($val as $s) {
  274.                 $stream fopen('php://memory''rwb+');
  275.                 $pos ftell($s);
  276.                 stream_copy_to_stream($s$stream);
  277.                 rewind($stream);
  278.                 fseek($s$pos); //go back to previous position
  279.                 $ret[] = $stream;
  280.             }
  281.             return is_array($this->value) ? $ret $ret[0];
  282.         }
  283.         if (! $this->wrapBinaryStreams) {
  284.             throw new LogicException("Attempting to create 'jackalope' stream instances but stream wrapper is not activated");
  285.         }
  286.         // return wrapped stream
  287.         if ($this->isMultiple()) {
  288.             $results = [];
  289.             // identifies all streams loaded by one backend call
  290.             $token md5(uniqid(mt_rand(), true));
  291.             // start with part = 1 since 0 will not be parsed properly by parse_url
  292.             for ($i 1$iMax count($this->length); $i <= $iMax$i++) {
  293.                 $this->streams[] = $results[] = fopen('jackalope://' $token'@' $this->session->getRegistryKey() . ':' $i $this->path'rwb+');
  294.             }
  295.             return $results;
  296.         }
  297.         // single property case
  298.         $result fopen('jackalope://' $this->session->getRegistryKey() . $this->path'rwb+');
  299.         $this->streams[] = $result;
  300.         return $result;
  301.     }
  302.     /**
  303.      * {@inheritDoc}
  304.      *
  305.      * @throws InvalidItemStateException
  306.      * @throws InvalidArgumentException
  307.      *
  308.      * @api
  309.      */
  310.     public function getLong()
  311.     {
  312.         $this->checkState();
  313.         if ($this->type !== PropertyType::LONG) {
  314.             return $this->valueConverter->convertType($this->valuePropertyType::LONG$this->type);
  315.         }
  316.         return $this->value;
  317.     }
  318.     /**
  319.      * {@inheritDoc}
  320.      *
  321.      * @throws InvalidArgumentException
  322.      *
  323.      * @api
  324.      */
  325.     public function getDouble()
  326.     {
  327.         $this->checkState();
  328.         if ($this->type !== PropertyType::DOUBLE) {
  329.             return $this->valueConverter->convertType($this->valuePropertyType::DOUBLE$this->type);
  330.         }
  331.         return $this->value;
  332.     }
  333.     /**
  334.      * {@inheritDoc}
  335.      *
  336.      * @throws InvalidItemStateException
  337.      * @throws InvalidArgumentException
  338.      *
  339.      * @api
  340.      */
  341.     public function getDecimal()
  342.     {
  343.         $this->checkState();
  344.         if ($this->type !== PropertyType::DECIMAL) {
  345.             return $this->valueConverter->convertType($this->valuePropertyType::DECIMAL$this->type);
  346.         }
  347.         return $this->value;
  348.     }
  349.     /**
  350.      * {@inheritDoc}
  351.      *
  352.      * @throws InvalidArgumentException
  353.      *
  354.      * @api
  355.      */
  356.     public function getDate()
  357.     {
  358.         $this->checkState();
  359.         if ($this->type !== PropertyType::DATE) {
  360.             return $this->valueConverter->convertType($this->valuePropertyType::DATE$this->type);
  361.         }
  362.         return $this->value;
  363.     }
  364.     /**
  365.      * {@inheritDoc}
  366.      *
  367.      * @throws InvalidArgumentException
  368.      *
  369.      * @api
  370.      */
  371.     public function getBoolean()
  372.     {
  373.         $this->checkState();
  374.         if ($this->type !== PropertyType::BOOLEAN) {
  375.             return $this->valueConverter->convertType($this->valuePropertyType::BOOLEAN$this->type);
  376.         }
  377.         return $this->value;
  378.     }
  379.     /**
  380.      * {@inheritDoc}
  381.      *
  382.      * @throws InvalidItemStateException
  383.      * @throws NoSuchWorkspaceException
  384.      *
  385.      * @api
  386.      */
  387.     public function getNode()
  388.     {
  389.         $this->checkState();
  390.         $values $this->isMultiple() ? $this->value : [$this->value];
  391.         $results = [];
  392.         switch ($this->type) {
  393.             case PropertyType::REFERENCE:
  394.                 $results $this->getReferencedNodes($valuesfalse);
  395.                 break;
  396.             case PropertyType::WEAKREFERENCE:
  397.                 $results $this->getReferencedNodes($valuestrue);
  398.                 break;
  399.             case PropertyType::STRING:
  400.                 foreach ($values as $value) {
  401.                     if (UUIDHelper::isUUID($value)) {
  402.                         $results[] = $this->objectManager->getNodeByIdentifier($value);
  403.                     } else {
  404.                         $results[] = $this->objectManager->getNode($value$this->parentPath);
  405.                     }
  406.                 }
  407.                 break;
  408.             case PropertyType::PATH:
  409.             case PropertyType::NAME:
  410.                 foreach ($values as $value) {
  411.                     // OPTIMIZE: use objectManager->getNodes instead of looping (but paths need to be absolute then)
  412.                     $results[] = $this->objectManager->getNode($value$this->parentPath);
  413.                 }
  414.                 break;
  415.             default:
  416.                 throw new ValueFormatException('Property is not a REFERENCE, WEAKREFERENCE or PATH (or convertible to PATH)');
  417.         }
  418.         return $this->isMultiple() ? $results reset($results);
  419.     }
  420.     /**
  421.      * {@inheritDoc}
  422.      *
  423.      * @throws InvalidItemStateException
  424.      *
  425.      * @api
  426.      */
  427.     public function getProperty()
  428.     {
  429.         $this->checkState();
  430.         $values $this->isMultiple() ? $this->value : [$this->value];
  431.         $results = [];
  432.         switch ($this->type) {
  433.             case PropertyType::PATH:
  434.             case PropertyType::STRING:
  435.             case PropertyType::NAME:
  436.                 foreach ($values as $value) {
  437.                     $results[] = $this->objectManager->getPropertyByPath(PathHelper::absolutizePath($value$this->parentPath));
  438.                 }
  439.                 break;
  440.             default:
  441.                 throw new ValueFormatException('Property is not a PATH (or convertible to PATH)');
  442.         }
  443.         return $this->isMultiple() ? $results $results[0];
  444.     }
  445.     /**
  446.      * {@inheritDoc}
  447.      *
  448.      * @api
  449.      */
  450.     public function getLength()
  451.     {
  452.         $this->checkState();
  453.         if (PropertyType::BINARY === $this->type) {
  454.             return $this->length;
  455.         }
  456.         $vals $this->isMultiple $this->value : [$this->value];
  457.         $ret = [];
  458.         foreach ($vals as $value) {
  459.             try {
  460.                 $ret[] = strlen($this->valueConverter->convertType($valuePropertyType::STRING$this->type));
  461.             } catch (Exception $e) {
  462.                 // @codeCoverageIgnoreStart
  463.                 $ret[] = -1;
  464.                 // @codeCoverageIgnoreEnd
  465.             }
  466.         }
  467.         return $this->isMultiple $ret $ret[0];
  468.     }
  469.     /**
  470.      * {@inheritDoc}
  471.      *
  472.      * @api
  473.      */
  474.     public function getDefinition()
  475.     {
  476.         $this->checkState();
  477.         if (empty($this->definition)) {
  478.             $this->definition $this->findItemDefinition(function (NodeTypeInterface $nt) {
  479.                 return $nt->getPropertyDefinitions();
  480.             });
  481.         }
  482.         return $this->definition;
  483.     }
  484.     /**
  485.      * {@inheritDoc}
  486.      *
  487.      * @api
  488.      */
  489.     public function getType()
  490.     {
  491.         $this->checkState();
  492.         return $this->type;
  493.     }
  494.     /**
  495.      * {@inheritDoc}
  496.      *
  497.      * @api
  498.      */
  499.     public function isMultiple()
  500.     {
  501.         $this->checkState();
  502.         return $this->isMultiple;
  503.     }
  504.     /**
  505.      * Also unsets internal reference in containing node
  506.      *
  507.      * {@inheritDoc}
  508.      *
  509.      * @uses Node::unsetProperty()
  510.      *
  511.      * @throws ItemNotFoundException
  512.      *
  513.      * @api
  514.      */
  515.     public function remove()
  516.     {
  517.         $this->checkState();
  518.         $parentNodeType $this->getParent()->getPrimaryNodeType();
  519.         //will throw a ConstraintViolationException if this property can't be removed
  520.         $parentNodeType->canRemoveProperty($this->getName(), true);
  521.         $this->getParent()->unsetProperty($this->name);
  522.         parent::remove();
  523.     }
  524.     /**
  525.      * Provide Traversable interface: redirect to getNodes with no filter
  526.      *
  527.      * @return \Iterator over all child nodes
  528.      *
  529.      * @throws InvalidItemStateException
  530.      * @throws ItemNotFoundException
  531.      * @throws RepositoryException
  532.      * @throws ValueFormatException
  533.      */
  534.     public function getIterator()
  535.     {
  536.         $this->checkState();
  537.         $value $this->getValue();
  538.         if (!is_array($value)) {
  539.             $value = [$value];
  540.         }
  541.         return new ArrayIterator($value);
  542.     }
  543.     /**
  544.      * Refresh this property
  545.      *
  546.      * {@inheritDoc}
  547.      *
  548.      * In Jackalope, this is also called internally to refresh when the node
  549.      * is accessed in state DIRTY.
  550.      *
  551.      * Triggers a reload of the containing node, as a property can only ever be
  552.      * loaded attached to a node.
  553.      *
  554.      * TODO: refactor this if we implement loading single properties
  555.      *
  556.      * @see Item::checkState
  557.      */
  558.     protected function refresh($keepChanges$internal false)
  559.     {
  560.         if ($this->isDeleted()) {
  561.             if ($internal) {
  562.                 // @codeCoverageIgnoreStart
  563.                 // FIXME: this should not be possible
  564.                 return;
  565.                 // @codeCoverageIgnoreEnd
  566.             }
  567.             throw new InvalidItemStateException('This property is deleted');
  568.         }
  569.         // Let the node refresh us
  570.         try {
  571.             // do not use getParent to avoid checkState - could lead to an endless loop
  572.             $this->objectManager->getNodeByPath($this->parentPath)->refresh($keepChanges);
  573.         } catch (ItemNotFoundException $e) {
  574.             $this->setDeleted();
  575.         }
  576.     }
  577.     /**
  578.      * Internally used to set the value of the property without any notification
  579.      * of changes nor state change.
  580.      *
  581.      * @param mixed      $value       The value to set.
  582.      * @param int|string $type        PropertyType constant
  583.      * @param boolean    $constructor Whether this is called from the constructor.
  584.      *
  585.      * @see Property::setValue()
  586.      *
  587.      * @throws AccessDeniedException
  588.      * @throws ItemNotFoundException
  589.      * @throws LockException
  590.      * @throws ConstraintViolationException
  591.      * @throws RepositoryException
  592.      * @throws VersionException
  593.      * @throws InvalidArgumentException
  594.      * @throws ValueFormatException
  595.      *
  596.      * @private
  597.      */
  598.     public function _setValue($value$type PropertyType::UNDEFINED$constructor false)
  599.     {
  600.         if (null === $value) {
  601.             $this->remove();
  602.             return;
  603.         }
  604.         if (is_string($type)) {
  605.             $type PropertyType::valueFromName($type);
  606.         } elseif (!is_numeric($type)) {
  607.             throw new RepositoryException("INTERNAL ERROR -- No valid type specified ($type)");
  608.         } else {
  609.             //sanity check. this will throw InvalidArgumentException if $type is not a valid type
  610.             PropertyType::nameFromValue($type);
  611.         }
  612.         if ($constructor || $this->isNew()) {
  613.             $this->isMultiple is_array($value);
  614.         }
  615.         if (is_array($value) && !$this->isMultiple) {
  616.             throw new ValueFormatException('Can not set a single value property ('.$this->name.') with an array of values');
  617.         }
  618.         if ($this->isMultiple && is_scalar($value)) {
  619.             throw new ValueFormatException('Can not set a multivalue property ('.$this->name.') with a scalar value');
  620.         }
  621.         if ($this->isMultiple) {
  622.             foreach ($value as $key => $v) {
  623.                 if (null === $v) {
  624.                     unset($value[$key]);
  625.                 }
  626.             }
  627.         }
  628.         //TODO: check if changing type allowed.
  629.         /*
  630.          * if ($type !== null && ! canHaveType($type)) {
  631.          *   throw new ConstraintViolationException("Can not set this property to type ".PropertyType::nameFromValue($type));
  632.          * }
  633.          */
  634.         if (PropertyType::UNDEFINED === $type) {
  635.             // avoid changing type of multivalue property with empty array
  636.             if (!$this->isMultiple()
  637.                 || count($value)
  638.                 || PropertyType::UNDEFINED === $this->type
  639.             ) {
  640.                 $type $this->valueConverter->determineType($value);
  641.             } else {
  642.                 $type $this->type;
  643.             }
  644.         }
  645.         $targetType $type;
  646.         /*
  647.          * TODO: find out with node type definition if the new type is allowed
  648.         if ($this->type !== $type) {
  649.             if (!canHaveType($type)) {
  650.                  //convert to an allowed type
  651.             }
  652.         }
  653.         */
  654.         if (PropertyType::BINARY !== $targetType
  655.             || $constructor && $this->isNew() // When in constructor mode, force conversion to re-determine the type as the desired type might not match the value
  656.             || !$this->isNew() && PropertyType::UNDEFINED !== $this->type && $this->type !== $targetType // changing an existing property to binary needs conversion
  657.         ) {
  658.             $value $this->valueConverter->convertType($value$targetType$constructor PropertyType::UNDEFINED $type);
  659.         }
  660.         if (PropertyType::BINARY === $targetType) {
  661.             if ($constructor && !$this->isNew()) {
  662.                 // reading a binary property from backend, we do not get the stream immediately but just the size
  663.                 if (is_array($value)) {
  664.                     $this->isMultiple true;
  665.                 }
  666.                 $this->type PropertyType::BINARY;
  667.                 $this->length $value;
  668.                 $this->value null;
  669.                 return;
  670.             }
  671.             if (is_array($value)) {
  672.                 $this->length = [];
  673.                 foreach ($value as $v) {
  674.                     $stat is_resource($v) ? fstat($v) : ['size' => -1];
  675.                     $this->length[] = $stat['size'];
  676.                 }
  677.             } elseif (is_resource($value)) {
  678.                 $stat fstat($value);
  679.                 $this->length $stat['size'];
  680.             } else {
  681.                 $this->length = -1;
  682.             }
  683.         }
  684.         $this->type $targetType;
  685.         $this->value $value;
  686.     }
  687.     /**
  688.      * Internally used after refresh from backend to set new length
  689.      *
  690.      * @param int $length the new length of this binary
  691.      *
  692.      * @private
  693.      */
  694.     public function _setLength($length)
  695.     {
  696.         $this->length $length;
  697.         $this->value null;
  698.     }
  699.     /**
  700.      * Close all open binary stream wrapper instances on shutdown.
  701.      */
  702.     public function __destruct()
  703.     {
  704.         foreach ($this->streams as $k => $v) {
  705.             // if this is not a resource, it means the stream has already been
  706.             // closed by client code
  707.             if (is_resource($v)) {
  708.                 fclose($v);
  709.                 unset($this->streams[$k]);
  710.             }
  711.         }
  712.     }
  713.     /**
  714.      * Get all nodes for $ids, ordered by that array, with duplicates if there are duplicates in $ids.
  715.      *
  716.      * @param string[] $ids  List of ids to fetch.
  717.      * @param boolean  $weak Whether these are weak references, to throw the right exception.
  718.      *
  719.      * @return Node[]
  720.      *
  721.      * @throws ItemNotFoundException If not all $ids are found and weak is true.
  722.      * @throws RepositoryException   If not all $ids are found and weak is false.
  723.      */
  724.     private function getReferencedNodes($ids$weak)
  725.     {
  726.         $results = [];
  727.         $nodes $this->objectManager->getNodesByIdentifier($ids);
  728.         $missing = [];
  729.         foreach ($ids as $id) {
  730.             if (isset($nodes[$id])) {
  731.                 $results[] = $nodes[$id];
  732.             } else {
  733.                 $missing[$id] = $id;
  734.             }
  735.         }
  736.         if (count($missing)) {
  737.             if ($weak) {
  738.                 throw new ItemNotFoundException(sprintf(
  739.                     'One or more weak reference targets have not been found: "%s".',
  740.                     implode('", "'$missing)
  741.                 ));
  742.             } else {
  743.                 throw new RepositoryException(sprintf(
  744.                     'Internal Error: Could not find one or more referenced nodes: "%s". ' .
  745.                     'If the referencing node is a frozen version, this can happen, otherwise it would be a bug.',
  746.                     implode('", "'$missing)
  747.                 ));
  748.             }
  749.         }
  750.         return $results;
  751.     }
  752. }