10
\$\begingroup\$

I tried to create the PHP-framework, with no experience in the task like this:

Folders:

www/
  |-- protected/
  |      |-- controllers/
  |      |      |-- Site.php
  |      |
  |      |-- core/
  |      |      |-- App.php
  |      |      |-- Controller.php
  |      |      |-- Criteria.php
  |      |      |-- Model.php
  |      |      |-- View.php
  |      |      |-- Pagination.php
  |      |     
  |      |-- models/
  |      |-- views/
  |      |-- inip.php
  |      |-- config.php
  |
  |-- index.php
  |-- .htaccess

.htaccess

AddDefaultCharset utf-8

RewriteEngine On  
RewriteCond %{REQUEST_FILENAME} !-f  
RewriteCond %{REQUEST_FILENAME} !-d  
RewriteRule .* index.php [L] 

index.php

require_once __DIR__.'/protected/init.php';

//Application start (singleton)
App::getInstance($connectParams);
App::route();

config.php

define('CORE_DIR', __DIR__.'/core/');
define('MODEL_DIR', __DIR__.'/models/');
define('CONTROLLER_DIR', __DIR__.'/controllers/');
define('VIEW_DIR', __DIR__.'/views/');
//DB connection params
$connectParams = array(
    'user' => 'root',
    'pass' => '123456',
    'host' => 'localhost',
    'dbname' => 'test',
    );

init.php

require_once __DIR__.'/config.php';
//Classes autoload
spl_autoload_register('autoload');
function autoload($className)
{
    $classFile = false;
    $fileName = $className.".php";
    $paths = array(
        CORE_DIR,
        MODEL_DIR,
        CONTROLLER_DIR,
        VIEW_DIR,
        );

    foreach ($paths as $path) {
        if (file_exists($path.$fileName)) {
            $classFile = $path.$fileName;
            break;
        }
    }

    if ($classFile!==false) {
        include $classFile;
    }
    else {
        die('<p>File for class '.$className.' not found!</p>');
    }
}

core/App.php

class App
{
    protected static $instance;
    public static $dbHandler;

    //Getting an App singleton
    public static function getInstance($connectParams=array())
    {
        if (self::$instance==null) {
            self::$instance = new self($connectParams);
        }
        return self::$instance;
    }

    protected function __construct($connectParams)
    {
        $host = (isset($connectParams['host']) ? $connectParams['host'] : 'localhost');
        $dbname = (isset($connectParams['dbname']) ? $connectParams['dbname'] : '');
        $user = (isset($connectParams['user']) ? $connectParams['user'] : 'root');
        $pass = (isset($connectParams['pass']) ? $connectParams['pass'] : '');

        try {  
            $dbHandler = new PDO("mysql:host=".$host.";dbname=".$dbname, $user, $pass);
            $dbHandler->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $dbHandler->exec("set names utf8");
        }  
        catch(PDOException $e) {  
            echo $e->getMessage(); 
        }
        self::$dbHandler = $dbHandler;
    }

    private function __clone()
    {
    }

    private function __wakeup()
    {
    }

    //Parsing the url
    public static function route()
    {
        $controllerName = 'Site';
        $actionName = 'page';
        $params = array();

        $routeArr = explode('/', $_SERVER['REQUEST_URI']);
        if (!empty($routeArr[1])) {
            $controllerName = ucfirst($routeArr[1]);
        }
        if (!empty($routeArr[2])) {
            $end = strpos($routeArr[2], '?');
            if ($end>0) {
                $actionName = substr($routeArr[2], 0, $end);
            }
            else{
                $actionName = $routeArr[2];
            }
        }

        if (count($routeArr)>3) { //Creating parameters array from the url
            for ($i=3; $i<count($routeArr); $i++) { 
                $end = strpos($routeArr[$i], '?');
                if ($end>0) {
                    $params[] = substr($routeArr[$i], 0, $end);
                }
                else{
                    $params[] = $routeArr[$i];
                }
            }
        }
        if (!empty($_REQUEST)) {
            foreach ($_REQUEST as $key => $value) {
                $params[$key] = $value;
            }
        }

        $controller = new $controllerName;
        $action = $actionName."Action";
        $controller->$action($params);
    }
}

core/Criteria.php

class Criteria
{
    protected $isStrict; // true/false - AND/OR
    protected $queryType; //SELECT/INSERT/UPDATE
    protected $query;
    protected $table;
    protected $currentModel; //Current model (using in save())
    protected $reqFields; //array('id','name','text')
    protected $condition; //array('name'=>array('TEST','LIKE')
    protected $addCondition; //'GROUP BY tbl2.id'/'ORDER BY id DESC'
    protected $leftJoins;
    protected $innerJoins;

    public $limit;
    public $offset;

