<commit_before><?php

namespace EntityPHP;

//Interface in order to force definition of method __structure()
interface iEntity
{
	public static function __structure();
}

//All classes inherited from this class can be bind to a table from DB
abstract class Entity implements iEntity
{
	protected	static	$table_name	=	null;
	protected	static	$id_name	=	'id';

	public function __construct(Array $props = array())
	{
		$this->setProps($props);
	}

	/**
	 * Prepare the data of the given Entity object for an INSERT or UPDATE SQL request
	 * @access private
	 * @param bool $update Do we have to prepare an UPDATE request?
	 * @return string SQL representation of the given value
	 * @throws \Exception
	 */
	final private function prepareDataForSQL($update = false)
	{
		$fields		=	static::__structure();
		$tableName	=	static::getTableName();

		$fieldsSQL	=	array();
		$valuesSQL	=	array();
		$foreignSQL	=	array();

		//Iterate through each properties of the class
		foreach($fields as $field => $sql_type)
		{
			$php_type	=	Core::getPHPType($sql_type);

			switch($php_type)
			{
				case Core::TYPE_INTEGER:
					$fieldsSQL[]	=	$field;
					$valuesSQL[]	=	intval($this->$field);
					break;

				case Core::TYPE_FLOAT:
					$fieldsSQL[]	=	$field;
					$valuesSQL[]	=	floatval($this->$field);
					break;

				case Core::TYPE_BOOLEAN:
					$fieldsSQL[]	=	$field;
					$valuesSQL[]	=	$this->$field ? 1 : 0;
					break;

				case Core::TYPE_STRING:
					$fieldsSQL[]	=	$field;
					$temp			=	htmlspecialchars_decode($this->$field, ENT_QUOTES);
					$temp			=	htmlspecialchars($temp, ENT_QUOTES, Core::$current_db_is_utf8 ? 'UTF-8' : 'ISO-8859-1');
					$valuesSQL[]	=	'"'.$temp.'"';
					break;

				case Core::TYPE_DATE:
				case Core::TYPE_TIME:
				case Core::TYPE_DATETIME:
				case Core::TYPE_TIMESTAMP:
				case Core::TYPE_YEAR:
					$fieldsSQL[]	=	$field;
					$format			=	null;

					if(empty($this->$field)) {
						$valuesSQL[]	=	'NULL';
						break;
					}

					switch($php_type)
					{
						case Core::TYPE_TIME:		$format	=	'H:i:s'; break;
						case Core::TYPE_DATETIME:	$format	=	'Y-m-d H:i:s'; break;
						case Core::TYPE_TIMESTAMP:	$format	=	'YmdHis'; break;
						case Core::TYPE_YEAR:		$format	=	'Y'; break;
						case Core::TYPE_DATE:		$format	=	'Y-m-d'; break;
					}

					$valuesSQL[]	=	'"'.(is_numeric($this->$field)
							? @date($format, $this->$field)
							: (
							$this->$field instanceof \DateTime
								? $this->$field->format($format)
								: $this->$field
							)).'"';

					break;

				case Core::TYPE_CLASS:
					$className	=	$sql_type;

					if( ! class_exists($className))
						throw new \Exception('The field "'.$field.'" is defined as an instance of "'.$className.'" but this class does not exist.');

					if( ! $update && ! ($this->$field instanceof $className))
						$this->load($field);

					if(empty($this->$field))
					{
						$fieldsSQL[]	=	'id_'.$field;
						$valuesSQL[]	=	'null';
						continue;
					}

					if($this->$field instanceof $className)
					{
						$other_id	=	$this->$field->getId();
						if($other_id > -1)
						{
							$fieldsSQL[]	=	'id_'.$field;
							$valuesSQL[]	=	$other_id;
						}
						else
							throw new \Exception('The field "'.$field.'" has to be an instance already saved in the DB.');
					}
					else
						throw new \Exception('The field "'.$field.'" is not an instance of "'.$className.'".');

					break;

				case Core::TYPE_ARRAY: //List of foreign keys

					$foreignClass	=	current($sql_type);

					if($update)
						$newId	=	$this->getId();
					else
					{
						$query		=	Core::$current_db->query('SHOW TABLE STATUS LIKE "'.$tableName.'"');
						$data		=	$query->fetch(\PDO::FETCH_ASSOC);
						$newId		=	$data['Auto_increment'];
					}

					$temp	=	array();

					if(count($this->$field) === 0 && ! $update)
						$this->load($field);

					if(empty($this->$field))
						$array	=	array();
					else
						$array = is_array($this->$field) ? $this->$field : $this->$field->getArray();

					foreach($array as $key => $obj)
					{
						if($obj->existsInDB())
							$temp[]	=	'('.$newId.','.$obj->getId().')';
						else
						{
							$tempArray	=	$obj->$field; //We have to use a temp variable... :(
							unset($tempArray[$key]);
						}
					}

					if( ! $this->$field instanceof EntityArray)
						$this->$field	=	new EntityArray($foreignClass, $this->$field);

					if($update)
						$foreignSQL[]	=	'DELETE FROM '.$tableName.'2'.$field.' WHERE id_'.$tableName.'='.$newId;

					if( ! empty($temp))
						$foreignSQL[]	=	'INSERT INTO '.$tableName.'2'.$field.' (id_'.$tableName.',id_'.$field.') VALUES '.implode(',', $temp);

					break;
			}
		}

		return array('fields' => $fieldsSQL, 'values' => $valuesSQL, 'foreign' => $foreignSQL);
	}

