Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
42.86% covered (danger)
42.86%
6 / 14
CRAP
88.46% covered (warning)
88.46%
115 / 130
MySQL
0.00% covered (danger)
0.00%
0 / 1
42.86% covered (danger)
42.86%
6 / 14
42.46
88.46% covered (warning)
88.46%
115 / 130
 __construct
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 5
 execute
0.00% covered (danger)
0.00%
0 / 1
2.03
80.00% covered (warning)
80.00%
4 / 5
 fetch
0.00% covered (danger)
0.00%
0 / 1
2.03
80.00% covered (warning)
80.00%
4 / 5
 bindParams
0.00% covered (danger)
0.00%
0 / 1
5.68
70.00% covered (warning)
70.00%
7 / 10
 find
0.00% covered (danger)
0.00%
0 / 1
3.04
83.33% covered (warning)
83.33%
10 / 12
 findAll
0.00% covered (danger)
0.00%
0 / 1
3.01
91.67% covered (success)
91.67%
11 / 12
 findBy
0.00% covered (danger)
0.00%
0 / 1
5.01
94.12% covered (success)
94.12%
16 / 17
 findOneBy
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 save
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 insert
0.00% covered (danger)
0.00%
0 / 1
4.00
94.74% covered (success)
94.74%
18 / 19
 update
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
16 / 16
 delete
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
9 / 9
 prepareSortByClause
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 prepareLimitClause
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
5 / 5
<?php
namespace Modelight\DataSource;
class MySQL extends \Modelight\DataSource implements \Modelight\DataSourceInterface
{
    /**
     * @var \PDO
     */
    protected $pdo = null;
    /**
     * Database constructor
     *
     * @param string $host
     * @param string $databaseName
     * @param string $username
     * @param string $password
     */
    public function __construct($host, $databaseName, $username, $password)
    {
        $this->pdo = new \PDO('mysql:dbname=' . $databaseName . ';host=' . $host, $username, $password, [
            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
            \PDO::ATTR_PERSISTENT => true
        ]);
    }
    /**
     * Executes query
     *
     * @param string $query
     * @param array $params
     * @return int Number of affected rows
     * @throws \Modelight\Exception
     */
    public function execute($query, array $params = array())
    {
        $stmt = $this->pdo->prepare($query);
        $this->bindParams($stmt, $params);
        if (!$stmt->execute()) {
            throw new \Modelight\Exception('Unexpected PDO result. PDOStatement::execute returns false. errorInfo: ' . json_encode($stmt->errorInfo()));
        }
        return $stmt->rowCount();
    }
    /**
     * Executes query for fetching results
     *
     * @param string $query
     * @param array $params
     * @return array
     * @throws \Modelight\Exception
     */
    public function fetch($query, array $params = array())
    {
        $stmt = $this->pdo->prepare($query);
        $this->bindParams($stmt, $params);
        if (!$stmt->execute()) {
            throw new \Modelight\Exception('Unexpected PDO result. PDOStatement::execute returns false. errorInfo: ' . json_encode($stmt->errorInfo()));
        }
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }
    /**
     * Runs PDOStatement::bindValue for each given param
     *
     * @param \PDOStatement $stmt
     * @param array $params
     * @return $this
     * @throws \Modelight\Exception
     */
    protected function bindParams(\PDOStatement $stmt, array $params = array())
    {
        foreach ($params as $fieldName => $fieldDefinition) {
            if (!is_array($fieldDefinition)) {
                throw new \Modelight\Exception('Field definition must be an array.');
            }
            if (!array_key_exists('value', $fieldDefinition)) {
                throw new \Modelight\Exception('value entry is missing in field definition.');
            }
            if (!array_key_exists('type', $fieldDefinition)) {
                throw new \Modelight\Exception('type entry is missing in field definition.');
            }
            $stmt->bindValue($fieldName, $fieldDefinition['value'], $fieldDefinition['type']);
        }
        return $this;
    }
    /**
     * Find one Model by primary key value
     *
     * @param string $modelClassName
     * @param mixed $primaryKeyValue
     * @return \Modelight\Model
     * @throws \Modelight\Exception
     */
    public function find($modelClassName, $primaryKeyValue)
    {
        $refClass = new \ReflectionClass($modelClassName);
        $defaultProperties = $refClass->getDefaultProperties();
        if (!array_key_exists('primaryKey', $defaultProperties)) {
            throw new \Modelight\Exception('primaryKey property is missing in ' . var_export($modelClassName, true) . ' class.');
        }
        if (!array_key_exists('fields', $defaultProperties)) {
            throw new \Modelight\Exception('fields property is missing in ' . var_export($modelClassName, true) . ' class.');
        }
        return $this->findOneBy($modelClassName, [
            $defaultProperties['primaryKey'] => [
                'value' => $primaryKeyValue,
                'type' => $defaultProperties['fields'][$defaultProperties['primaryKey']]['type']
            ]
        ]);
    }
    /**
     * Find all Model
     *
     * @param string $modelClassName
     * @param int|null $limit
     * @param int|null $offset
     * @param array $sortBy
     * @return \Modelight\Collection
     * @throws \Modelight\Exception
     */
    public function findAll($modelClassName, $limit = null, $offset = null, array $sortBy = array())
    {
        $refClass = new \ReflectionClass($modelClassName);
        $defaultProperties = $refClass->getDefaultProperties();
        if (!array_key_exists('tableName', $defaultProperties)) {
            throw new \Modelight\Exception('tableName property is missing in ' . var_export($modelClassName, true) . ' class.');
        }
        $query = "SELECT t.* FROM " . $defaultProperties['tableName'] . " t" .
            $this->prepareSortByClause($sortBy) .
            $this->prepareLimitClause($limit, $offset);
        $collection = new \Modelight\Collection();
        foreach ($this->fetch($query) as $row) {
            $collection->append(new $modelClassName($row));
        }
        return $collection;
    }
    /**
     * Find Model by criterias
     *
     * @param string $modelClassName
     * @param array $criterias
     * @param int|null $limit
     * @param int|null $offset
     * @param array $sortBy
     * @return \Modelight\Collection
     * @throws \Modelight\Exception
     */
    public function findBy($modelClassName, array $criterias, $limit = null, $offset = null, array $sortBy = array())
    {
        $refClass = new \ReflectionClass($modelClassName);
        $defaultProperties = $refClass->getDefaultProperties();
        if (!array_key_exists('tableName', $defaultProperties)) {
            throw new \Modelight\Exception('tableName property is missing in ' . var_export($modelClassName, true) . ' class.');
        }
        $query = "SELECT t.* FROM " . $defaultProperties['tableName'] . " t";
        $whereClauses = [];
        foreach ($criterias as $fieldName => $fieldValue) {
            $whereClauses[] = " ( " . $fieldName . " = :" . $fieldName . " ) ";
        }
        $query .= count($whereClauses) > 0 ? " WHERE " . implode(' AND ', $whereClauses) : "";
        $query .= $this->prepareSortByClause($sortBy);
        $query .= $this->prepareLimitClause($limit, $offset);
        $collection = new \Modelight\Collection();
        foreach ($this->fetch($query, $criterias) as $row) {
            $collection->append(new $modelClassName($row));
        }
        return $collection;
    }
    /**
     * Find one Model by criterias
     *
     * @param string $modelClassName
     * @param array $criterias
     * @param int|null $limit
     * @param int|null $offset
     * @param array $sortBy
     * @return \Modelight\Model
     * @throws \Modelight\Exception
     */
    public function findOneBy($modelClassName, array $criterias, $limit = null, $offset = null, array $sortBy = array())
    {
        $collection = $this->findBy($modelClassName, $criterias, $limit, $offset, $sortBy);
        if (!$collection->offsetExists(0)) {
            return null;
        }
        return $collection->offsetGet(0);
    }
    /**
     * Save Model
     *
     * @param \Modelight\Model $model
     * @return \Modelight\Model
     */
    public function save(\Modelight\Model $model)
    {
        $pkValue = $model->get($model->getPrimaryKey());
        if ($pkValue === null) {
            $this->insert($model);
        } else {
            $this->update($model);
        }
        return $model;
    }
    /**
     * Insert Model
     *
     * @param \Modelight\Model $model
     * @return \Modelight\Model
     * @throws \Modelight\Exception
     */
    public function insert(\Modelight\Model $model)
    {
        $query = "INSERT INTO " . $model->getTableName() . " SET ";
        $setClauses = [];
        $params = [];
        foreach ($model->getFields() as $fieldName => $fieldDefinition) {
            if ($fieldName === $model->getPrimaryKey()) {
                continue;
            }
            $setClauses[] = $fieldName . " = :" . $fieldName;
            $params[$fieldName] = [
                'value' => $model->get($fieldName),
                'type' => $fieldDefinition['type']
            ];
        }
        $query .= " " . implode(", ", $setClauses);
        $stmt = $this->pdo->prepare($query);
        $this->bindParams($stmt, $params);
        if (!$stmt->execute()) {
            throw new \Modelight\Exception('Unexpected PDO result. PDOStatement::execute returns false. errorInfo: ' . json_encode($stmt->errorInfo()));
        }
        $model->set($model->getPrimaryKey(), $this->pdo->lastInsertId());
        return $model;
    }
    /**
     * Update Model
     *
     * @param \Modelight\Model $model
     * @return \Modelight\Model
     * @throws \Modelight\Exception
     */
    public function update(\Modelight\Model $model)
    {
        $query = "UPDATE " . $model->getTableName() . " SET ";
        $setClauses = [];
        $params = [];
        foreach ($model->getFields() as $fieldName => $fieldDefinition) {
            if ($fieldName !== $model->getPrimaryKey()) {
                $setClauses[] = $fieldName . " = :" . $fieldName;
            }
            $params[$fieldName] = [
                'value' => $model->get($fieldName),
                'type' => $fieldDefinition['type']
            ];
        }
        $query .= " " . implode(", ", $setClauses);
        $query .= " WHERE " . $model->getPrimaryKey() . " = :" . $model->getPrimaryKey() . " ";
        $this->execute($query, $params);
        return $model;
    }
    /**
     * Delete Model
     *
     * @param \Modelight\Model $model
     * @return \Modelight\Model
     * @throws \Modelight\Exception
     */
    public function delete(\Modelight\Model $model)
    {
        $query = "DELETE FROM " . $model->getTableName() . " WHERE " . $model->getPrimaryKey() . " = :" . $model->getPrimaryKey();
        $fields = $model->getFields();
        $this->execute($query, [
            $model->getPrimaryKey() => [
                'value' => $model->get($model->getPrimaryKey()),
                'type' => $fields[$model->getPrimaryKey()]['type']
            ]
        ]);
        return $model;
    }
    /**
     * Prepare sortBy clause
     *
     * @param array $sortyBy Ex.: ['fieldName1' => 'ASC', 'fieldNameTest' => 'DESC']
     * @return string
     */
    protected function prepareSortByClause(array $sortyBy)
    {
        $clauses = [];
        foreach ($sortyBy as $fieldName => $sortOrder) {
            $clauses[] = $fieldName . " " . $sortOrder;
        }
        return count($clauses) > 0 ? " ORDER BY " . implode(', ', $clauses) : "";
    }
    /**
     * Prepare limit clause
     *
     * @param int|null $limit
     * @param int|null $offset
     * @return string
     */
    protected function prepareLimitClause($limit = null, $offset = null)
    {
        if ($limit === null && $offset === null) {
            return "";
        }
        if ($offset === null) {
            return " LIMIT " . (int)$limit . " ";
        }
        return " LIMIT " . (int)$offset . "," . (int)$limit . " ";
    }
}