    public function __construct($model, $queryType='SELECT', $isStrict=true, $reqFields=array())
    {
        if (is_object($model)) { //На всякий случай
            $this->table = $model->tableName;
            $this->currentModel = $model;
        }
        else {
            $this->table = $model;
            $this->currentModel = new $model;
        }
        $this->queryType = $queryType;
        $this->isStrict = $isStrict;
        foreach ($reqFields as $field) {
            $end = strpos($field, '.');
            if ($end===false) {
                $this->reqFields[] = $this->table.'.'.$field.' AS '.$this->table.'_'.$field;
            }
            else{
                $preffix = substr($field, 0, $end);
                $suffix = substr($field, $end+1, strlen($field));
                $this->reqFields[] = $field.' AS '.$preffix.'_'.$suffix;
            }
        }
    }

    public function __get($name)
    {
        return $this->$name;
    }

    public function condition($params)
    {
        if (!empty($params)) {
            foreach ($params as $key => $value) {
                $end = strpos($key, '.');
                if ($end===false) {
                    $queryParams[$this->table.'.'.$key] = $value;
                }
                else{
                    $queryParams[$key] = $value;
                }
            }
            reset($queryParams); //Getting the first element of the array
            $firstKey = key($queryParams);
            $this->condition = ' WHERE '.$firstKey.(isset($queryParams[$firstKey][1]) ? ' '.$queryParams[$firstKey][1].' ' : '=').'\''.$queryParams[$firstKey][0].'\'';
            unset($queryParams[$firstKey]); //Removing the first element of the array
            foreach ($queryParams as $pkey => $pvalue) {
                $this->condition .= ' '.($this->isStrict ? 'AND ' : 'OR ').$pkey.(isset($pvalue[1]) ? ' '.$pvalue[1].' ' : '=\'').$pvalue[0].'\'';
            }
        }
    }

    public function addCondition($str='')
    {
        $this->addCondition = $str;
    }

    public function leftJoins($relations=array())
    {
        if (!empty($relations)) {
            foreach ($relations as $key => $value) {
                $this->leftJoins[$key] = array($value[0], ucfirst($value[1]), $value[2]);
            }
        }
    }

    public function innerJoins($relations=array())
    {
        if (!empty($relations)) {
            foreach ($relations as $key => $value) {
                $this->innerJoins[$key] = array($value[0], ucfirst($value[1]), $value[2]);
            }
        }
    }

    private function buildJoins($relationArr, $relationType, $joinedQuery)
    {
        if (isset($relationArr[1])) {
            $joinedModel = new $relationArr[1]();
            if (empty($this->reqFields)) {
                foreach ($joinedModel->tableFields as $fieldName => $fieldValue) {
                    $this->query .= ', '.$joinedModel->tableName.'.'.$fieldName.' AS '.$joinedModel->tableName.'_'.$fieldName;
                }
            }
        }
        if ($relationArr[0]=='BELONGS_TO') {
            $joinedQuery .= ' '.$relationType.' JOIN '.$joinedModel->tableName;
            $joinedQuery .= ' ON '.$this->table.'.'.$relationArr[2].'='.$joinedModel->tableName.'.'.$joinedModel->tableKey;
        }
        if ($relationArr[0]=='HAS_MANY') {
            $joinedQuery .= ' '.$relationType.' JOIN '.$joinedModel->tableName;
            $joinedQuery .= ' ON '.$this->table.'.'.$this->currentModel->tableKey.'='.$joinedModel->tableName.'.'.$relationArr[2];
        }
        if ($relationArr[0]=='MANY_MANY') {
            if (is_array($relationArr[2])) {
                $bundleTable = ucfirst($relationArr[2][0]);
                $currentTableKey = $relationArr[2][1]; 
                $joinedTableKey = $relationArr[2][2]; 

                $bundleModel = new $bundleTable();
                $joinedQuery .= ' '.$relationType.' JOIN '.$bundleModel->tableName;
                $joinedQuery .= ' ON '.$this->table.'.'.$this->currentModel->tableKey.'='.$bundleModel->tableName.'.'.$currentTableKey;
                $joinedQuery .= ' '.$relationType.' JOIN '.$joinedModel->tableName;
                $joinedQuery .= ' ON '.$joinedModel->tableName.'.'.$joinedModel->tableKey.'='.$bundleModel->tableName.'.'.$joinedTableKey;  
            }
        }
        return $joinedQuery;
    }