	/**
	 * Get the name of the primary id field of the Entity which calls this methods
	 * @access public
	 * @static
	 * @return string Field name
	 * @throws \Exception
	 */
	public static function getIdName()
	{
		return static::$id_name;
	}

	/**
	 * Get the SQL table name of the Entity which calls this method
	 * @access public
	 * @static
	 * @return string Table name
	 * @throws \Exception
	 */
	public static function getTableName()
	{
		if( ! empty(static::$table_name))
			return static::$table_name;

		$calledClass	=	get_called_class();

		if($calledClass != 'EntityPHP\Entity')
		{
			$parentClass	=	get_parent_class($calledClass);

			//Prevision for the future when we'll handle Entity inheritance
			while($parentClass !== 'EntityPHP\Entity')
			{
				$calledClass	=	$parentClass;
				$parentClass	=	get_parent_class($calledClass);
			}

			//Handle namespaces
			$start	=	strrpos($calledClass, '\\');

			if($start === false)
				return $calledClass;

			return substr($calledClass, $start + 1);
		}
		throw new \Exception('Entity::getTableName() : only subclasses of Entity can call this method.');
	}

	/**
	 * Get the Entities this Entity depends on
	 * @access public
	 * @static
	 */
	final public static function getDependencies()
	{
		$className	=	get_called_class();

		//Prevision for the future when we'll handle Entity inheritance
		if(get_parent_class($className) === 'EntityPHP\Entity')
		{
			$fields         =   static::__structure();
			$dependencies   =   array();

			foreach($fields as $field)
			{
				// Check if the field is another Entity
				switch(Core::getPHPType($field))
				{
					case Core::TYPE_CLASS:
						$dependencies[] = $field;
						break;

					case Core::TYPE_ARRAY:
						foreach ($field as $key => $value)
						{
							// Check if it's an associative array
							if(is_int($key))
								$dependencies[] = $value;
							else
								$dependencies[] = $key;
						}
						break;
				}
			}

			return array_unique($dependencies);
		}
		else
			throw new \Exception('Only direct subclasses of Entity can call "getDependencies()".');
	}

	/**
	 * Check if the SQL table corresponding to the Entity class exists
	 * @access public
	 * @static
	 */
	final public static function tableExists()
	{
		$className	=	get_called_class();

		//Prevision for the future when we'll handle Entity inheritance
		if(get_parent_class($className) === 'EntityPHP\Entity')
		{
			$tableName	=	$className::getTableName();

			$query	=	Core::$current_db->query('SELECT DISTINCT table_name FROM information_schema.statistics WHERE table_name = "' . $tableName . '"');
			return $query->rowCount() > 0;
		}
		else
			throw new \Exception('Only direct subclasses of Entity can call "tableExists()".');
	}

	/**
	 * Create the SQL table of the Entity class which calls this method
	 * @access public
	 * @static
	 */<commit_msg>feat(Entity): Add function to check if junction table exists<commit_after><?php

namespace EntityPHP;

//Interface in order to force definition of method __structure()
interface iEntity
{
	public static function __structure();
}

//All classes inherited from this class can be bind to a table from DB
abstract class Entity implements iEntity
{
	protected	static	$table_name	=	null;
	protected	static	$id_name	=	'id';

	public function __construct(Array $props = array())
	{
		$this->setProps($props);
	}

