Blame view

libraries/vendor/joomla/data/src/DataObject.php 8.6 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
<?php
/**
 * Part of the Joomla Framework Data Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Data;

use Joomla\Registry\Registry;

/**
 * DataObject is a class that is used to store data but allowing you to access the data
 * by mimicking the way PHP handles class properties.
 *
 * @since  1.0
 */
class DataObject implements DumpableInterface, \IteratorAggregate, \JsonSerializable, \Countable
{
	/**
	 * The data object properties.
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $properties = array();

	/**
	 * The class constructor.
	 *
	 * @param   mixed  $properties  Either an associative array or another object
	 *                              by which to set the initial properties of the new object.
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($properties = array())
	{
		// Check the properties input.
		if (!empty($properties))
		{
			// Bind the properties.
			$this->bind($properties);
		}
	}

	/**
	 * The magic get method is used to get a data property.
	 *
	 * This method is a public proxy for the protected getProperty method.
	 *
	 * Note: Magic __get does not allow recursive calls. This can be tricky because the error generated by recursing into
	 * __get is "Undefined property:  {CLASS}::{PROPERTY}" which is misleading. This is relevant for this class because
	 * requesting a non-visible property can trigger a call to a sub-function. If that references the property directly in
	 * the object, it will cause a recursion into __get.
	 *
	 * @param   string  $property  The name of the data property.
	 *
	 * @return  mixed  The value of the data property, or null if the data property does not exist.
	 *
	 * @see     DataObject::getProperty()
	 * @since   1.0
	 */
	public function __get($property)
	{
		return $this->getProperty($property);
	}

	/**
	 * The magic isset method is used to check the state of an object property.
	 *
	 * @param   string  $property  The name of the data property.
	 *
	 * @return  boolean  True if set, otherwise false is returned.
	 *
	 * @since   1.0
	 */
	public function __isset($property)
	{
		return isset($this->properties[$property]);
	}

	/**
	 * The magic set method is used to set a data property.
	 *
	 * This is a public proxy for the protected setProperty method.
	 *
	 * @param   string  $property  The name of the data property.
	 * @param   mixed   $value     The value to give the data property.
	 *
	 * @return  void
	 *
	 * @see     DataObject::setProperty()
	 * @since   1.0
	 */
	public function __set($property, $value)
	{
		$this->setProperty($property, $value);
	}

	/**
	 * The magic unset method is used to unset a data property.
	 *
	 * @param   string  $property  The name of the data property.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function __unset($property)
	{
		unset($this->properties[$property]);
	}

	/**
	 * Binds an array or object to this object.
	 *
	 * @param   mixed    $properties   An associative array of properties or an object.
	 * @param   boolean  $updateNulls  True to bind null values, false to ignore null values.
	 *
	 * @return  DataObject  Returns itself to allow chaining.
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function bind($properties, $updateNulls = true)
	{
		// Check the properties data type.
		if (!is_array($properties) && !is_object($properties))
		{
			throw new \InvalidArgumentException(sprintf('%s(%s)', __METHOD__, gettype($properties)));
		}

		// Check if the object is traversable.
		if ($properties instanceof \Traversable)
		{
			// Convert iterator to array.
			$properties = iterator_to_array($properties);
		}
		elseif (is_object($properties))
		// Check if the object needs to be converted to an array.
		{
			// Convert properties to an array.
			$properties = (array) $properties;
		}

		// Bind the properties.
		foreach ($properties as $property => $value)
		{
			// Check if the value is null and should be bound.
			if ($value === null && !$updateNulls)
			{
				continue;
			}

			// Set the property.
			$this->setProperty($property, $value);
		}

		return $this;
	}

	/**
	 * Dumps the data properties into a stdClass object, recursively if appropriate.
	 *
	 * @param   integer            $depth   The maximum depth of recursion (default = 3).
	 *                                      For example, a depth of 0 will return a stdClass with all the properties in native
	 *                                      form. A depth of 1 will recurse into the first level of properties only.
	 * @param   \SplObjectStorage  $dumped  An array of already serialized objects that is used to avoid infinite loops.
	 *
	 * @return  \stdClass  The data properties as a simple PHP stdClass object.
	 *
	 * @since   1.0
	 */
	public function dump($depth = 3, \SplObjectStorage $dumped = null)
	{
		// Check if we should initialise the recursion tracker.
		if ($dumped === null)
		{
			$dumped = new \SplObjectStorage;
		}

		// Add this object to the dumped stack.
		$dumped->attach($this);

		// Setup a container.
		$dump = new \stdClass;

		// Dump all object properties.
		foreach (array_keys($this->properties) as $property)
		{
			// Get the property.
			$dump->$property = $this->dumpProperty($property, $depth, $dumped);
		}

		return $dump;
	}