    private function buildQuery()
    {
        if ($this->queryType=='SELECT') {
            $this->query = $this->queryType;
            if (!empty($this->reqFields)) {
                if (!in_array($this->currentModel->tableKey, $this->reqFields)) {
                    $this->query .= ' '.$this->table.'.'.$this->currentModel->tableKey.' AS '.$this->table.'_'.$this->currentModel->tableKey.',';
                }
                $i=1;
                foreach ($this->reqFields as $reqField) {
                    if ($i<count($this->reqFields)) {
                        $this->query .= ' '.$reqField.',';
                    }
                    else{
                        $this->query .= ' '.$reqField;
                    }
                    $i++;
                }
            }
            else {
                $i=1;
                foreach ($this->currentModel->tableFields as $key => $value) {
                    if ($i<count($this->currentModel->tableFields)) {
                        $this->query .= ' '.$this->table.'.'.$key.' AS '.$this->table.'_'.$key.',';
                    }
                    else{
                        $this->query .= ' '.$this->table.'.'.$key.' AS '.$this->table.'_'.$key;
                    }
                    $i++;
                }
            }

            //JOIN's
            $joinedQuery = '';
            if (!empty($this->leftJoins)) {
                foreach ($this->leftJoins as $value) {
                    $joinedQuery = $this->buildJoins($value, 'LEFT', $joinedQuery);
                }
            }
            if (!empty($this->innerJoins)) {
                foreach ($this->innerJoins as $value) {
                    $joinedQuery = $this->buildJoins($value, 'INNER', $joinedQuery);
                }
            }

            $this->query .= ' FROM '.$this->table;
            $this->query .= $joinedQuery;
            $this->query .= $this->condition;
            $this->query .= ' '.$this->addCondition;
            if (isset($this->limit) && isset($this->offset)) {
                $this->query .= ' LIMIT '.$this->offset.','.$this->limit;
            }
        }
        elseif ($this->queryType=='SAVE') {

            //Removing the tied object's properties to avoid using it in INSERT/UPDATE commands
            foreach ($this->currentModel->tableRelations as $relName => $relValue) {
                $relations[] = $relName;
            }
            foreach ($this->currentModel->tableFields as $field => $value) {
                if (!in_array($field, $relations)) {
                    $originalFields[$field] = $value;
                }
            }

            if ($this->currentModel->tableFields[$this->currentModel->tableKey]) {
                $this->query = 'UPDATE '.$this->table.' SET';
                $i=1;
                foreach ($originalFields as $key => $value) {
                    if ($i<count($originalFields)) {
                        $this->query .= ' '.$key.'=\''.$value.'\',';
                    }
                    else{
                        $this->query .= ' '.$key.'=\''.$value.'\'';
                    }
                    $i++;
                }
                $this->query .= $this->condition;
                $this->query .= ' '.$this->addCondition;
            }
            else{
                $this->query = 'INSERT INTO '.$this->table.' (';
                foreach ($originalFields as $key => $value) {
                    if ($key!=$this->currentModel->tableKey && !empty($value)) {
                        $fieldsArr[] = $key;
                        $valuesArr[] = $value;
                    }
                }
                $i=1;
                foreach ($fieldsArr as $field) {
                    if ($i<count($fieldsArr)) {
                        $this->query .= ' `'.$field.'`,';
                    }
                    else{
                        $this->query .= ' `'.$field.'`';
                    }
                    $i++;
                }
                $this->query .= ') VALUES (';
                $i=1;
                foreach ($valuesArr as $value) {
                    if ($i<count($valuesArr)) {
                        $this->query .= ' \''.$value.'\',';
                    }
                    else{
                        $this->query .= ' \''.$value.'\'';
                    }
                    $i++;
                }
                $this->query .= ')';
            }
        }
    }

    public function execute()
    {
        $this->buildQuery();

        $queryObj = App::$dbHandler->prepare($this->query);
        $result = $queryObj->execute();

        if ($this->queryType=='SELECT') {
            $result = $queryObj->fetchAll(PDO::FETCH_ASSOC);
        }
        elseif ($this->queryType=='SAVE') {
            $result = App::$dbHandler->lastInsertId();
        }

        return $result;
    }
}

core/Model.php

abstract class Model
{
    protected $tableName;
    protected $tableFields;
    protected $tableRelations;
    protected $tableKey;

    abstract protected function tableName();
    abstract protected function tableFields();
    abstract protected function tableRelations();
    abstract protected function tableKey();

    public final function __construct()
    {
        $this->tableName = $this->tableName();
        $this->tableFields = $this->tableFields();
        $this->tableRelations = $this->tableRelations();
        $this->tableKey = $this->tableKey();
    }