	/**
	 * Prepare the data of the given Entity object for an INSERT or UPDATE SQL request
	 * @access private
	 * @param bool $update Do we have to prepare an UPDATE request?
	 * @return string SQL representation of the given value
	 * @throws \Exception
	 */
	final private function prepareDataForSQL($update = false)
	{
		$fields		=	static::__structure();
		$tableName	=	static::getTableName();

		$fieldsSQL	=	array();
		$valuesSQL	=	array();
		$foreignSQL	=	array();

		//Iterate through each properties of the class
		foreach($fields as $field => $sql_type)
		{
			$php_type	=	Core::getPHPType($sql_type);

			switch($php_type)
			{
				case Core::TYPE_INTEGER:
					$fieldsSQL[]	=	$field;
					$valuesSQL[]	=	intval($this->$field);
					break;

				case Core::TYPE_FLOAT:
					$fieldsSQL[]	=	$field;
					$valuesSQL[]	=	floatval($this->$field);
					break;

				case Core::TYPE_BOOLEAN:
					$fieldsSQL[]	=	$field;
					$valuesSQL[]	=	$this->$field ? 1 : 0;
					break;

				case Core::TYPE_STRING:
					$fieldsSQL[]	=	$field;
					$temp			=	htmlspecialchars_decode($this->$field, ENT_QUOTES);
					$temp			=	htmlspecialchars($temp, ENT_QUOTES, Core::$current_db_is_utf8 ? 'UTF-8' : 'ISO-8859-1');
					$valuesSQL[]	=	'"'.$temp.'"';
					break;

				case Core::TYPE_DATE:
				case Core::TYPE_TIME:
				case Core::TYPE_DATETIME:
				case Core::TYPE_TIMESTAMP:
				case Core::TYPE_YEAR:
					$fieldsSQL[]	=	$field;
					$format			=	null;

					if(empty($this->$field)) {
						$valuesSQL[]	=	'NULL';
						break;
					}

					switch($php_type)
					{
						case Core::TYPE_TIME:		$format	=	'H:i:s'; break;
						case Core::TYPE_DATETIME:	$format	=	'Y-m-d H:i:s'; break;
						case Core::TYPE_TIMESTAMP:	$format	=	'YmdHis'; break;
						case Core::TYPE_YEAR:		$format	=	'Y'; break;
						case Core::TYPE_DATE:		$format	=	'Y-m-d'; break;
					}

					$valuesSQL[]	=	'"'.(is_numeric($this->$field)
							? @date($format, $this->$field)
							: (
							$this->$field instanceof \DateTime
								? $this->$field->format($format)
								: $this->$field
							)).'"';

					break;

				case Core::TYPE_CLASS:
					$className	=	$sql_type;

					if( ! class_exists($className))
						throw new \Exception('The field "'.$field.'" is defined as an instance of "'.$className.'" but this class does not exist.');

					if( ! $update && ! ($this->$field instanceof $className))
						$this->load($field);

					if(empty($this->$field))
					{
						$fieldsSQL[]	=	'id_'.$field;
						$valuesSQL[]	=	'null';
						continue;
					}

					if($this->$field instanceof $className)
					{
						$other_id	=	$this->$field->getId();
						if($other_id > -1)
						{
							$fieldsSQL[]	=	'id_'.$field;
							$valuesSQL[]	=	$other_id;
						}
						else
							throw new \Exception('The field "'.$field.'" has to be an instance already saved in the DB.');
					}
					else
						throw new \Exception('The field "'.$field.'" is not an instance of "'.$className.'".');

					break;

				case Core::TYPE_ARRAY: //List of foreign keys

					$foreignClass	=	current($sql_type);

					if($update)
						$newId	=	$this->getId();
					else
					{
						$query		=	Core::$current_db->query('SHOW TABLE STATUS LIKE "'.$tableName.'"');
						$data		=	$query->fetch(\PDO::FETCH_ASSOC);
						$newId		=	$data['Auto_increment'];
					}

					$temp	=	array();

					if(count($this->$field) === 0 && ! $update)
						$this->load($field);

					if(empty($this->$field))
						$array	=	array();
					else
						$array = is_array($this->$field) ? $this->$field : $this->$field->getArray();

					foreach($array as $key => $obj)
					{
						if($obj->existsInDB())
							$temp[]	=	'('.$newId.','.$obj->getId().')';
						else
						{
							$tempArray	=	$obj->$field; //We have to use a temp variable... :(
							unset($tempArray[$key]);
						}
					}

					if( ! $this->$field instanceof EntityArray)
						$this->$field	=	new EntityArray($foreignClass, $this->$field);

					if($update)
						$foreignSQL[]	=	'DELETE FROM '.$tableName.'2'.$field.' WHERE id_'.$tableName.'='.$newId;

					if( ! empty($temp))
						$foreignSQL[]	=	'INSERT INTO '.$tableName.'2'.$field.' (id_'.$tableName.',id_'.$field.') VALUES '.implode(',', $temp);

					break;
			}
		}

		return array('fields' => $fieldsSQL, 'values' => $valuesSQL, 'foreign' => $foreignSQL);
	}