	/**
	 * Gets this object represented as an ArrayIterator.
	 *
	 * This allows the data properties to be access via a foreach statement.
	 *
	 * @return  \ArrayIterator  This object represented as an ArrayIterator.
	 *
	 * @see     IteratorAggregate::getIterator()
	 * @since   1.0
	 */
	public function getIterator()
	{
		return new \ArrayIterator($this->dump(0));
	}

	/**
	 * Gets the data properties in a form that can be serialised to JSON format.
	 *
	 * @return  string  An object that can be serialised by json_encode().
	 *
	 * @since   1.0
	 */
	public function jsonSerialize()
	{
		return $this->dump();
	}

	/**
	 * Dumps a data property.
	 *
	 * If recursion is set, this method will dump any object implementing Data\Dumpable (like Data\Object and Data\Set); it will
	 * convert a Date object to a string; and it will convert a Registry to an object.
	 *
	 * @param   string             $property  The name of the data property.
	 * @param   integer            $depth     The current depth of recursion (a value of 0 will ignore recursion).
	 * @param   \SplObjectStorage  $dumped    An array of already serialized objects that is used to avoid infinite loops.
	 *
	 * @return  mixed  The value of the dumped property.
	 *
	 * @since   1.0
	 */
	protected function dumpProperty($property, $depth, \SplObjectStorage $dumped)
	{
		$value = $this->getProperty($property);

		if ($depth > 0)
		{
			// Check if the object is also an dumpable object.
			if ($value instanceof DumpableInterface)
			{
				// Do not dump the property if it has already been dumped.
				if (!$dumped->contains($value))
				{
					$value = $value->dump($depth - 1, $dumped);
				}
			}

			// Check if the object is a date.
			if ($value instanceof \DateTime)
			{
				$value = $value->format('Y-m-d H:i:s');
			}
			elseif ($value instanceof Registry)
			// Check if the object is a registry.
			{
				$value = $value->toObject();
			}
		}

		return $value;
	}

	/**
	 * Gets a data property.
	 *
	 * @param   string  $property  The name of the data property.
	 *
	 * @return  mixed  The value of the data property.
	 *
	 * @see     DataObject::__get()
	 * @since   1.0
	 */
	protected function getProperty($property)
	{
		// Get the raw value.
		$value = array_key_exists($property, $this->properties) ? $this->properties[$property] : null;

		return $value;
	}

	/**
	 * Sets a data property.
	 *
	 * If the name of the property starts with a null byte, this method will return null.
	 *
	 * @param   string  $property  The name of the data property.
	 * @param   mixed   $value     The value to give the data property.
	 *
	 * @return  mixed  The value of the data property.
	 *
	 * @see     DataObject::__set()
	 * @since   1.0
	 */
	protected function setProperty($property, $value)
	{
		/*
		 * Check if the property starts with a null byte. If so, discard it because a later attempt to try to access it
		 * can cause a fatal error. See http://us3.php.net/manual/en/language.types.array.php#language.types.array.casting
		 */
		if (strpos($property, "\0") === 0)
		{
			return null;
		}

		// Set the value.
		$this->properties[$property] = $value;

		return $value;
	}

	/**
	 * Count the number of data properties.
	 *
	 * @return  integer  The number of data properties.
	 *
	 * @since   1.0
	 */
	public function count()
	{
		return count($this->properties);
	}
}