    public final function __get($name)
    {
        switch ($name) {
            case 'tableName':
                return $this->tableName;
                break;
            case 'tableFields':
                return $this->tableFields;
                break;
            case 'tableRelations':
                return $this->tableRelations;
                break;
            case 'tableKey':
                return $this->tableKey;
                break;
            case 'id':
                return $this->tableFields[$this->tableKey];
                break;
            case isset($this->tableRelations[$name]):
                if ($this->tableRelations[$name][0]=='BELONGS_TO') {
                    $linkedName = ucfirst($this->tableRelations[$name][1]);
                    $linkedObj = new $linkedName;
                    $linkedRes = $linkedObj->findById($this->tableFields[$this->tableRelations[$name][2]]);
                    return $linkedRes;
                }
                elseif ($this->tableRelations[$name][0]=='HAS_MANY') {
                    $linkedName = ucfirst($this->tableRelations[$name][1]);
                    $linkedObj = new $linkedName;
                    $criteria = new Criteria($linkedObj);
                    $criteria->condition(array($this->tableRelations[$name][2]=>array($this->id)));
                    $linkedRes = $linkedObj->findByCriteria($criteria);
                    return $linkedRes;
                }
                elseif ($this->tableRelations[$name][0]=='MANY_MANY') {
                    return 'MANY_MANY'; //not implemented yet!
                }
                break;
            case isset($this->tableFields[$name]):
                return $this->tableFields[$name];
                break;
        }
    }

    public final function __set($name, $value)
    {
        switch ($name) {
            case array_key_exists($name, $this->tableFields):
                $this->tableFields[$name] = $value;
                break;
            case (array_key_exists($name, $this->tableRelations) && is_array($value)):
                if ($this->tableRelations[$name][0]=='BELONGS_TO') {
                    $linkedName = ucfirst($this->tableRelations[$name][1]);
                    $linkedObj = new $linkedName;

                    //I have to check is the field unique!
                    foreach ($value as $key => $val) {
                        if (array_key_exists($key, $linkedObj->tableFields)) {
                            $linkedObj->tableFields[$key] = $val;
                            $criteria = new Criteria($linkedObj);
                            $criteria->condition(array($key=>array($val)));
                        }
                    }
                    $test = $linkedObj->findByCriteria($criteria);
                    if (empty($test)) {
                        $lastId = $linkedObj->save();
                    }
                    else{
                        foreach ($test as $key => $value) {
                            $lastId = $key;
                        }   
                    }
                    ///////////////////////////

                    $this->tableFields[$this->tableRelations[$name][2]] = $lastId;
                }
                break;
        }
    }

    private function isEmpty()
    {
        foreach ($this->tableFields as $field) {
            if (!empty($field)) {
                $isNotEmpty[] = 1;
            }
            else {
                $isNotEmpty[] = 0;
            }
        }
        if (in_array('1', $isNotEmpty)) {
            return false;
        }
        else {
            return true;
        }
    }

    private function getResultObjects($result)
    {
        if (!empty($this->tableFields)) {
            if (!empty($result)) {
                foreach ($result as $row) {
                    $modelObj = new $this->tableName;
                    foreach ($modelObj->tableFields as $mkey => $mvalue) {
                        foreach ($row as $rkey => $rvalue) {
                            if ($modelObj->tableName.'_'.$mkey==$rkey) {
                                $modelObj->tableFields[$mkey] = $rvalue;
                            }
                        }
                    }
                    foreach ($modelObj->tableRelations as $key => $value) {
                        $tmp = ucfirst($value[1]);
                        $relObject = new $tmp;
                        foreach ($relObject->tableFields as $rokey => $rovalue) {
                            foreach ($row as $rkey => $rvalue) {
                                if ($relObject->tableName.'_'.$rokey==$rkey) {
                                    $relObject->tableFields[$rokey] = $rvalue;
                                }
                            }
                        }
                        if (!$relObject->isEmpty()) {
                            $modelObj->tableFields[$key] = $relObject;
                        }
                        else{
                            $modelObj->tableFields[$key] = null;
                        }
                    }
                    $modelArr[] = $modelObj;
                }

                foreach ($modelArr as $model) {
                    foreach ($model->tableRelations as $relName => $relValue) {
                        $subObjArr[$model->id][] = $model->tableFields[$relName];
                    }
                    $parentModelArr[$model->id] = $model;
                }

                foreach ($subObjArr as $sKey => $sValue) {
                    foreach ($parentModelArr as $pKey => $pValue) {
                        if ($pKey==$sKey) {
                            foreach ($pValue->tableRelations as $relName => $relValue) {
                                $pValue->tableFields[$relName] = $sValue;
                            }
                            $finalArr[$pKey] = $pValue;
                        }
                    }
                }
                ////////////////////////////////////////////////////////////////////////////
            }
        }
        return $finalArr;
    }

    public final function findById($id=null)
    {
        $data = array();
        if ($id==intval($id) && isset(App::$dbHandler)) {
            $criteria = new Criteria($this->tableName);
            $criteria->condition(array($this->tableKey=>array($id)));
            $result = $criteria->execute();
        }
        if (!empty($result)) {
            $data = $this->getResultObjects($result);
        }

/*echo "<pre>";
print_r($criteria);
echo "</pre><hr>";*/

        return $data;
    }