	/**
	 * Get the name of the primary id field of the Entity which calls this methods
	 * @access public
	 * @static
	 * @return string Field name
	 * @throws \Exception
	 */
	public static function getIdName()
	{
		return static::$id_name;
	}

	/**
	 * Get the SQL table name of the Entity which calls this method
	 * @access public
	 * @static
	 * @return string Table name
	 * @throws \Exception
	 */
	public static function getTableName()
	{
		if( ! empty(static::$table_name))
			return static::$table_name;

		$calledClass	=	get_called_class();

		if($calledClass != 'EntityPHP\Entity')
		{
			$parentClass	=	get_parent_class($calledClass);

			//Prevision for the future when we'll handle Entity inheritance
			while($parentClass !== 'EntityPHP\Entity')
			{
				$calledClass	=	$parentClass;
				$parentClass	=	get_parent_class($calledClass);
			}

			//Handle namespaces
			$start	=	strrpos($calledClass, '\\');

			if($start === false)
				return $calledClass;

			return substr($calledClass, $start + 1);
		}
		throw new \Exception('Entity::getTableName() : only subclasses of Entity can call this method.');
	}

	/**
	 * Get the Entities this Entity depends on
	 * @access public
	 * @static
	 */
	final public static function getDependencies()
	{
		$className	=	get_called_class();

		//Prevision for the future when we'll handle Entity inheritance
		if(get_parent_class($className) === 'EntityPHP\Entity')
		{
			$fields         =   static::__structure();
			$dependencies   =   array();

			foreach($fields as $field)
			{
				// Check if the field is another Entity
				switch(Core::getPHPType($field))
				{
					case Core::TYPE_CLASS:
						$dependencies[] = $field;
						break;

					case Core::TYPE_ARRAY:
						foreach ($field as $key => $value)
						{
							// Check if it's an associative array
							if(is_int($key))
								$dependencies[] = $value;
							else
								$dependencies[] = $key;
						}
						break;
				}
			}

			return array_unique($dependencies);
		}
		else
			throw new \Exception('Only direct subclasses of Entity can call "getDependencies()".');
	}

	/**
	 * Check if the SQL table corresponding to the Entity class exists
	 * @access public
	 * @static
	 */
	final public static function tableExists()
	{
		$className	=	get_called_class();

		//Prevision for the future when we'll handle Entity inheritance
		if(get_parent_class($className) === 'EntityPHP\Entity')
		{
			$tableName	=	$className::getTableName();

			$query	=	Core::$current_db->query('SELECT DISTINCT table_name FROM information_schema.statistics WHERE table_name = "' . $tableName . '"');
			return $query->rowCount() > 0;
		}
		else
			throw new \Exception('Only direct subclasses of Entity can call "tableExists()".');
	}

	/**
	 * Check if the junction table between this entity and another is present
	 * @access public
	 * @param string $field_name Field name linking to the other table
	 * @static
	 */
	final public static function junctionTableExists($field_name)
	{
		$className	=	get_called_class();

		//Prevision for the future when we'll handle Entity inheritance
		if(get_parent_class($className) === 'EntityPHP\Entity')
		{
			$tableName		=	$className::getTableName();

			echo "Checking $tableName $field_name\n";

			$query	=	Core::$current_db->query('SELECT DISTINCT table_name FROM information_schema.statistics WHERE table_name = "'.$tableName.'2'.$field_name.'"');
			return $query->rowCount() > 0;
		}
		else
			throw new \Exception('Only direct subclasses of Entity can call "junctionTableExists()".');
	}

	/**
	 * Create the SQL table of the Entity class which calls this method
	 * @access public
	 * @static
	 */
{'fraction_non_alphanumeric': 0.1556410574655579, 'fraction_numerical': 0.0019858508129576764, 'mean_word_length': 2.9692118226600983, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 94, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7411082', 'n_tokens_mistral': 6733, 'n_tokens_neox': 5239, 'n_words': 1342}