    public final function findByParams($params=array(), $isStrict=true, $comparison='=')
    {
        $data = array();
        if (!empty($params) && isset(App::$dbHandler)) {
            $criteria = new Criteria($this->tableName, 'SELECT', $isStrict);
            $criteria->condition($params);
            $result = $criteria->execute();
        }
        if (!empty($result)) {
            $data = $this->getResultObjects($result);
        }

/*echo "<pre>";
print_r($criteria);
echo "</pre><hr>";*/

        return $data;
    }

    public final function findByCriteria($criteria)
    {
        $data = array();
        if (is_object($criteria)) {
            $result = $criteria->execute();
        }
        if (!empty($result)) {
            $data = $this->getResultObjects($result);
        }

/*echo "<pre>";
print_r($criteria);
echo "</pre><hr>";*/

/*echo "<pre>";
print_r($result);
echo "</pre><hr>";*/

        return $data;
    }

    //I can do this better!
    public final function findBySql($query)
    {
        $data = array();
        if (empty($query)) return $data;

        $queryObj = App::$dbHandler->prepare($query);
        $result = $queryObj->execute();
        $data = $queryObj->fetchAll(PDO::FETCH_ASSOC);

        return $data;
    }

    //Check this!
    public final function count($criteria)
    {
        $data = array();
        if (is_object($criteria)) {
            $query = "SELECT COUNT(*) as countNum FROM ".$criteria->table." ".$criteria->condition;
            $queryObj = App::$dbHandler->prepare($query);
            $result = $queryObj->execute();
        }
        if (!empty($result)) {
            $result = $queryObj->fetchAll(PDO::FETCH_ASSOC);
        }

        return $result;
    }

    public final function save()
    {
        $criteria = new Criteria($this, 'SAVE');
        $criteria->condition(array($this->tableKey=>array($this->tableFields[$this->tableKey])));
        $result = $criteria->execute();

/*echo "<pre>";
print_r($criteria);
echo "</pre><hr>";*/

        return $result;
    }
}

core/Controller.php

class Controller
{
    public function __call($name, $params=array())
    {
        try 
        {
            throw new Exception("<p>Method does not exist!</p>");
        } 
        catch (Exception $e) 
        {
            echo $e->getMessage();
        }
    }

    public static function __callStatic($name, $params=array())
    {
        try 
        {
            throw new Exception("<p>Method does not exist!</p>");
        } 
        catch (Exception $e) 
        {
            echo $e->getMessage();
        }
    }

    public function __get($name)
    {

    }

    protected function redirect($path)
    {
        header("Location:".$path);
    }
}

core/Pagination.php

class Pagination
{

    protected $count;
    protected $pages;
    protected $offset=0;
    protected $data;
    protected $curPage=0;

    public $query;
    public $pageSize;

    public function __construct($count, $get_arr=null, $pageSize=null, $isPaging=true)
    {
        if (!$isPaging) return false;

        $this->count = intval($count);

        if (!empty($pageSize) && $isPaging) {
            $this->pageSize = intval($pageSize);
        }
        else{
            $this->pageSize = 10;
        }

        if (isset($get_arr['page'])) {
            $this->curPage = intval($get_arr['page']);
        }

        if ($isPaging) {
            $this->offset = (intval($this->curPage)-1)*$this->pageSize;
        }
        if ($this->offset<0 || !$isPaging) {
            $this->offset = 0;
        }

        $this->pages = ceil($this->count/$this->pageSize);
    }

    public function __get($name)
    {
        if ($name=='pageCount') {
            return $this->pages;
        }
        if ($name=='curPage') {
            return $this->curPage;
        }
    }

    public function applyLimit($query)
    {
        if (is_object($query)) {
            if ($this->pages>1) {
                $this->query = $query;
                $this->query->limit = $this->pageSize;
                $this->query->offset = $this->offset;
            }
            else{
                $this->query = $query;
            }
        }
        else{
            if ($this->pages>1) {
                $this->query = $query." LIMIT ".$this->offset.",".$this->pageSize."";
            }
            else{
                $this->query = $query;
            }
        }
    }

}

core/View.php is empty yet.

I wrote this controller for example.

controllers/Site.php (I did not realize the views, so I have to write the html code in the controller. I know it's wrong, but it's temporary.)

class Site extends Controller
{

    public function pageAction()
    {   
        $model = new Articles;
        $criteria = new Criteria($model);

        $condition = array();
        if (!empty($_GET['Search']['auto']) && intval($_GET['Search']['auto'])) {
            $condition['fk_auto'] = array(intval($_GET['Search']['auto']));
        }
        if (!empty($_GET['Search']['group']) && intval($_GET['Search']['group'])) {
            $condition['fk_group'] = array(intval($_GET['Search']['group']));
        }
        if (!empty($_GET['Search']['number']) && strlen($_GET['Search']['number'])) {
            $condition['number'] = array(addslashes($_GET['Search']['number']));
        }
        $criteria->condition($condition);

        //Pagination
        $count = $model->count($criteria);
        $pages = new Pagination($count[0]['countNum'], $_GET);
        $pages->applyLimit($criteria);
        ///////////

        $data = $model->findByCriteria($criteria);

            echo "<form id='advanced' action='/site/page' method='GET'>
                <table id='search_form'>
                    <tr>
                        <td>
                            Auto<br>
                            <select name='Search[auto]'>
                                <option>--Select auto--</option>";
                                $autos = Autos::getAll();
                                foreach ($autos as $auto) {
                                    echo "<option value='".$auto->id."' ".(isset($_GET['Search']['auto']) && $_GET['Search']['auto']==$auto->id ? "selected" : null).">".$auto->auto."</option>";
                                }
                            echo "</select>
                        </td>
                        <td>
                            Group<br>
                            <select name='Search[group]'>
                                <option>--Select group--</option>";
                                $groups = Groups::getAll();
                                foreach ($groups as $group) {
                                    echo "<option value='".$group->id."' ".(isset($_GET['Search']['group']) && $_GET['Search']['group']==$group->id ? "selected" : null).">".$group->group."</option>";
                                }
                            echo "</select>
                        </td>
                        <td>
                            Number<br>
                            <input type='text' name='Search[number]' value='".$_GET['Search']['number']."'>
                        </td>
                    </tr>           
                    <tr>
                        <td colspan='3'><input type='submit' name='Search[submit]' class='submit' value='find'></td>
                    </tr>
                </table>
            </form>";

        if (!empty($data)) {
            echo "<table class='techtab' cellpadding='1' cellspacing='0' border='1'>";
                echo "<tr>";
                    echo "<td class='head'>group</td>";
                    echo "<td class='head'>auto</td>";
                    echo "<td class='head'>brand</td>";
                    echo "<td class='head'>number</td>";
                    echo "<td class='head'>original</td>";
                    echo "<td class='head'>description</td>";
                    echo "<td class='head'>Analogic</td>";
                echo "</tr>";
            $cnt=1;
            foreach ($data as $article) {
                echo "<tr ".($cnt%2==0 ? "class='even'" : "class='odd'").">";
                        echo "<td>";
                            foreach ($article->groups as $group) {
                                echo $group->group;
                            }
                        echo "</td>";
                        echo "<td>";
                            foreach ($article->autos as $auto) {
                                echo $auto->auto;
                            }
                        echo "</td>";
                        echo "<td>";
                            foreach ($article->brands as $brand) {
                                echo $brand->brand;
                            }
                        echo "</td>";
                        echo "<td>".$article->number."</td>";
                        echo "<td>";
                            foreach ($article->originals as $original) {
                                echo $original->original;
                            }
                        echo "</td>";
                        echo "<td>".$article->description."</td>";
                        echo "<td>";
                            foreach ($article->originals as $original) {
                                $articles = new Articles;
                                $criteria = new Criteria($articles);
                                $criteria->condition(array('fk_original'=>array($original->id)));
                                $analogs = $articles->findByCriteria($criteria);
                                if (!empty($analogs)) {
                                    foreach ($analogs as $analog) {
                                        if ($analog->number!=$article->number) {
                                            echo $analog->number."<br>";
                                        }
                                    }
                                }
                            }
                        echo "</td>";
                echo "</tr>";
                $cnt++;
            }
            echo "</table>";


            if (!empty($pages) && is_object($pages)) {
                if ($pages->pageCount>1) {
                    $url = preg_replace('[.page=\d+]', '', $_SERVER['REQUEST_URI']);
                    if (strpos($url, '?')) {
                        $ref = $url."&";
                    }
                    else{
                        $ref = $url."?";
                    }

                    $current = $pages->curPage;
                    $prev = ($current-1>=0 ? $current-1 : null);
                    if ($current==0) {
                        $next = ($current+2<=$pages->pageCount ? $current+2 : null);
                    }
                    else{
                        $next = ($current+1<=$pages->pageCount ? $current+1 : null);
                    }

                    $start = ($current-1>0 ? $current-1 : 1);
                    if ($current==0) {
                        $finish = ($current+2<$pages->pageCount ? $current+2 : $pages->pageCount);
                    }
                    else{
                       $finish = ($current+1<$pages->pageCount ? $current+1 : $pages->pageCount); 
                    }

                    echo "<ul id='pager'>";
                        if (!empty($prev)) {
                            echo "<li><a href='".$ref."page=".$prev."'>Prev.</a></li>";
                        }
                        for ($i=$start; $i<=$finish; $i++) {
                            echo "<li class='".($current==$i || ($current==0 && $i==1) ? "active" : "unactive")."'><a href='".$ref."page=".$i."'>".$i."</a></li>";
                        }
                        if (!empty($next)) {
                            echo "<li><a href='".$ref."page=".$next."'>Next</a></li>";
                        }
                    echo "</ul>";
                }
            }
        }
    }

    public function importAction()
    {
        $data = file_get_contents($_SERVER['DOCUMENT_ROOT'].'/data/test.xml');

        $errors = array();
        $articles = new SimpleXMLElement($data);

        if (!isset($articles->article)) {
            $errors[] = 'Invalid file!';
        }
        else{
            foreach ($articles as $article) {
                $model = new Articles;
                $model->art = $article->id_article;
                $model->number = $article->number;
                $model->description = $article->descr;
                $model->groups = array('group'=>$article->group);
                $model->autos = array('auto'=>$article->auto);
                $model->brands = array('brand'=>$article->brand);
                $model->originals = array('original'=>$article->original);
                $model->save();
            }
        }
    }

}

In importAction() I parse and import simple XML file like this:

    <article>
        <id_article>13</id_article>
        <group>Сайлентблоки</group>
        <auto>VAG</auto>
        <brand>FEBI</brand>
        <number>07623</number>
        <original>431505172</original>
        <descr>Сайлентблок тяги Панара AUDI 8090100200A6</descr>
        <supplier>SMART ЛЕГКОВОЙ</supplier>
        <deadline>24 ч</deadline>
        <rest>10</rest>
    </article>

In pageAction() I build the table contains the data from the DB with some filters.

Here is the dropbox link to get all sources including xml-file and database:

\$\endgroup\$
  • 1
    \$\begingroup\$ Are you deliberately reinventing a web framework? That sounds unconventional, especially if you are working alone or if other developers are much less experienced.. \$\endgroup\$ – 200_success Jun 6 '14 at 11:25
  • 2
    \$\begingroup\$ Yes. I did this to check my php skill, reached in 2 years of programming experience. \$\endgroup\$ – ditas Jun 6 '14 at 14:38
2
\$\begingroup\$

Alright... here it goes! I'll section each part by the headings you gave.


Folders

This all looks fine and dandy. I haven't seen any rules or anything to prevent the public from accessing protected though. Maybe I missed it, but it should be obvious this needs to be accessible only by certain pages (no people).

.htaccess

  • I suggest replacing your flag [L] with [END].

Using the [END] flag terminates not only the current round of rewrite processing (like [L]) but also prevents any subsequent rewrite processing from occurring in per-directory (htaccess) context.

Via Apache.org

index.php

Not too much code here, but I think I'll be commenting on getInstance() later!

config.php

Again, very little code here.

  • If you folders are plural, I think the constants should be too. So MODEL_DIR should be MODELS_DIR.
  • I suggest changing the name of $connectParams to something more meaningful. What are you connecting too? If the name had more to it, that comment before it wouldn't be necessary. I also don't see any need to concatenate "parameters" into "params".
  • The closing parenthesis shouldn't be indented.

init.php

  • Why is this called "init" if all it does is autoload? It does more than just "initialize".
  • Before this project gets too far, I highly recommend two things: namespaces and PHP-FIG's autoloader. For a framework, namespaces will be all too great (see quote), and PHP-FIG really knows their stuff.

    PHP Namespaces provide a way in which to group related classes, interfaces, functions and constants.

    Here's beginner-friendly a namespace introduction.

  • The closing parenthesis of $paths shouldn't be indented.

  • This whole section:

    foreach ($paths as $path) {
        if (file_exists($path.$fileName)) {
            $classFile = $path.$fileName;
            break;
        }
    }
    
    if ($classFile!==false) {
        include $classFile;
    }
    else {
        die('<p>File for class '.$className.' not found!</p>');
    }
    

    can simply be:

    foreach ($paths as $path) {
        if (file_exists($path . $fileName)) {
            if (!(include $path . $fileName)) {
                // Was a file, but it couldn't be included
            }
            break;
        }
    }
    

    Notice $classFile is now eliminated too.

  • I did take out your die() function. I think this is a bad practice and should be avoided, especially in something production worthy. Try and implement a user friendly error reporting system.

core/App.php

  • protected static $instance; Why is this protected? What could this class extend? What in the world is an "instance"? Give the variables some meaning.
  • public static $dbHandler; Why is this public? I think the "public" shouldn't be able to set this, so I say encapsulate and give it a get function. The name is not very readable. Avoid the vague "handlers" and "managers". There are plenty of options.
  • Space out the name and the default in $connectParams=array(). I also suggest type hinting an array for this.
  • The conditional self::$instance==null can really become an easier to read is_null(self::$instance).
  • It's strange that the constructor is protected. Will this class be extended? (I'd say that's unlikely) If not, I'd suggest private.
  • The first four initialization in that constructor seem useless. Check if any of the keys are not set, and then throw an error. There's no point in setting up a database that's guaranteed to fail.
  • What good does it do to echo $e->getMessage();? No one visits this page, so no one will see that message. If they do see it, unwanted information may be contained in it. Again, you need to set up proper error handling.
  • Confusing names here: self::$dbHandler = $dbHandler. I say change both. This line is weird to read.
  • __clone() is dead. What good does it do?
  • Same with __wakeup(). Except he's missing his sister, __sleep(). These two go hand in hand, why one and not the other?
  • Will the default page always be "Site"? I see a lack of future proofing here.
  • $params is a terrible name.
  • All that URL parsing might be in need of parse_url(). That whole chunk needs re-factoring. It's ugly, inconsistent, and difficult to follow.
  • Will the actions always end with "Action"? Future-proof this baby...

core/Criteria.php

  • What's a "Criteria"? Based on the class name, I immediately have no idea what it will do.
  • That's a lot of variables. Do you really need that many? And will they all be extended?
  • I see two public variables, do those two need to be public?
  • I suggest (is_object($model)) be ($modelinstanceofModel).
  • I think you're trying to make a query builder class? I suggest you check out how others do this.

I'm not going to comment on the rest, because I think it could use some major alterations

core/Model.php

  • I don't see the point in this:

    foreach ($test as $key => $value) {
        $lastId = $key;
    }
    

    This must be dead code...

  • There's no default for the switch in the __set(). I recommend one.
  • This:

    foreach ($this->tableFields as $field) {
        if (!empty($field)) {
            $isNotEmpty[] = 1;
        }
        else {
            $isNotEmpty[] = 0;
        }
    }
    if (in_array('1', $isNotEmpty)) {
        return false;
    }
    else {
        return true;
    }
    

    Could simply be:

    foreach ($this->tableFields as $field) {
        if (!empty($field)) {
            return false;
        }
    }
    return true;
    

    Also you should keep the else on the same line as the last bracket.

  • There are no words for how disastrous getResultObjects() looks. Un-nest some of that and figure out a way to reduce the number of loops. It can't be performance friendly either! I would say you've successfully created spaghetti code!

    Spaghetti code is code where “everything is everywhere”, and in which answering simple questions such as (a) where a certain piece of functionality is implemented, (b) determining where an object is instantiated and how to create it, and (c) assessing a critical section for correctness, just to name a few examples of questions one might want to ask about code, require understanding the whole program, because of the relentless pinging about the source code that answer simple questions requires. It’s code that is incomprehensible unless one has the discipline to follow each noodle through from one side to the other. That is spaghetti code.

    From some blog that happened to explain it well

  • I believe if (!empty($result)) { will throw an error if the conditional before that was not met and could not create $result. Unsure of how empty handles that...

  • There are better names than $data, $result, and $id.

core/Controller.php

I'm very confused here. This entire class is dead. Both calling magic methods are dead (producing only an error), the __get has no variables to get, and then all of a sudden we see a redirecting method? I don't get it.

Also, error handling is needed here instead of willy-nilly throwing errors.

core/Pagination.php

  • What is this: if (!$isPaging) return false;? Why do you have the parameter if you're just going to bail the constructor? And why is the constructor returning something?
  • Why is intval($count) needed? Would it ever be something besides an integer? If it does receive a non-int, don't be afraid to properly throw an error.
  • !empty($pageSize) && $isPaging --> !is_null($pageSize). Remember, you bailed if $isPaging is false.
  • $this->pageSize = 10; set this as the default in the arguments list.
  • if ($isPaging) { why?
  • Use a switch for __get($name). It allows for extension.
  • What type of object should is_object($query) be? Use instanceof.

controllers/Site.php

  • isset($_GET['Search']['auto']) && $_GET['Search']['auto']==$auto->id being in a ternary is very messy. Consider either breaking this apart or re-factoring the methods called.
  • This Regexp doesn't make a whole lot of sense: [.page=\d+]. Why is it all in square brackets?
  • You have some incomplete code. You set $errors[] = 'Invalid file!'; but never use the errors...?

XML

  • I find it easier to read if the Id is in the tag, such as <article id="13">.
  • I feel like <article>s should be in <group>s, not vise-versa.

Boy oh boy that was a lot! I hope there was at least one thing someone got out of that! :) I'm now too tired to fix any spelling or grammar. If someone else could get to that, I'd appreciate it, if not, I might go back!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.